diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..297c949 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +app/include/Configuration.inc.php + diff --git a/app/.htaccess b/app/.htaccess new file mode 100644 index 0000000..ce168ba --- /dev/null +++ b/app/.htaccess @@ -0,0 +1,10 @@ +RewriteEngine On + +# Do not remove this line, otherwise mod_rewrite rules will stop working +# RewriteBase / + +RewriteRule ^StyleSheets/(.*)\.css$ lessc.php?filename=$1 [PT,L,QSA] + +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^(.*)$ index.php?virtualpath=$1 [PT,L,QSA] diff --git a/app/include/Configuration.inc.php.example b/app/include/Configuration.inc.php.example new file mode 100644 index 0000000..f8a5009 --- /dev/null +++ b/app/include/Configuration.inc.php.example @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/app/index.php b/app/index.php new file mode 100644 index 0000000..61395b8 --- /dev/null +++ b/app/index.php @@ -0,0 +1,39 @@ +. + // ============================================================================= + + // Load the Phast content (which also include_once's the system modules and + // other Phast-specific stuff) + require_once("lib/phast/System.inc.php"); + + // Bring in the Phast\System and Phast\IncludeFile classes so we can simply refer + // to them (in this file only) as "System" and "IncludeFile", respectively, from + // now on + use Phast\System; + use Phast\IncludeFile; + + // We need to set the root path of the Web site. It's usually something like + // /var/www/yourdomain.com. + System::SetApplicationPath(dirname(__FILE__)); + + // Tell Phast that we are ready to launch the application. This searches the entire + // directory hierarchy for Phast files, loading *.phpx files as Phast XML files and + // *.phpx.php files as PHP code-behind files. We may decide to use the *.ctlx extension + // (and its associated *.ctlx.php) for PHPX controls. + System::Launch(); +?> \ No newline at end of file diff --git a/app/lib/phast/CancelEventArgs.inc.php b/app/lib/phast/CancelEventArgs.inc.php new file mode 100644 index 0000000..3d4aeb8 --- /dev/null +++ b/app/lib/phast/CancelEventArgs.inc.php @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/app/lib/phast/Compilers/StyleSheet/Internal/Formatters/ClassicFormatter.inc.php b/app/lib/phast/Compilers/StyleSheet/Internal/Formatters/ClassicFormatter.inc.php new file mode 100644 index 0000000..8d91cfd --- /dev/null +++ b/app/lib/phast/Compilers/StyleSheet/Internal/Formatters/ClassicFormatter.inc.php @@ -0,0 +1,99 @@ +indentLevel = 0; + } + + public function indentStr($n = 0) { + return str_repeat($this->indentChar, max($this->indentLevel + $n, 0)); + } + + public function property($name, $value) { + return $name . $this->assignSeparator . $value . ";"; + } + + protected function isEmpty($block) { + if (empty($block->lines)) { + foreach ($block->children as $child) { + if (!$this->isEmpty($child)) return false; + } + + return true; + } + return false; + } + + public function block($block) { + if ($this->isEmpty($block)) return; + + $inner = $pre = $this->indentStr(); + + $isSingle = !$this->disableSingle && + is_null($block->type) && count($block->lines) == 1; + + if (!empty($block->selectors)) { + $this->indentLevel++; + + if ($this->breakSelectors) { + $selectorSeparator = $this->selectorSeparator . $this->break . $pre; + } else { + $selectorSeparator = $this->selectorSeparator; + } + + echo $pre . + implode($selectorSeparator, $block->selectors); + if ($isSingle) { + echo $this->openSingle; + $inner = ""; + } else { + echo $this->open . $this->break; + $inner = $this->indentStr(); + } + + } + + if (!empty($block->lines)) { + $glue = $this->break.$inner; + echo $inner . implode($glue, $block->lines); + if (!$isSingle && !empty($block->children)) { + echo $this->break; + } + } + + foreach ($block->children as $child) { + $this->block($child); + } + + if (!empty($block->selectors)) { + if (!$isSingle && empty($block->children)) echo $this->break; + + if ($isSingle) { + echo $this->closeSingle . $this->break; + } else { + echo $pre . $this->close . $this->break; + } + + $this->indentLevel--; + } + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/Compilers/StyleSheet/Internal/Formatters/CompressedFormatter.inc.php b/app/lib/phast/Compilers/StyleSheet/Internal/Formatters/CompressedFormatter.inc.php new file mode 100644 index 0000000..d9438ae --- /dev/null +++ b/app/lib/phast/Compilers/StyleSheet/Internal/Formatters/CompressedFormatter.inc.php @@ -0,0 +1,20 @@ + \ No newline at end of file diff --git a/app/lib/phast/Compilers/StyleSheet/Internal/Formatters/LessJSFormatter.inc.php b/app/lib/phast/Compilers/StyleSheet/Internal/Formatters/LessJSFormatter.inc.php new file mode 100644 index 0000000..d332e5a --- /dev/null +++ b/app/lib/phast/Compilers/StyleSheet/Internal/Formatters/LessJSFormatter.inc.php @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/app/lib/phast/Compilers/StyleSheet/Internal/LessStyleSheetCompiler.inc.php b/app/lib/phast/Compilers/StyleSheet/Internal/LessStyleSheetCompiler.inc.php new file mode 100644 index 0000000..52c112d --- /dev/null +++ b/app/lib/phast/Compilers/StyleSheet/Internal/LessStyleSheetCompiler.inc.php @@ -0,0 +1,3592 @@ +importDir as $dir) { + $full = $dir.(substr($dir, -1) != '/' ? '/' : '').$url; + if ($this->fileExists($file = $full.'.less') || $this->fileExists($file = $full)) { + return $file; + } + } + + return null; + } + + protected function fileExists($name) { + return is_file($name); + } + + static public function compressList($items, $delim) { + if (!isset($items[1]) && isset($items[0])) return $items[0]; + else return array('list', $delim, $items); + } + + static public function preg_quote($what) { + return preg_quote($what, '/'); + } + + protected function tryImport($importPath, $parentBlock, $out) { + + if ($importPath[0] == "function" && $importPath[1] == "url") + { + $importPath = $this->flattenList($importPath[2]); + } + + // BEGIN UGLY HACK: test this!!! 2014-01-21 MBecker + if ($importPath[0] == "list" && $importPath[2][0][0] == "keyword" && $importPath[2][0][1] == "css") + { + echo("@import \"" . $importPath[2][1][2][0] . "\";\n"); + return true; + } + // END UGLY HACK + + $str = $this->coerceString($importPath); + if ($str === null) return false; + + $url = $this->compileValue($this->lib_e($str)); + + // don't import if it ends in css + if (substr_compare($url, '.css', -4, 4) === 0) return false; + + $realPath = $this->findImport($url); + + if ($realPath === null) return false; + + if ($this->importDisabled) { + return array(false, "/* import disabled */"); + } + + if (isset($this->allParsedFiles[realpath($realPath)])) { + return array(false, null); + } + + $this->addParsedFile($realPath); + $parser = $this->makeParser($realPath); + $root = $parser->parse(file_get_contents($realPath)); + + // set the parents of all the block props + foreach ($root->props as $prop) { + if ($prop[0] == "block") { + $prop[1]->parent = $parentBlock; + } + } + + // copy mixins into scope, set their parents + // bring blocks from import into current block + // TODO: need to mark the source parser these came from this file + foreach ($root->children as $childName => $child) { + if (isset($parentBlock->children[$childName])) { + $parentBlock->children[$childName] = array_merge( + $parentBlock->children[$childName], + $child); + } else { + $parentBlock->children[$childName] = $child; + } + } + + $pi = pathinfo($realPath); + $dir = $pi["dirname"]; + + list($top, $bottom) = $this->sortProps($root->props, true); + $this->compileImportedProps($top, $parentBlock, $out, $parser, $dir); + + return array(true, $bottom, $parser, $dir); + } + + protected function compileImportedProps($props, $block, $out, $sourceParser, $importDir) { + $oldSourceParser = $this->sourceParser; + + $oldImport = $this->importDir; + + // TODO: this is because the importDir api is stupid + $this->importDir = (array)$this->importDir; + array_unshift($this->importDir, $importDir); + + if (is_array($props)) + { + foreach ($props as $prop) + { + $this->compileProp($prop, $block, $out); + } + } + + $this->importDir = $oldImport; + $this->sourceParser = $oldSourceParser; + } + + /** + * Recursively compiles a block. + * + * A block is analogous to a CSS block in most cases. A single LESS document + * is encapsulated in a block when parsed, but it does not have parent tags + * so all of it's children appear on the root level when compiled. + * + * Blocks are made up of props and children. + * + * Props are property instructions, array tuples which describe an action + * to be taken, eg. write a property, set a variable, mixin a block. + * + * The children of a block are just all the blocks that are defined within. + * This is used to look up mixins when performing a mixin. + * + * Compiling the block involves pushing a fresh environment on the stack, + * and iterating through the props, compiling each one. + * + * See LessStyleSheetCompiler::compileProp() + * + */ + protected function compileBlock($block) { + switch ($block->type) { + case "root": + $this->compileRoot($block); + break; + case null: + $this->compileCSSBlock($block); + break; + case "media": + $this->compileMedia($block); + break; + case "directive": + $name = "@" . $block->name; + if (!empty($block->value)) { + $name .= " " . $this->compileValue($this->reduce($block->value)); + } + + $this->compileNestedBlock($block, array($name)); + break; + default: + $this->throwError("unknown block type: $block->type\n"); + } + } + + protected function compileCSSBlock($block) { + $env = $this->pushEnv(); + + $selectors = $this->compileSelectors($block->tags); + $env->selectors = $this->multiplySelectors($selectors); + $out = $this->makeOutputBlock(null, $env->selectors); + + $this->scope->children[] = $out; + $this->compileProps($block, $out); + + $block->scope = $env; // mixins carry scope with them! + $this->popEnv(); + } + + protected function compileMedia($media) { + $env = $this->pushEnv($media); + $parentScope = $this->mediaParent($this->scope); + + $query = $this->compileMediaQuery($this->multiplyMedia($env)); + + $this->scope = $this->makeOutputBlock($media->type, array($query)); + $parentScope->children[] = $this->scope; + + $this->compileProps($media, $this->scope); + + if (count($this->scope->lines) > 0) { + $orphanSelelectors = $this->findClosestSelectors(); + if (!is_null($orphanSelelectors)) { + $orphan = $this->makeOutputBlock(null, $orphanSelelectors); + $orphan->lines = $this->scope->lines; + array_unshift($this->scope->children, $orphan); + $this->scope->lines = array(); + } + } + + $this->scope = $this->scope->parent; + $this->popEnv(); + } + + protected function mediaParent($scope) { + while (!empty($scope->parent)) { + if (!empty($scope->type) && $scope->type != "media") { + break; + } + $scope = $scope->parent; + } + + return $scope; + } + + protected function compileNestedBlock($block, $selectors) { + $this->pushEnv($block); + $this->scope = $this->makeOutputBlock($block->type, $selectors); + $this->scope->parent->children[] = $this->scope; + + $this->compileProps($block, $this->scope); + + $this->scope = $this->scope->parent; + $this->popEnv(); + } + + protected function compileRoot($root) { + $this->pushEnv(); + $this->scope = $this->makeOutputBlock($root->type); + $this->compileProps($root, $this->scope); + $this->popEnv(); + } + + protected function compileProps($block, $out) { + foreach ($this->sortProps($block->props) as $prop) { + $this->compileProp($prop, $block, $out); + } + $out->lines = $this->deduplicate($out->lines); + } + + /** + * Deduplicate lines in a block. Comments are not deduplicated. If a + * duplicate rule is detected, the comments immediately preceding each + * occurence are consolidated. + */ + protected function deduplicate($lines) { + $unique = array(); + $comments = array(); + + foreach($lines as $line) { + if (strpos($line, '/*') === 0) { + $comments[] = $line; + continue; + } + if (!in_array($line, $unique)) { + $unique[] = $line; + } + array_splice($unique, array_search($line, $unique), 0, $comments); + $comments = array(); + } + return array_merge($unique, $comments); + } + + protected function sortProps($props, $split = false) { + $vars = array(); + $imports = array(); + $other = array(); + $stack = array(); + + foreach ($props as $prop) { + switch ($prop[0]) { + case "comment": + $stack[] = $prop; + break; + case "assign": + $stack[] = $prop; + if (isset($prop[1][0]) && $prop[1][0] == $this->vPrefix) { + $vars = array_merge($vars, $stack); + } else { + $other = array_merge($other, $stack); + } + $stack = array(); + break; + case "import": + $id = self::$nextImportId++; + $prop[] = $id; + $stack[] = $prop; + $imports = array_merge($imports, $stack); + $other[] = array("import_mixin", $id); + $stack = array(); + break; + default: + $stack[] = $prop; + $other = array_merge($other, $stack); + $stack = array(); + break; + } + } + $other = array_merge($other, $stack); + + if ($split) { + return array(array_merge($vars, $imports), $other); + } else { + return array_merge($vars, $imports, $other); + } + } + + protected function compileMediaQuery($queries) { + $compiledQueries = array(); + foreach ($queries as $query) { + $parts = array(); + foreach ($query as $q) { + switch ($q[0]) { + case "mediaType": + $parts[] = implode(" ", array_slice($q, 1)); + break; + case "mediaExp": + if (isset($q[2])) { + $parts[] = "($q[1]: " . + $this->compileValue($this->reduce($q[2])) . ")"; + } else { + $parts[] = "($q[1])"; + } + break; + case "variable": + $parts[] = $this->compileValue($this->reduce($q)); + break; + } + } + + if (count($parts) > 0) { + $compiledQueries[] = implode(" and ", $parts); + } + } + + $out = "@media"; + if (!empty($parts)) { + $out .= " " . + implode($this->formatter->selectorSeparator, $compiledQueries); + } + return $out; + } + + protected function multiplyMedia($env, $childQueries = null) { + if (is_null($env) || + !empty($env->block->type) && $env->block->type != "media") + { + return $childQueries; + } + + // plain old block, skip + if (empty($env->block->type)) { + return $this->multiplyMedia($env->parent, $childQueries); + } + + $out = array(); + $queries = $env->block->queries; + if (is_null($childQueries)) { + $out = $queries; + } else { + foreach ($queries as $parent) { + foreach ($childQueries as $child) { + $out[] = array_merge($parent, $child); + } + } + } + + return $this->multiplyMedia($env->parent, $out); + } + + protected function expandParentSelectors(&$tag, $replace) { + $parts = explode("$&$", $tag); + $count = 0; + foreach ($parts as &$part) { + $part = str_replace($this->parentSelector, $replace, $part, $c); + $count += $c; + } + $tag = implode($this->parentSelector, $parts); + return $count; + } + + protected function findClosestSelectors() { + $env = $this->env; + $selectors = null; + while ($env !== null) { + if (isset($env->selectors)) { + $selectors = $env->selectors; + break; + } + $env = $env->parent; + } + + return $selectors; + } + + + // multiply $selectors against the nearest selectors in env + protected function multiplySelectors($selectors) { + // find parent selectors + + $parentSelectors = $this->findClosestSelectors(); + if (is_null($parentSelectors)) { + // kill parent reference in top level selector + foreach ($selectors as &$s) { + $this->expandParentSelectors($s, ""); + } + + return $selectors; + } + + $out = array(); + foreach ($parentSelectors as $parent) { + foreach ($selectors as $child) { + $count = $this->expandParentSelectors($child, $parent); + + // don't prepend the parent tag if & was used + if ($count > 0) { + $out[] = trim($child); + } else { + $out[] = trim($parent . ' ' . $child); + } + } + } + + return $out; + } + + // reduces selector expressions + protected function compileSelectors($selectors) { + $out = array(); + + foreach ($selectors as $s) { + if (is_array($s)) { + list(, $value) = $s; + $out[] = trim($this->compileValue($this->reduce($value))); + } else { + $out[] = $s; + } + } + + return $out; + } + + protected function eq($left, $right) { + return $left == $right; + } + + protected function patternMatch($block, $orderedArgs, $keywordArgs) { + // match the guards if it has them + // any one of the groups must have all its guards pass for a match + if (!empty($block->guards)) { + $groupPassed = false; + foreach ($block->guards as $guardGroup) { + foreach ($guardGroup as $guard) { + $this->pushEnv(); + $this->zipSetArgs($block->args, $orderedArgs, $keywordArgs); + + $negate = false; + if ($guard[0] == "negate") { + $guard = $guard[1]; + $negate = true; + } + + $passed = $this->reduce($guard) == self::$TRUE; + if ($negate) $passed = !$passed; + + $this->popEnv(); + + if ($passed) { + $groupPassed = true; + } else { + $groupPassed = false; + break; + } + } + + if ($groupPassed) break; + } + + if (!$groupPassed) { + return false; + } + } + + if (empty($block->args)) { + return $block->isVararg || empty($orderedArgs) && empty($keywordArgs); + } + + $remainingArgs = $block->args; + if ($keywordArgs) { + $remainingArgs = array(); + foreach ($block->args as $arg) { + if ($arg[0] == "arg" && isset($keywordArgs[$arg[1]])) { + continue; + } + + $remainingArgs[] = $arg; + } + } + + $i = -1; // no args + // try to match by arity or by argument literal + foreach ($remainingArgs as $i => $arg) { + switch ($arg[0]) { + case "lit": + if (empty($orderedArgs[$i]) || !$this->eq($arg[1], $orderedArgs[$i])) { + return false; + } + break; + case "arg": + // no arg and no default value + if (!isset($orderedArgs[$i]) && !isset($arg[2])) { + return false; + } + break; + case "rest": + $i--; // rest can be empty + break 2; + } + } + + if ($block->isVararg) { + return true; // not having enough is handled above + } else { + $numMatched = $i + 1; + // greater than becuase default values always match + return $numMatched >= count($orderedArgs); + } + } + + protected function patternMatchAll($blocks, $orderedArgs, $keywordArgs, $skip=array()) { + $matches = null; + foreach ($blocks as $block) { + // skip seen blocks that don't have arguments + if (isset($skip[$block->id]) && !isset($block->args)) { + continue; + } + + if ($this->patternMatch($block, $orderedArgs, $keywordArgs)) { + $matches[] = $block; + } + } + + return $matches; + } + + // attempt to find blocks matched by path and args + protected function findBlocks($searchIn, $path, $orderedArgs, $keywordArgs, $seen=array()) { + if ($searchIn == null) return null; + if (isset($seen[$searchIn->id])) return null; + $seen[$searchIn->id] = true; + + $name = $path[0]; + + if (isset($searchIn->children[$name])) { + $blocks = $searchIn->children[$name]; + if (count($path) == 1) { + $matches = $this->patternMatchAll($blocks, $orderedArgs, $keywordArgs, $seen); + if (!empty($matches)) { + // This will return all blocks that match in the closest + // scope that has any matching block, like lessjs + return $matches; + } + } else { + $matches = array(); + foreach ($blocks as $subBlock) { + $subMatches = $this->findBlocks($subBlock, + array_slice($path, 1), $orderedArgs, $keywordArgs, $seen); + + if (!is_null($subMatches)) { + foreach ($subMatches as $sm) { + $matches[] = $sm; + } + } + } + + return count($matches) > 0 ? $matches : null; + } + } + if ($searchIn->parent === $searchIn) return null; + return $this->findBlocks($searchIn->parent, $path, $orderedArgs, $keywordArgs, $seen); + } + + // sets all argument names in $args to either the default value + // or the one passed in through $values + protected function zipSetArgs($args, $orderedValues, $keywordValues) { + $assignedValues = array(); + + $i = 0; + foreach ($args as $a) { + if ($a[0] == "arg") { + if (isset($keywordValues[$a[1]])) { + // has keyword arg + $value = $keywordValues[$a[1]]; + } elseif (isset($orderedValues[$i])) { + // has ordered arg + $value = $orderedValues[$i]; + $i++; + } elseif (isset($a[2])) { + // has default value + $value = $a[2]; + } else { + $this->throwError("Failed to assign arg " . $a[1]); + $value = null; // :( + } + + $value = $this->reduce($value); + $this->set($a[1], $value); + $assignedValues[] = $value; + } else { + // a lit + $i++; + } + } + + // check for a rest + $last = end($args); + if ($last[0] == "rest") { + $rest = array_slice($orderedValues, count($args) - 1); + $this->set($last[1], $this->reduce(array("list", " ", $rest))); + } + + // wow is this the only true use of PHP's + operator for arrays? + $this->env->arguments = $assignedValues + $orderedValues; + } + + // compile a prop and update $lines or $blocks appropriately + protected function compileProp($prop, $block, $out) { + // set error position context + $this->sourceLoc = isset($prop[-1]) ? $prop[-1] : -1; + + switch ($prop[0]) { + case 'assign': + list(, $name, $value) = $prop; + if ($name[0] == $this->vPrefix) { + $this->set($name, $value); + } else { + $out->lines[] = $this->formatter->property($name, + $this->compileValue($this->reduce($value))); + } + break; + case 'block': + list(, $child) = $prop; + $this->compileBlock($child); + break; + case 'mixin': + list(, $path, $args, $suffix) = $prop; + + $orderedArgs = array(); + $keywordArgs = array(); + foreach ((array)$args as $arg) { + $argval = null; + switch ($arg[0]) { + case "arg": + if (!isset($arg[2])) { + $orderedArgs[] = $this->reduce(array("variable", $arg[1])); + } else { + $keywordArgs[$arg[1]] = $this->reduce($arg[2]); + } + break; + + case "lit": + $orderedArgs[] = $this->reduce($arg[1]); + break; + default: + $this->throwError("Unknown arg type: " . $arg[0]); + } + } + + $mixins = $this->findBlocks($block, $path, $orderedArgs, $keywordArgs); + + if ($mixins === null) { + $this->throwError("{$prop[1][0]} is undefined"); + } + + foreach ($mixins as $mixin) { + if ($mixin === $block && !$orderedArgs) { + continue; + } + + $haveScope = false; + if (isset($mixin->parent->scope)) { + $haveScope = true; + $mixinParentEnv = $this->pushEnv(); + $mixinParentEnv->storeParent = $mixin->parent->scope; + } + + $haveArgs = false; + if (isset($mixin->args)) { + $haveArgs = true; + $this->pushEnv(); + $this->zipSetArgs($mixin->args, $orderedArgs, $keywordArgs); + } + + $oldParent = $mixin->parent; + if ($mixin != $block) $mixin->parent = $block; + + foreach ($this->sortProps($mixin->props) as $subProp) { + if ($suffix !== null && + $subProp[0] == "assign" && + is_string($subProp[1]) && + $subProp[1][0] != $this->vPrefix) + { + $subProp[2] = array( + 'list', ' ', + array($subProp[2], array('keyword', $suffix)) + ); + } + + $this->compileProp($subProp, $mixin, $out); + } + + $mixin->parent = $oldParent; + + if ($haveArgs) $this->popEnv(); + if ($haveScope) $this->popEnv(); + } + + break; + case 'raw': + $out->lines[] = $prop[1]; + break; + case "directive": + list(, $name, $value) = $prop; + $out->lines[] = "@$name " . $this->compileValue($this->reduce($value)).';'; + break; + case "comment": + $out->lines[] = $prop[1]; + break; + case "import"; + list(, $importPath, $importId) = $prop; + $importPath = $this->reduce($importPath); + + if (!isset($this->env->imports)) { + $this->env->imports = array(); + } + + $result = $this->tryImport($importPath, $block, $out); + + $this->env->imports[$importId] = $result === false ? + array(false, "@import " . $this->compileValue($importPath).";") : + $result; + + break; + case "import_mixin": + list(,$importId) = $prop; + $import = $this->env->imports[$importId]; + if ($import[0] === false) { + if (isset($import[1])) { + $out->lines[] = $import[1]; + } + } else { + list(, $bottom, $parser, $importDir) = $import; + $this->compileImportedProps($bottom, $block, $out, $parser, $importDir); + } + + break; + default: + $this->throwError("unknown op: {$prop[0]}\n"); + } + } + + + /** + * Compiles a primitive value into a CSS property value. + * + * Values in lessphp are typed by being wrapped in arrays, their format is + * typically: + * + * array(type, contents [, additional_contents]*) + * + * The input is expected to be reduced. This function will not work on + * things like expressions and variables. + */ + protected function compileValue($value) { + switch ($value[0]) { + case 'list': + // [1] - delimiter + // [2] - array of values + return implode($value[1], array_map(array($this, 'compileValue'), $value[2])); + case 'raw_color': + if (!empty($this->formatter->compressColors)) { + return $this->compileValue($this->coerceColor($value)); + } + return $value[1]; + case 'keyword': + // [1] - the keyword + return $value[1]; + case 'number': + list(, $num, $unit) = $value; + // [1] - the number + // [2] - the unit + if ($this->numberPrecision !== null) { + $num = round($num, $this->numberPrecision); + } + return $num . $unit; + case 'string': + // [1] - contents of string (includes quotes) + list(, $delim, $content) = $value; + foreach ($content as &$part) { + if (is_array($part)) { + $part = $this->compileValue($part); + } + } + return $delim . implode($content) . $delim; + case 'color': + // [1] - red component (either number or a %) + // [2] - green component + // [3] - blue component + // [4] - optional alpha component + list(, $r, $g, $b) = $value; + $r = round($r); + $g = round($g); + $b = round($b); + + if (count($value) == 5 && $value[4] != 1) { // rgba + return 'rgba('.$r.','.$g.','.$b.','.$value[4].')'; + } + + $h = sprintf("#%02x%02x%02x", $r, $g, $b); + + if (!empty($this->formatter->compressColors)) { + // Converting hex color to short notation (e.g. #003399 to #039) + if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) { + $h = '#' . $h[1] . $h[3] . $h[5]; + } + } + + return $h; + + case 'function': + list(, $name, $args) = $value; + return $name.'('.$this->compileValue($args).')'; + default: // assumed to be unit + $this->throwError("unknown value type: $value[0]"); + } + } + + protected function lib_pow($args) { + list($base, $exp) = $this->assertArgs($args, 2, "pow"); + return pow($this->assertNumber($base), $this->assertNumber($exp)); + } + + protected function lib_pi() { + return pi(); + } + + protected function lib_mod($args) { + list($a, $b) = $this->assertArgs($args, 2, "mod"); + return $this->assertNumber($a) % $this->assertNumber($b); + } + + protected function lib_tan($num) { + return tan($this->assertNumber($num)); + } + + protected function lib_sin($num) { + return sin($this->assertNumber($num)); + } + + protected function lib_cos($num) { + return cos($this->assertNumber($num)); + } + + protected function lib_atan($num) { + $num = atan($this->assertNumber($num)); + return array("number", $num, "rad"); + } + + protected function lib_asin($num) { + $num = asin($this->assertNumber($num)); + return array("number", $num, "rad"); + } + + protected function lib_acos($num) { + $num = acos($this->assertNumber($num)); + return array("number", $num, "rad"); + } + + protected function lib_sqrt($num) { + return sqrt($this->assertNumber($num)); + } + + protected function lib_extract($value) { + list($list, $idx) = $this->assertArgs($value, 2, "extract"); + $idx = $this->assertNumber($idx); + // 1 indexed + if ($list[0] == "list" && isset($list[2][$idx - 1])) { + return $list[2][$idx - 1]; + } + } + + protected function lib_isnumber($value) { + return $this->toBool($value[0] == "number"); + } + + protected function lib_isstring($value) { + return $this->toBool($value[0] == "string"); + } + + protected function lib_iscolor($value) { + return $this->toBool($this->coerceColor($value)); + } + + protected function lib_iskeyword($value) { + return $this->toBool($value[0] == "keyword"); + } + + protected function lib_ispixel($value) { + return $this->toBool($value[0] == "number" && $value[2] == "px"); + } + + protected function lib_ispercentage($value) { + return $this->toBool($value[0] == "number" && $value[2] == "%"); + } + + protected function lib_isem($value) { + return $this->toBool($value[0] == "number" && $value[2] == "em"); + } + + protected function lib_isrem($value) { + return $this->toBool($value[0] == "number" && $value[2] == "rem"); + } + + protected function lib_rgbahex($color) { + $color = $this->coerceColor($color); + if (is_null($color)) + $this->throwError("color expected for rgbahex"); + + return sprintf("#%02x%02x%02x%02x", + isset($color[4]) ? $color[4]*255 : 255, + $color[1],$color[2], $color[3]); + } + + protected function lib_argb($color){ + return $this->lib_rgbahex($color); + } + + // utility func to unquote a string + protected function lib_e($arg) { + switch ($arg[0]) { + case "list": + $items = $arg[2]; + if (isset($items[0])) { + return $this->lib_e($items[0]); + } + $this->throwError("unrecognised input"); + case "string": + $arg[1] = ""; + return $arg; + case "keyword": + return $arg; + default: + return array("keyword", $this->compileValue($arg)); + } + } + + protected function lib__sprintf($args) { + if ($args[0] != "list") return $args; + $values = $args[2]; + $string = array_shift($values); + $template = $this->compileValue($this->lib_e($string)); + + $i = 0; + if (preg_match_all('/%[dsa]/', $template, $m)) { + foreach ($m[0] as $match) { + $val = isset($values[$i]) ? + $this->reduce($values[$i]) : array('keyword', ''); + + // lessjs compat, renders fully expanded color, not raw color + if ($color = $this->coerceColor($val)) { + $val = $color; + } + + $i++; + $rep = $this->compileValue($this->lib_e($val)); + $template = preg_replace('/'.self::preg_quote($match).'/', + $rep, $template, 1); + } + } + + $d = $string[0] == "string" ? $string[1] : '"'; + return array("string", $d, array($template)); + } + + protected function lib_floor($arg) { + $value = $this->assertNumber($arg); + return array("number", floor($value), $arg[2]); + } + + protected function lib_ceil($arg) { + $value = $this->assertNumber($arg); + return array("number", ceil($value), $arg[2]); + } + + protected function lib_round($arg) { + if($arg[0] != "list") { + $value = $this->assertNumber($arg); + return array("number", round($value), $arg[2]); + } else { + $value = $this->assertNumber($arg[2][0]); + $precision = $this->assertNumber($arg[2][1]); + return array("number", round($value, $precision), $arg[2][0][2]); + } + } + + protected function lib_unit($arg) { + if ($arg[0] == "list") { + list($number, $newUnit) = $arg[2]; + return array("number", $this->assertNumber($number), + $this->compileValue($this->lib_e($newUnit))); + } else { + return array("number", $this->assertNumber($arg), ""); + } + } + + /** + * Helper function to get arguments for color manipulation functions. + * takes a list that contains a color like thing and a percentage + */ + public function colorArgs($args) { + if ($args[0] != 'list' || count($args[2]) < 2) { + return array(array('color', 0, 0, 0), 0); + } + list($color, $delta) = $args[2]; + $color = $this->assertColor($color); + $delta = floatval($delta[1]); + + return array($color, $delta); + } + + protected function lib_darken($args) { + list($color, $delta) = $this->colorArgs($args); + + $hsl = $this->toHSL($color); + $hsl[3] = $this->clamp($hsl[3] - $delta, 100); + return $this->toRGB($hsl); + } + + protected function lib_lighten($args) { + list($color, $delta) = $this->colorArgs($args); + + $hsl = $this->toHSL($color); + $hsl[3] = $this->clamp($hsl[3] + $delta, 100); + return $this->toRGB($hsl); + } + + protected function lib_saturate($args) { + list($color, $delta) = $this->colorArgs($args); + + $hsl = $this->toHSL($color); + $hsl[2] = $this->clamp($hsl[2] + $delta, 100); + return $this->toRGB($hsl); + } + + protected function lib_desaturate($args) { + list($color, $delta) = $this->colorArgs($args); + + $hsl = $this->toHSL($color); + $hsl[2] = $this->clamp($hsl[2] - $delta, 100); + return $this->toRGB($hsl); + } + + protected function lib_spin($args) { + list($color, $delta) = $this->colorArgs($args); + + $hsl = $this->toHSL($color); + + $hsl[1] = $hsl[1] + $delta % 360; + if ($hsl[1] < 0) $hsl[1] += 360; + + return $this->toRGB($hsl); + } + + protected function lib_fadeout($args) { + list($color, $delta) = $this->colorArgs($args); + $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta/100); + return $color; + } + + protected function lib_fadein($args) { + list($color, $delta) = $this->colorArgs($args); + $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta/100); + return $color; + } + + protected function lib_hue($color) { + $hsl = $this->toHSL($this->assertColor($color)); + return round($hsl[1]); + } + + protected function lib_saturation($color) { + $hsl = $this->toHSL($this->assertColor($color)); + return round($hsl[2]); + } + + protected function lib_lightness($color) { + $hsl = $this->toHSL($this->assertColor($color)); + return round($hsl[3]); + } + + // get the alpha of a color + // defaults to 1 for non-colors or colors without an alpha + protected function lib_alpha($value) { + if (!is_null($color = $this->coerceColor($value))) { + return isset($color[4]) ? $color[4] : 1; + } + } + + // set the alpha of the color + protected function lib_fade($args) { + list($color, $alpha) = $this->colorArgs($args); + $color[4] = $this->clamp($alpha / 100.0); + return $color; + } + + protected function lib_percentage($arg) { + $num = $this->assertNumber($arg); + return array("number", $num*100, "%"); + } + + // mixes two colors by weight + // mix(@color1, @color2, [@weight: 50%]); + // http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method + protected function lib_mix($args) { + if ($args[0] != "list" || count($args[2]) < 2) + $this->throwError("mix expects (color1, color2, weight)"); + + list($first, $second) = $args[2]; + $first = $this->assertColor($first); + $second = $this->assertColor($second); + + $first_a = $this->lib_alpha($first); + $second_a = $this->lib_alpha($second); + + if (isset($args[2][2])) { + $weight = $args[2][2][1] / 100.0; + } else { + $weight = 0.5; + } + + $w = $weight * 2 - 1; + $a = $first_a - $second_a; + + $w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0; + $w2 = 1.0 - $w1; + + $new = array('color', + $w1 * $first[1] + $w2 * $second[1], + $w1 * $first[2] + $w2 * $second[2], + $w1 * $first[3] + $w2 * $second[3], + ); + + if ($first_a != 1.0 || $second_a != 1.0) { + $new[] = $first_a * $weight + $second_a * ($weight - 1); + } + + return $this->fixColor($new); + } + + protected function lib_contrast($args) { + $darkColor = array('color', 0, 0, 0); + $lightColor = array('color', 255, 255, 255); + $threshold = 0.43; + + if ( $args[0] == 'list' ) { + $inputColor = ( isset($args[2][0]) ) ? $this->assertColor($args[2][0]) : $lightColor; + $darkColor = ( isset($args[2][1]) ) ? $this->assertColor($args[2][1]) : $darkColor; + $lightColor = ( isset($args[2][2]) ) ? $this->assertColor($args[2][2]) : $lightColor; + $threshold = ( isset($args[2][3]) ) ? $this->assertNumber($args[2][3]) : $threshold; + } + else { + $inputColor = $this->assertColor($args); + } + + $inputColor = $this->coerceColor($inputColor); + $darkColor = $this->coerceColor($darkColor); + $lightColor = $this->coerceColor($lightColor); + + //Figure out which is actually light and dark! + if ( $this->lib_luma($darkColor) > $this->lib_luma($lightColor) ) { + $t = $lightColor; + $lightColor = $darkColor; + $darkColor = $t; + } + + $inputColor_alpha = $this->lib_alpha($inputColor); + if ( ( $this->lib_luma($inputColor) * $inputColor_alpha) < $threshold) { + return $lightColor; + } + return $darkColor; + } + + protected function lib_luma($color) { + $color = $this->coerceColor($color); + return (0.2126 * $color[0] / 255) + (0.7152 * $color[1] / 255) + (0.0722 * $color[2] / 255); + } + + + public function assertColor($value, $error = "expected color value") { + $color = $this->coerceColor($value); + if (is_null($color)) $this->throwError($error); + return $color; + } + + public function assertNumber($value, $error = "expecting number") { + if ($value[0] == "number") return $value[1]; + $this->throwError($error); + } + + public function assertArgs($value, $expectedArgs, $name="") { + if ($expectedArgs == 1) { + return $value; + } else { + if ($value[0] !== "list" || $value[1] != ",") $this->throwError("expecting list"); + $values = $value[2]; + $numValues = count($values); + if ($expectedArgs != $numValues) { + if ($name) { + $name = $name . ": "; + } + + $this->throwError("${name}expecting $expectedArgs arguments, got $numValues"); + } + + return $values; + } + } + + protected function toHSL($color) { + if ($color[0] == 'hsl') return $color; + + $r = $color[1] / 255; + $g = $color[2] / 255; + $b = $color[3] / 255; + + $min = min($r, $g, $b); + $max = max($r, $g, $b); + + $L = ($min + $max) / 2; + if ($min == $max) { + $S = $H = 0; + } else { + if ($L < 0.5) + $S = ($max - $min)/($max + $min); + else + $S = ($max - $min)/(2.0 - $max - $min); + + if ($r == $max) $H = ($g - $b)/($max - $min); + elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min); + elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min); + + } + + $out = array('hsl', + ($H < 0 ? $H + 6 : $H)*60, + $S*100, + $L*100, + ); + + if (count($color) > 4) $out[] = $color[4]; // copy alpha + return $out; + } + + protected function toRGB_helper($comp, $temp1, $temp2) { + if ($comp < 0) $comp += 1.0; + elseif ($comp > 1) $comp -= 1.0; + + if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp; + if (2 * $comp < 1) return $temp2; + if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6; + + return $temp1; + } + + /** + * Converts a hsl array into a color value in rgb. + * Expects H to be in range of 0 to 360, S and L in 0 to 100 + */ + protected function toRGB($color) { + if ($color[0] == 'color') return $color; + + $H = $color[1] / 360; + $S = $color[2] / 100; + $L = $color[3] / 100; + + if ($S == 0) { + $r = $g = $b = $L; + } else { + $temp2 = $L < 0.5 ? + $L*(1.0 + $S) : + $L + $S - $L * $S; + + $temp1 = 2.0 * $L - $temp2; + + $r = $this->toRGB_helper($H + 1/3, $temp1, $temp2); + $g = $this->toRGB_helper($H, $temp1, $temp2); + $b = $this->toRGB_helper($H - 1/3, $temp1, $temp2); + } + + // $out = array('color', round($r*255), round($g*255), round($b*255)); + $out = array('color', $r*255, $g*255, $b*255); + if (count($color) > 4) $out[] = $color[4]; // copy alpha + return $out; + } + + protected function clamp($v, $max = 1, $min = 0) { + return min($max, max($min, $v)); + } + + /** + * Convert the rgb, rgba, hsl color literals of function type + * as returned by the parser into values of color type. + */ + protected function funcToColor($func) { + $fname = $func[1]; + if ($func[2][0] != 'list') return false; // need a list of arguments + $rawComponents = $func[2][2]; + + if ($fname == 'hsl' || $fname == 'hsla') { + $hsl = array('hsl'); + $i = 0; + foreach ($rawComponents as $c) { + $val = $this->reduce($c); + $val = isset($val[1]) ? floatval($val[1]) : 0; + + if ($i == 0) $clamp = 360; + elseif ($i < 3) $clamp = 100; + else $clamp = 1; + + $hsl[] = $this->clamp($val, $clamp); + $i++; + } + + while (count($hsl) < 4) $hsl[] = 0; + return $this->toRGB($hsl); + + } elseif ($fname == 'rgb' || $fname == 'rgba') { + $components = array(); + $i = 1; + foreach ($rawComponents as $c) { + $c = $this->reduce($c); + if ($i < 4) { + if ($c[0] == "number" && $c[2] == "%") { + $components[] = 255 * ($c[1] / 100); + } else { + $components[] = floatval($c[1]); + } + } elseif ($i == 4) { + if ($c[0] == "number" && $c[2] == "%") { + $components[] = 1.0 * ($c[1] / 100); + } else { + $components[] = floatval($c[1]); + } + } else break; + + $i++; + } + while (count($components) < 3) $components[] = 0; + array_unshift($components, 'color'); + return $this->fixColor($components); + } + + return false; + } + + protected function reduce($value, $forExpression = false) { + switch ($value[0]) { + case "interpolate": + $reduced = $this->reduce($value[1]); + $var = $this->compileValue($reduced); + $res = $this->reduce(array("variable", $this->vPrefix . $var)); + + if ($res[0] == "raw_color") { + $res = $this->coerceColor($res); + } + + if (empty($value[2])) $res = $this->lib_e($res); + + return $res; + case "variable": + $key = $value[1]; + if (is_array($key)) { + $key = $this->reduce($key); + $key = $this->vPrefix . $this->compileValue($this->lib_e($key)); + } + + $seen =& $this->env->seenNames; + + if (!empty($seen[$key])) { + $this->throwError("infinite loop detected: $key"); + } + + $seen[$key] = true; + $out = $this->reduce($this->get($key)); + $seen[$key] = false; + return $out; + case "list": + foreach ($value[2] as &$item) { + $item = $this->reduce($item, $forExpression); + } + return $value; + case "expression": + return $this->evaluate($value); + case "string": + foreach ($value[2] as &$part) { + if (is_array($part)) { + $strip = $part[0] == "variable"; + $part = $this->reduce($part); + if ($strip) $part = $this->lib_e($part); + } + } + return $value; + case "escape": + list(,$inner) = $value; + return $this->lib_e($this->reduce($inner)); + case "function": + $color = $this->funcToColor($value); + if ($color) return $color; + + list(, $name, $args) = $value; + if ($name == "%") $name = "_sprintf"; + $f = isset($this->libFunctions[$name]) ? + $this->libFunctions[$name] : array($this, 'lib_'.$name); + + if (is_callable($f)) { + if ($args[0] == 'list') + $args = self::compressList($args[2], $args[1]); + + $ret = call_user_func($f, $this->reduce($args, true), $this); + + if (is_null($ret)) { + return array("string", "", array( + $name, "(", $args, ")" + )); + } + + // convert to a typed value if the result is a php primitive + if (is_numeric($ret)) $ret = array('number', $ret, ""); + elseif (!is_array($ret)) $ret = array('keyword', $ret); + + return $ret; + } + + // plain function, reduce args + $value[2] = $this->reduce($value[2]); + return $value; + case "unary": + list(, $op, $exp) = $value; + $exp = $this->reduce($exp); + + if ($exp[0] == "number") { + switch ($op) { + case "+": + return $exp; + case "-": + $exp[1] *= -1; + return $exp; + } + } + return array("string", "", array($op, $exp)); + } + + if ($forExpression) { + switch ($value[0]) { + case "keyword": + if ($color = $this->coerceColor($value)) { + return $color; + } + break; + case "raw_color": + return $this->coerceColor($value); + } + } + + return $value; + } + + + // coerce a value for use in color operation + protected function coerceColor($value) { + switch($value[0]) { + case 'color': return $value; + case 'raw_color': + $c = array("color", 0, 0, 0); + $colorStr = substr($value[1], 1); + $num = hexdec($colorStr); + $width = strlen($colorStr) == 3 ? 16 : 256; + + for ($i = 3; $i > 0; $i--) { // 3 2 1 + $t = $num % $width; + $num /= $width; + + $c[$i] = $t * (256/$width) + $t * floor(16/$width); + } + + return $c; + case 'keyword': + $name = $value[1]; + if (isset(self::$cssColors[$name])) { + $rgba = explode(',', self::$cssColors[$name]); + + if(isset($rgba[3])) + return array('color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]); + + return array('color', $rgba[0], $rgba[1], $rgba[2]); + } + return null; + } + } + + // make something string like into a string + protected function coerceString($value) { + switch ($value[0]) { + case "string": + return $value; + case "keyword": + return array("string", "", array($value[1])); + } + return null; + } + + // turn list of length 1 into value type + protected function flattenList($value) { + if ($value[0] == "list" && count($value[2]) == 1) { + return $this->flattenList($value[2][0]); + } + return $value; + } + + public function toBool($a) { + if ($a) return self::$TRUE; + else return self::$FALSE; + } + + // evaluate an expression + protected function evaluate($exp) { + list(, $op, $left, $right, $whiteBefore, $whiteAfter) = $exp; + + $left = $this->reduce($left, true); + $right = $this->reduce($right, true); + + if ($leftColor = $this->coerceColor($left)) { + $left = $leftColor; + } + + if ($rightColor = $this->coerceColor($right)) { + $right = $rightColor; + } + + $ltype = $left[0]; + $rtype = $right[0]; + + // operators that work on all types + if ($op == "and") { + return $this->toBool($left == self::$TRUE && $right == self::$TRUE); + } + + if ($op == "=") { + return $this->toBool($this->eq($left, $right) ); + } + + if ($op == "+" && !is_null($str = $this->stringConcatenate($left, $right))) { + return $str; + } + + // type based operators + $fname = "op_${ltype}_${rtype}"; + if (is_callable(array($this, $fname))) { + $out = $this->$fname($op, $left, $right); + if (!is_null($out)) return $out; + } + + // make the expression look it did before being parsed + $paddedOp = $op; + if ($whiteBefore) $paddedOp = " " . $paddedOp; + if ($whiteAfter) $paddedOp .= " "; + + return array("string", "", array($left, $paddedOp, $right)); + } + + protected function stringConcatenate($left, $right) { + if ($strLeft = $this->coerceString($left)) { + if ($right[0] == "string") { + $right[1] = ""; + } + $strLeft[2][] = $right; + return $strLeft; + } + + if ($strRight = $this->coerceString($right)) { + array_unshift($strRight[2], $left); + return $strRight; + } + } + + + // make sure a color's components don't go out of bounds + protected function fixColor($c) { + foreach (range(1, 3) as $i) { + if ($c[$i] < 0) $c[$i] = 0; + if ($c[$i] > 255) $c[$i] = 255; + } + + return $c; + } + + protected function op_number_color($op, $lft, $rgt) { + if ($op == '+' || $op == '*') { + return $this->op_color_number($op, $rgt, $lft); + } + } + + protected function op_color_number($op, $lft, $rgt) { + if ($rgt[0] == '%') $rgt[1] /= 100; + + return $this->op_color_color($op, $lft, + array_fill(1, count($lft) - 1, $rgt[1])); + } + + protected function op_color_color($op, $left, $right) { + $out = array('color'); + $max = count($left) > count($right) ? count($left) : count($right); + foreach (range(1, $max - 1) as $i) { + $lval = isset($left[$i]) ? $left[$i] : 0; + $rval = isset($right[$i]) ? $right[$i] : 0; + switch ($op) { + case '+': + $out[] = $lval + $rval; + break; + case '-': + $out[] = $lval - $rval; + break; + case '*': + $out[] = $lval * $rval; + break; + case '%': + $out[] = $lval % $rval; + break; + case '/': + if ($rval == 0) $this->throwError("evaluate error: can't divide by zero"); + $out[] = $lval / $rval; + break; + default: + $this->throwError('evaluate error: color op number failed on op '.$op); + } + } + return $this->fixColor($out); + } + + function lib_red($color){ + $color = $this->coerceColor($color); + if (is_null($color)) { + $this->throwError('color expected for red()'); + } + + return $color[1]; + } + + function lib_green($color){ + $color = $this->coerceColor($color); + if (is_null($color)) { + $this->throwError('color expected for green()'); + } + + return $color[2]; + } + + function lib_blue($color){ + $color = $this->coerceColor($color); + if (is_null($color)) { + $this->throwError('color expected for blue()'); + } + + return $color[3]; + } + + + // operator on two numbers + protected function op_number_number($op, $left, $right) { + $unit = empty($left[2]) ? $right[2] : $left[2]; + + $value = 0; + switch ($op) { + case '+': + $value = $left[1] + $right[1]; + break; + case '*': + $value = $left[1] * $right[1]; + break; + case '-': + $value = $left[1] - $right[1]; + break; + case '%': + $value = $left[1] % $right[1]; + break; + case '/': + if ($right[1] == 0) $this->throwError('parse error: divide by zero'); + $value = $left[1] / $right[1]; + break; + case '<': + return $this->toBool($left[1] < $right[1]); + case '>': + return $this->toBool($left[1] > $right[1]); + case '>=': + return $this->toBool($left[1] >= $right[1]); + case '=<': + return $this->toBool($left[1] <= $right[1]); + default: + $this->throwError('parse error: unknown number operator: '.$op); + } + + return array("number", $value, $unit); + } + + + /* environment functions */ + + protected function makeOutputBlock($type, $selectors = null) { + $b = new stdclass; + $b->lines = array(); + $b->children = array(); + $b->selectors = $selectors; + $b->type = $type; + $b->parent = $this->scope; + return $b; + } + + // the state of execution + protected function pushEnv($block = null) { + $e = new stdclass; + $e->parent = $this->env; + $e->store = array(); + $e->block = $block; + + $this->env = $e; + return $e; + } + + // pop something off the stack + protected function popEnv() { + $old = $this->env; + $this->env = $this->env->parent; + return $old; + } + + // set something in the current env + protected function set($name, $value) { + $this->env->store[$name] = $value; + } + + + // get the highest occurrence entry for a name + protected function get($name) { + if (array_key_exists($name, $this->overrideVars)) + { + return array("raw_color", $this->overrideVars[$name]); + } + + $current = $this->env; + + $isArguments = $name == $this->vPrefix . 'arguments'; + while ($current) { + if ($isArguments && isset($current->arguments)) { + return array('list', ' ', $current->arguments); + } + + if (isset($current->store[$name])) + return $current->store[$name]; + else { + $current = isset($current->storeParent) ? + $current->storeParent : $current->parent; + } + } + + $this->throwError("variable $name is undefined"); + } + + // inject array of unparsed strings into environment as variables + protected function injectVariables($args) { + $this->pushEnv(); + $parser = new LessStyleSheetParser($this, __METHOD__); + foreach ($args as $name => $strValue) { + if ($name[0] != '@') $name = '@'.$name; + $parser->count = 0; + $parser->buffer = (string)$strValue; + if (!$parser->propertyValue($value)) { + throw new Exception("failed to parse passed in variable $name: $strValue"); + } + + $this->set($name, $value); + } + } + + /** + * Initialize any static state, can initialize parser for a file + * $opts isn't used yet + */ + public function __construct($fname = null) { + if ($fname !== null) { + // used for deprecated parse method + $this->_parseFile = $fname; + } + } + + public function compile($string, $name = null) { + $locale = setlocale(LC_NUMERIC, 0); + setlocale(LC_NUMERIC, "C"); + + $this->parser = $this->makeParser($name); + $root = $this->parser->parse($string); + + $this->env = null; + $this->scope = null; + + if (!empty($this->registeredVars)) { + $this->injectVariables($this->registeredVars); + } + + $this->sourceParser = $this->parser; // used for error messages + $this->compileBlock($root); + + ob_start(); + $this->formatter->block($this->scope); + $out = ob_get_clean(); + setlocale(LC_NUMERIC, $locale); + return $out; + } + + public function compileFile($fname, $outFname = null) { + if (!is_readable($fname)) { + throw new Exception('load error: failed to find '.$fname); + } + + $pi = pathinfo($fname); + + $oldImport = $this->importDir; + + $this->importDir = (array)$this->importDir; + $this->importDir[] = $pi['dirname'].'/'; + + $this->addParsedFile($fname); + + $out = $this->compile(file_get_contents($fname), $fname); + + $this->importDir = $oldImport; + + if ($outFname !== null) { + return file_put_contents($outFname, $out); + } + + return $out; + } + + // compile only if changed input has changed or output doesn't exist + public function checkedCompile($in, $out) { + if (!is_file($out) || filemtime($in) > filemtime($out)) { + $this->compileFile($in, $out); + return true; + } + return false; + } + + /** + * Execute lessphp on a .less file or a lessphp cache structure + * + * The lessphp cache structure contains information about a specific + * less file having been parsed. It can be used as a hint for future + * calls to determine whether or not a rebuild is required. + * + * The cache structure contains two important keys that may be used + * externally: + * + * compiled: The final compiled CSS + * updated: The time (in seconds) the CSS was last compiled + * + * The cache structure is a plain-ol' PHP associative array and can + * be serialized and unserialized without a hitch. + * + * @param mixed $in Input + * @param bool $force Force rebuild? + * @return array lessphp cache structure + */ + public function cachedCompile($in, $force = false) { + // assume no root + $root = null; + + if (is_string($in)) { + $root = $in; + } elseif (is_array($in) and isset($in['root'])) { + if ($force or ! isset($in['files'])) { + // If we are forcing a recompile or if for some reason the + // structure does not contain any file information we should + // specify the root to trigger a rebuild. + $root = $in['root']; + } elseif (isset($in['files']) and is_array($in['files'])) { + foreach ($in['files'] as $fname => $ftime ) { + if (!file_exists($fname) or filemtime($fname) > $ftime) { + // One of the files we knew about previously has changed + // so we should look at our incoming root again. + $root = $in['root']; + break; + } + } + } + } else { + // TODO: Throw an exception? We got neither a string nor something + // that looks like a compatible lessphp cache structure. + return null; + } + + if ($root !== null) { + // If we have a root value which means we should rebuild. + $out = array(); + $out['root'] = $root; + $out['compiled'] = $this->compileFile($root); + $out['files'] = $this->allParsedFiles(); + $out['updated'] = time(); + return $out; + } else { + // No changes, pass back the structure + // we were given initially. + return $in; + } + + } + + // parse and compile buffer + // This is deprecated + public function parse($str = null, $initialVariables = null) { + if (is_array($str)) { + $initialVariables = $str; + $str = null; + } + + $oldVars = $this->registeredVars; + if ($initialVariables !== null) { + $this->setVariables($initialVariables); + } + + if ($str == null) { + if (empty($this->_parseFile)) { + throw new exception("nothing to parse"); + } + + $out = $this->compileFile($this->_parseFile); + } else { + $out = $this->compile($str); + } + + $this->registeredVars = $oldVars; + return $out; + } + + protected function makeParser($name) { + $parser = new LessStyleSheetParser($this, $name); + $parser->writeComments = $this->preserveComments; + + return $parser; + } + + public $formatter; + + public function setPreserveComments($preserve) { + $this->preserveComments = $preserve; + } + + public function registerFunction($name, $func) { + $this->libFunctions[$name] = $func; + } + + public function unregisterFunction($name) { + unset($this->libFunctions[$name]); + } + + public function setVariables($variables) { + $this->registeredVars = array_merge($this->registeredVars, $variables); + } + + public function unsetVariable($name) { + unset($this->registeredVars[$name]); + } + + public function setImportDir($dirs) { + $this->importDir = (array)$dirs; + } + + public function addImportDir($dir) { + $this->importDir = (array)$this->importDir; + $this->importDir[] = $dir; + } + + public function allParsedFiles() { + return $this->allParsedFiles; + } + + public function addParsedFile($file) { + $this->allParsedFiles[realpath($file)] = filemtime($file); + } + + /** + * Uses the current value of $this->count to show line and line number + */ + public function throwError($msg = null) { + if ($this->sourceLoc >= 0) { + $this->sourceParser->throwError($msg, $this->sourceLoc); + } + throw new exception($msg); + } + + // compile file $in to file $out if $in is newer than $out + // returns true when it compiles, false otherwise + public static function ccompile($in, $out, $less = null) { + if ($less === null) { + $less = new self; + } + return $less->checkedCompile($in, $out); + } + + public static function cexecute($in, $force = false, $less = null) { + if ($less === null) { + $less = new self; + } + return $less->cachedCompile($in, $force); + } + + static protected $cssColors = array( + 'aliceblue' => '240,248,255', + 'antiquewhite' => '250,235,215', + 'aqua' => '0,255,255', + 'aquamarine' => '127,255,212', + 'azure' => '240,255,255', + 'beige' => '245,245,220', + 'bisque' => '255,228,196', + 'black' => '0,0,0', + 'blanchedalmond' => '255,235,205', + 'blue' => '0,0,255', + 'blueviolet' => '138,43,226', + 'brown' => '165,42,42', + 'burlywood' => '222,184,135', + 'cadetblue' => '95,158,160', + 'chartreuse' => '127,255,0', + 'chocolate' => '210,105,30', + 'coral' => '255,127,80', + 'cornflowerblue' => '100,149,237', + 'cornsilk' => '255,248,220', + 'crimson' => '220,20,60', + 'cyan' => '0,255,255', + 'darkblue' => '0,0,139', + 'darkcyan' => '0,139,139', + 'darkgoldenrod' => '184,134,11', + 'darkgray' => '169,169,169', + 'darkgreen' => '0,100,0', + 'darkgrey' => '169,169,169', + 'darkkhaki' => '189,183,107', + 'darkmagenta' => '139,0,139', + 'darkolivegreen' => '85,107,47', + 'darkorange' => '255,140,0', + 'darkorchid' => '153,50,204', + 'darkred' => '139,0,0', + 'darksalmon' => '233,150,122', + 'darkseagreen' => '143,188,143', + 'darkslateblue' => '72,61,139', + 'darkslategray' => '47,79,79', + 'darkslategrey' => '47,79,79', + 'darkturquoise' => '0,206,209', + 'darkviolet' => '148,0,211', + 'deeppink' => '255,20,147', + 'deepskyblue' => '0,191,255', + 'dimgray' => '105,105,105', + 'dimgrey' => '105,105,105', + 'dodgerblue' => '30,144,255', + 'firebrick' => '178,34,34', + 'floralwhite' => '255,250,240', + 'forestgreen' => '34,139,34', + 'fuchsia' => '255,0,255', + 'gainsboro' => '220,220,220', + 'ghostwhite' => '248,248,255', + 'gold' => '255,215,0', + 'goldenrod' => '218,165,32', + 'gray' => '128,128,128', + 'green' => '0,128,0', + 'greenyellow' => '173,255,47', + 'grey' => '128,128,128', + 'honeydew' => '240,255,240', + 'hotpink' => '255,105,180', + 'indianred' => '205,92,92', + 'indigo' => '75,0,130', + 'ivory' => '255,255,240', + 'khaki' => '240,230,140', + 'lavender' => '230,230,250', + 'lavenderblush' => '255,240,245', + 'lawngreen' => '124,252,0', + 'lemonchiffon' => '255,250,205', + 'lightblue' => '173,216,230', + 'lightcoral' => '240,128,128', + 'lightcyan' => '224,255,255', + 'lightgoldenrodyellow' => '250,250,210', + 'lightgray' => '211,211,211', + 'lightgreen' => '144,238,144', + 'lightgrey' => '211,211,211', + 'lightpink' => '255,182,193', + 'lightsalmon' => '255,160,122', + 'lightseagreen' => '32,178,170', + 'lightskyblue' => '135,206,250', + 'lightslategray' => '119,136,153', + 'lightslategrey' => '119,136,153', + 'lightsteelblue' => '176,196,222', + 'lightyellow' => '255,255,224', + 'lime' => '0,255,0', + 'limegreen' => '50,205,50', + 'linen' => '250,240,230', + 'magenta' => '255,0,255', + 'maroon' => '128,0,0', + 'mediumaquamarine' => '102,205,170', + 'mediumblue' => '0,0,205', + 'mediumorchid' => '186,85,211', + 'mediumpurple' => '147,112,219', + 'mediumseagreen' => '60,179,113', + 'mediumslateblue' => '123,104,238', + 'mediumspringgreen' => '0,250,154', + 'mediumturquoise' => '72,209,204', + 'mediumvioletred' => '199,21,133', + 'midnightblue' => '25,25,112', + 'mintcream' => '245,255,250', + 'mistyrose' => '255,228,225', + 'moccasin' => '255,228,181', + 'navajowhite' => '255,222,173', + 'navy' => '0,0,128', + 'oldlace' => '253,245,230', + 'olive' => '128,128,0', + 'olivedrab' => '107,142,35', + 'orange' => '255,165,0', + 'orangered' => '255,69,0', + 'orchid' => '218,112,214', + 'palegoldenrod' => '238,232,170', + 'palegreen' => '152,251,152', + 'paleturquoise' => '175,238,238', + 'palevioletred' => '219,112,147', + 'papayawhip' => '255,239,213', + 'peachpuff' => '255,218,185', + 'peru' => '205,133,63', + 'pink' => '255,192,203', + 'plum' => '221,160,221', + 'powderblue' => '176,224,230', + 'purple' => '128,0,128', + 'red' => '255,0,0', + 'rosybrown' => '188,143,143', + 'royalblue' => '65,105,225', + 'saddlebrown' => '139,69,19', + 'salmon' => '250,128,114', + 'sandybrown' => '244,164,96', + 'seagreen' => '46,139,87', + 'seashell' => '255,245,238', + 'sienna' => '160,82,45', + 'silver' => '192,192,192', + 'skyblue' => '135,206,235', + 'slateblue' => '106,90,205', + 'slategray' => '112,128,144', + 'slategrey' => '112,128,144', + 'snow' => '255,250,250', + 'springgreen' => '0,255,127', + 'steelblue' => '70,130,180', + 'tan' => '210,180,140', + 'teal' => '0,128,128', + 'thistle' => '216,191,216', + 'tomato' => '255,99,71', + 'transparent' => '0,0,0,0', + 'turquoise' => '64,224,208', + 'violet' => '238,130,238', + 'wheat' => '245,222,179', + 'white' => '255,255,255', + 'whitesmoke' => '245,245,245', + 'yellow' => '255,255,0', + 'yellowgreen' => '154,205,50' + ); + } + + // responsible for taking a string of LESS code and converting it into a + // syntax tree + class LessStyleSheetParser { + static protected $nextBlockId = 0; // used to uniquely identify blocks + + static protected $precedence = array( + '=<' => 0, + '>=' => 0, + '=' => 0, + '<' => 0, + '>' => 0, + + '+' => 1, + '-' => 1, + '*' => 2, + '/' => 2, + '%' => 2, + ); + + static protected $whitePattern; + static protected $commentMulti; + + static protected $commentSingle = "//"; + static protected $commentMultiLeft = "/*"; + static protected $commentMultiRight = "*/"; + + // regex string to match any of the operators + static protected $operatorString; + + // these properties will supress division unless it's inside parenthases + static protected $supressDivisionProps = + array('/border-radius$/i', '/^font$/i'); + + protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document", "viewport", "-moz-viewport", "-o-viewport", "-ms-viewport"); + protected $lineDirectives = array("charset"); + + /** + * if we are in parens we can be more liberal with whitespace around + * operators because it must evaluate to a single value and thus is less + * ambiguous. + * + * Consider: + * property1: 10 -5; // is two numbers, 10 and -5 + * property2: (10 -5); // should evaluate to 5 + */ + protected $inParens = false; + + // caches preg escaped literals + static protected $literalCache = array(); + + public function __construct($lessc, $sourceName = null) { + $this->eatWhiteDefault = true; + // reference to less needed for vPrefix, mPrefix, and parentSelector + $this->lessc = $lessc; + + $this->sourceName = $sourceName; // name used for error messages + + $this->writeComments = false; + + if (!self::$operatorString) { + self::$operatorString = + '('.implode('|', array_map(array('lessc', 'preg_quote'), + array_keys(self::$precedence))).')'; + + $commentSingle = LessStyleSheetCompiler::preg_quote(self::$commentSingle); + $commentMultiLeft = LessStyleSheetCompiler::preg_quote(self::$commentMultiLeft); + $commentMultiRight = LessStyleSheetCompiler::preg_quote(self::$commentMultiRight); + + self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight; + self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais'; + } + } + + public function parse($buffer) { + $this->count = 0; + $this->line = 1; + + $this->env = null; // block stack + $this->buffer = $this->writeComments ? $buffer : $this->removeComments($buffer); + $this->pushSpecialBlock("root"); + $this->eatWhiteDefault = true; + $this->seenComments = array(); + + // trim whitespace on head + // if (preg_match('/^\s+/', $this->buffer, $m)) { + // $this->line += substr_count($m[0], "\n"); + // $this->buffer = ltrim($this->buffer); + // } + $this->whitespace(); + + // parse the entire file + while (false !== $this->parseChunk()); + + if ($this->count != strlen($this->buffer)) + $this->throwError(); + + // TODO report where the block was opened + if ( !property_exists($this->env, 'parent') || !is_null($this->env->parent) ) + throw new exception('parse error: unclosed block'); + + return $this->env; + } + + /** + * Parse a single chunk off the head of the buffer and append it to the + * current parse environment. + * Returns false when the buffer is empty, or when there is an error. + * + * This function is called repeatedly until the entire document is + * parsed. + * + * This parser is most similar to a recursive descent parser. Single + * functions represent discrete grammatical rules for the language, and + * they are able to capture the text that represents those rules. + * + * Consider the function LessStyleSheetCompiler::keyword(). (all parse functions are + * structured the same) + * + * The function takes a single reference argument. When calling the + * function it will attempt to match a keyword on the head of the buffer. + * If it is successful, it will place the keyword in the referenced + * argument, advance the position in the buffer, and return true. If it + * fails then it won't advance the buffer and it will return false. + * + * All of these parse functions are powered by LessStyleSheetCompiler::match(), which behaves + * the same way, but takes a literal regular expression. Sometimes it is + * more convenient to use match instead of creating a new function. + * + * Because of the format of the functions, to parse an entire string of + * grammatical rules, you can chain them together using &&. + * + * But, if some of the rules in the chain succeed before one fails, then + * the buffer position will be left at an invalid state. In order to + * avoid this, LessStyleSheetCompiler::seek() is used to remember and set buffer positions. + * + * Before parsing a chain, use $s = $this->seek() to remember the current + * position into $s. Then if a chain fails, use $this->seek($s) to + * go back where we started. + */ + protected function parseChunk() { + if (empty($this->buffer)) return false; + $s = $this->seek(); + + if ($this->whitespace()) { + return true; + } + + // setting a property + if ($this->keyword($key) && $this->assign() && + $this->propertyValue($value, $key) && $this->end()) + { + $this->append(array('assign', $key, $value), $s); + return true; + } else { + $this->seek($s); + } + + + // look for special css blocks + if ($this->literal('@', false)) { + $this->count--; + + // media + if ($this->literal('@media')) { + if (($this->mediaQueryList($mediaQueries) || true) + && $this->literal('{')) + { + $media = $this->pushSpecialBlock("media"); + $media->queries = is_null($mediaQueries) ? array() : $mediaQueries; + return true; + } else { + $this->seek($s); + return false; + } + } + + if ($this->literal("@", false) && $this->keyword($dirName)) { + if ($this->isDirective($dirName, $this->blockDirectives)) { + if (($this->openString("{", $dirValue, null, array(";")) || true) && + $this->literal("{")) + { + $dir = $this->pushSpecialBlock("directive"); + $dir->name = $dirName; + if (isset($dirValue)) $dir->value = $dirValue; + return true; + } + } elseif ($this->isDirective($dirName, $this->lineDirectives)) { + if ($this->propertyValue($dirValue) && $this->end()) { + $this->append(array("directive", $dirName, $dirValue)); + return true; + } + } + } + + $this->seek($s); + } + + // setting a variable + if ($this->variable($var) && $this->assign() && + $this->propertyValue($value) && $this->end()) + { + $this->append(array('assign', $var, $value), $s); + return true; + } else { + $this->seek($s); + } + + if ($this->import($importValue)) { + $this->append($importValue, $s); + return true; + } + + // opening parametric mixin + if ($this->tag($tag, true) && $this->argumentDef($args, $isVararg) && + ($this->guards($guards) || true) && + $this->literal('{')) + { + $block = $this->pushBlock($this->fixTags(array($tag))); + $block->args = $args; + $block->isVararg = $isVararg; + if (!empty($guards)) $block->guards = $guards; + return true; + } else { + $this->seek($s); + } + + // opening a simple block + if ($this->tags($tags) && $this->literal('{', false)) { + $tags = $this->fixTags($tags); + $this->pushBlock($tags); + return true; + } else { + $this->seek($s); + } + + // closing a block + if ($this->literal('}', false)) { + try { + $block = $this->pop(); + } catch (exception $e) { + $this->seek($s); + $this->throwError($e->getMessage()); + } + + $hidden = false; + if (is_null($block->type)) { + $hidden = true; + if (!isset($block->args)) { + foreach ($block->tags as $tag) { + if (!is_string($tag) || $tag[0] != $this->lessc->mPrefix) { + $hidden = false; + break; + } + } + } + + foreach ($block->tags as $tag) { + if (is_string($tag)) { + $this->env->children[$tag][] = $block; + } + } + } + + if (!$hidden) { + $this->append(array('block', $block), $s); + } + + // this is done here so comments aren't bundled into he block that + // was just closed + $this->whitespace(); + return true; + } + + // mixin + if ($this->mixinTags($tags) && + ($this->argumentDef($argv, $isVararg) || true) && + ($this->keyword($suffix) || true) && $this->end()) + { + $tags = $this->fixTags($tags); + $this->append(array('mixin', $tags, $argv, $suffix), $s); + return true; + } else { + $this->seek($s); + } + + // spare ; + if ($this->literal(';')) return true; + + return false; // got nothing, throw error + } + + protected function isDirective($dirname, $directives) { + // TODO: cache pattern in parser + $pattern = implode("|", + array_map(array("lessc", "preg_quote"), $directives)); + $pattern = '/^(-[a-z-]+-)?(' . $pattern . ')$/i'; + + return preg_match($pattern, $dirname); + } + + protected function fixTags($tags) { + // move @ tags out of variable namespace + foreach ($tags as &$tag) { + if ($tag[0] == $this->lessc->vPrefix) + $tag[0] = $this->lessc->mPrefix; + } + return $tags; + } + + // a list of expressions + protected function expressionList(&$exps) { + $values = array(); + + while ($this->expression($exp)) { + $values[] = $exp; + } + + if (count($values) == 0) return false; + + $exps = LessStyleSheetCompiler::compressList($values, ' '); + return true; + } + + /** + * Attempt to consume an expression. + * @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code + */ + protected function expression(&$out) { + if ($this->value($lhs)) { + $out = $this->expHelper($lhs, 0); + + // look for / shorthand + if (!empty($this->env->supressedDivision)) { + unset($this->env->supressedDivision); + $s = $this->seek(); + if ($this->literal("/") && $this->value($rhs)) { + $out = array("list", "", + array($out, array("keyword", "/"), $rhs)); + } else { + $this->seek($s); + } + } + + return true; + } + return false; + } + + /** + * recursively parse infix equation with $lhs at precedence $minP + */ + protected function expHelper($lhs, $minP) { + $this->inExp = true; + $ss = $this->seek(); + + while (true) { + $whiteBefore = isset($this->buffer[$this->count - 1]) && + ctype_space($this->buffer[$this->count - 1]); + + // If there is whitespace before the operator, then we require + // whitespace after the operator for it to be an expression + $needWhite = $whiteBefore && !$this->inParens; + + if ($this->match(self::$operatorString.($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) { + if (!$this->inParens && isset($this->env->currentProperty) && $m[1] == "/" && empty($this->env->supressedDivision)) { + foreach (self::$supressDivisionProps as $pattern) { + if (preg_match($pattern, $this->env->currentProperty)) { + $this->env->supressedDivision = true; + break 2; + } + } + } + + + $whiteAfter = isset($this->buffer[$this->count - 1]) && + ctype_space($this->buffer[$this->count - 1]); + + if (!$this->value($rhs)) break; + + // peek for next operator to see what to do with rhs + if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]]) { + $rhs = $this->expHelper($rhs, self::$precedence[$next[1]]); + } + + $lhs = array('expression', $m[1], $lhs, $rhs, $whiteBefore, $whiteAfter); + $ss = $this->seek(); + + continue; + } + + break; + } + + $this->seek($ss); + + return $lhs; + } + + // consume a list of values for a property + public function propertyValue(&$value, $keyName = null) { + $values = array(); + + if ($keyName !== null) $this->env->currentProperty = $keyName; + + $s = null; + while ($this->expressionList($v)) { + $values[] = $v; + $s = $this->seek(); + if (!$this->literal(',')) break; + } + + if ($s) $this->seek($s); + + if ($keyName !== null) unset($this->env->currentProperty); + + if (count($values) == 0) return false; + + $value = LessStyleSheetCompiler::compressList($values, ', '); + return true; + } + + protected function parenValue(&$out) { + $s = $this->seek(); + + // speed shortcut + if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "(") { + return false; + } + + $inParens = $this->inParens; + if ($this->literal("(") && + ($this->inParens = true) && $this->expression($exp) && + $this->literal(")")) + { + $out = $exp; + $this->inParens = $inParens; + return true; + } else { + $this->inParens = $inParens; + $this->seek($s); + } + + return false; + } + + // a single value + protected function value(&$value) { + $s = $this->seek(); + + // speed shortcut + if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "-") { + // negation + if ($this->literal("-", false) && + (($this->variable($inner) && $inner = array("variable", $inner)) || + $this->unit($inner) || + $this->parenValue($inner))) + { + $value = array("unary", "-", $inner); + return true; + } else { + $this->seek($s); + } + } + + if ($this->parenValue($value)) return true; + if ($this->unit($value)) return true; + if ($this->color($value)) return true; + if ($this->func($value)) return true; + if ($this->string($value)) return true; + + if ($this->keyword($word)) { + $value = array('keyword', $word); + return true; + } + + // try a variable + if ($this->variable($var)) { + $value = array('variable', $var); + return true; + } + + // unquote string (should this work on any type? + if ($this->literal("~") && $this->string($str)) { + $value = array("escape", $str); + return true; + } else { + $this->seek($s); + } + + // css hack: \0 + if ($this->literal('\\') && $this->match('([0-9]+)', $m)) { + $value = array('keyword', '\\'.$m[1]); + return true; + } else { + $this->seek($s); + } + + return false; + } + + // an import statement + protected function import(&$out) { + if (!$this->literal('@import')) return false; + + // @import "something.css" media; + // @import url("something.css") media; + // @import url(something.css) media; + + if ($this->propertyValue($value)) { + $out = array("import", $value); + return true; + } + } + + protected function mediaQueryList(&$out) { + if ($this->genericList($list, "mediaQuery", ",", false)) { + $out = $list[2]; + return true; + } + return false; + } + + protected function mediaQuery(&$out) { + $s = $this->seek(); + + $expressions = null; + $parts = array(); + + if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->keyword($mediaType)) { + $prop = array("mediaType"); + if (isset($only)) $prop[] = "only"; + if (isset($not)) $prop[] = "not"; + $prop[] = $mediaType; + $parts[] = $prop; + } else { + $this->seek($s); + } + + + if (!empty($mediaType) && !$this->literal("and")) { + // ~ + } else { + $this->genericList($expressions, "mediaExpression", "and", false); + if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]); + } + + if (count($parts) == 0) { + $this->seek($s); + return false; + } + + $out = $parts; + return true; + } + + protected function mediaExpression(&$out) { + $s = $this->seek(); + $value = null; + if ($this->literal("(") && + $this->keyword($feature) && + ($this->literal(":") && $this->expression($value) || true) && + $this->literal(")")) + { + $out = array("mediaExp", $feature); + if ($value) $out[] = $value; + return true; + } elseif ($this->variable($variable)) { + $out = array('variable', $variable); + return true; + } + + $this->seek($s); + return false; + } + + // an unbounded string stopped by $end + protected function openString($end, &$out, $nestingOpen=null, $rejectStrs = null) { + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + $stop = array("'", '"', "@{", $end); + $stop = array_map(array("lessc", "preg_quote"), $stop); + // $stop[] = self::$commentMulti; + + if (!is_null($rejectStrs)) { + $stop = array_merge($stop, $rejectStrs); + } + + $patt = '(.*?)('.implode("|", $stop).')'; + + $nestingLevel = 0; + + $content = array(); + while ($this->match($patt, $m, false)) { + if (!empty($m[1])) { + $content[] = $m[1]; + if ($nestingOpen) { + $nestingLevel += substr_count($m[1], $nestingOpen); + } + } + + $tok = $m[2]; + + $this->count-= strlen($tok); + if ($tok == $end) { + if ($nestingLevel == 0) { + break; + } else { + $nestingLevel--; + } + } + + if (($tok == "'" || $tok == '"') && $this->string($str)) { + $content[] = $str; + continue; + } + + if ($tok == "@{" && $this->interpolation($inter)) { + $content[] = $inter; + continue; + } + + if (!empty($rejectStrs) && in_array($tok, $rejectStrs)) { + break; + } + + $content[] = $tok; + $this->count+= strlen($tok); + } + + $this->eatWhiteDefault = $oldWhite; + + if (count($content) == 0) return false; + + // trim the end + if (is_string(end($content))) { + $content[count($content) - 1] = rtrim(end($content)); + } + + $out = array("string", "", $content); + return true; + } + + protected function string(&$out) { + $s = $this->seek(); + if ($this->literal('"', false)) { + $delim = '"'; + } elseif ($this->literal("'", false)) { + $delim = "'"; + } else { + return false; + } + + $content = array(); + + // look for either ending delim , escape, or string interpolation + $patt = '([^\n]*?)(@\{|\\\\|' . + LessStyleSheetCompiler::preg_quote($delim).')'; + + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + while ($this->match($patt, $m, false)) { + $content[] = $m[1]; + if ($m[2] == "@{") { + $this->count -= strlen($m[2]); + if ($this->interpolation($inter, false)) { + $content[] = $inter; + } else { + $this->count += strlen($m[2]); + $content[] = "@{"; // ignore it + } + } elseif ($m[2] == '\\') { + $content[] = $m[2]; + if ($this->literal($delim, false)) { + $content[] = $delim; + } + } else { + $this->count -= strlen($delim); + break; // delim + } + } + + $this->eatWhiteDefault = $oldWhite; + + if ($this->literal($delim)) { + $out = array("string", $delim, $content); + return true; + } + + $this->seek($s); + return false; + } + + protected function interpolation(&$out) { + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = true; + + $s = $this->seek(); + if ($this->literal("@{") && + $this->openString("}", $interp, null, array("'", '"', ";")) && + $this->literal("}", false)) + { + $out = array("interpolate", $interp); + $this->eatWhiteDefault = $oldWhite; + if ($this->eatWhiteDefault) $this->whitespace(); + return true; + } + + $this->eatWhiteDefault = $oldWhite; + $this->seek($s); + return false; + } + + protected function unit(&$unit) { + // speed shortcut + if (isset($this->buffer[$this->count])) { + $char = $this->buffer[$this->count]; + if (!ctype_digit($char) && $char != ".") return false; + } + + if ($this->match('([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?', $m)) { + $unit = array("number", $m[1], empty($m[2]) ? "" : $m[2]); + return true; + } + return false; + } + + // a # color + protected function color(&$out) { + if ($this->match('(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))', $m)) { + if (strlen($m[1]) > 7) { + $out = array("string", "", array($m[1])); + } else { + $out = array("raw_color", $m[1]); + } + return true; + } + + return false; + } + + // consume an argument definition list surrounded by () + // each argument is a variable name with optional value + // or at the end a ... or a variable named followed by ... + // arguments are separated by , unless a ; is in the list, then ; is the + // delimiter. + protected function argumentDef(&$args, &$isVararg) { + $s = $this->seek(); + if (!$this->literal('(')) return false; + + $values = array(); + $delim = ","; + $method = "expressionList"; + + $isVararg = false; + while (true) { + if ($this->literal("...")) { + $isVararg = true; + break; + } + + if ($this->$method($value)) { + if ($value[0] == "variable") { + $arg = array("arg", $value[1]); + $ss = $this->seek(); + + if ($this->assign() && $this->$method($rhs)) { + $arg[] = $rhs; + } else { + $this->seek($ss); + if ($this->literal("...")) { + $arg[0] = "rest"; + $isVararg = true; + } + } + + $values[] = $arg; + if ($isVararg) break; + continue; + } else { + $values[] = array("lit", $value); + } + } + + + if (!$this->literal($delim)) { + if ($delim == "," && $this->literal(";")) { + // found new delim, convert existing args + $delim = ";"; + $method = "propertyValue"; + + // transform arg list + if (isset($values[1])) { // 2 items + $newList = array(); + foreach ($values as $i => $arg) { + switch($arg[0]) { + case "arg": + if ($i) { + $this->throwError("Cannot mix ; and , as delimiter types"); + } + $newList[] = $arg[2]; + break; + case "lit": + $newList[] = $arg[1]; + break; + case "rest": + $this->throwError("Unexpected rest before semicolon"); + } + } + + $newList = array("list", ", ", $newList); + + switch ($values[0][0]) { + case "arg": + $newArg = array("arg", $values[0][1], $newList); + break; + case "lit": + $newArg = array("lit", $newList); + break; + } + + } elseif ($values) { // 1 item + $newArg = $values[0]; + } + + if ($newArg) { + $values = array($newArg); + } + } else { + break; + } + } + } + + if (!$this->literal(')')) { + $this->seek($s); + return false; + } + + $args = $values; + + return true; + } + + // consume a list of tags + // this accepts a hanging delimiter + protected function tags(&$tags, $simple = false, $delim = ',') { + $tags = array(); + while ($this->tag($tt, $simple)) { + $tags[] = $tt; + if (!$this->literal($delim)) break; + } + if (count($tags) == 0) return false; + + return true; + } + + // list of tags of specifying mixin path + // optionally separated by > (lazy, accepts extra >) + protected function mixinTags(&$tags) { + $tags = array(); + while ($this->tag($tt, true)) { + $tags[] = $tt; + $this->literal(">"); + } + + if (count($tags) == 0) return false; + + return true; + } + + // a bracketed value (contained within in a tag definition) + protected function tagBracket(&$parts, &$hasExpression) { + // speed shortcut + if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "[") { + return false; + } + + $s = $this->seek(); + + $hasInterpolation = false; + + if ($this->literal("[", false)) { + $attrParts = array("["); + // keyword, string, operator + while (true) { + if ($this->literal("]", false)) { + $this->count--; + break; // get out early + } + + if ($this->match('\s+', $m)) { + $attrParts[] = " "; + continue; + } + if ($this->string($str)) { + // escape parent selector, (yuck) + foreach ($str[2] as &$chunk) { + $chunk = str_replace($this->lessc->parentSelector, "$&$", $chunk); + } + + $attrParts[] = $str; + $hasInterpolation = true; + continue; + } + + if ($this->keyword($word)) { + $attrParts[] = $word; + continue; + } + + if ($this->interpolation($inter, false)) { + $attrParts[] = $inter; + $hasInterpolation = true; + continue; + } + + // operator, handles attr namespace too + if ($this->match('[|-~\$\*\^=]+', $m)) { + $attrParts[] = $m[0]; + continue; + } + + break; + } + + if ($this->literal("]", false)) { + $attrParts[] = "]"; + foreach ($attrParts as $part) { + $parts[] = $part; + } + $hasExpression = $hasExpression || $hasInterpolation; + return true; + } + $this->seek($s); + } + + $this->seek($s); + return false; + } + + // a space separated list of selectors + protected function tag(&$tag, $simple = false) { + if ($simple) + $chars = '^@,:;{}\][>\(\) "\''; + else + $chars = '^@,;{}["\''; + + $s = $this->seek(); + + $hasExpression = false; + $parts = array(); + while ($this->tagBracket($parts, $hasExpression)); + + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + while (true) { + if ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) { + $parts[] = $m[1]; + if ($simple) break; + + while ($this->tagBracket($parts, $hasExpression)); + continue; + } + + if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") { + if ($this->interpolation($interp)) { + $hasExpression = true; + $interp[2] = true; // don't unescape + $parts[] = $interp; + continue; + } + + if ($this->literal("@")) { + $parts[] = "@"; + continue; + } + } + + if ($this->unit($unit)) { // for keyframes + $parts[] = $unit[1]; + $parts[] = $unit[2]; + continue; + } + + break; + } + + $this->eatWhiteDefault = $oldWhite; + if (!$parts) { + $this->seek($s); + return false; + } + + if ($hasExpression) { + $tag = array("exp", array("string", "", $parts)); + } else { + $tag = trim(implode($parts)); + } + + $this->whitespace(); + return true; + } + + // a css function + protected function func(&$func) { + $s = $this->seek(); + + if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal('(')) { + $fname = $m[1]; + + $sPreArgs = $this->seek(); + + $args = array(); + while (true) { + $ss = $this->seek(); + // this ugly nonsense is for ie filter properties + if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) { + $args[] = array("string", "", array($name, "=", $value)); + } else { + $this->seek($ss); + if ($this->expressionList($value)) { + $args[] = $value; + } + } + + if (!$this->literal(',')) break; + } + $args = array('list', ',', $args); + + if ($this->literal(')')) { + $func = array('function', $fname, $args); + return true; + } elseif ($fname == 'url') { + // couldn't parse and in url? treat as string + $this->seek($sPreArgs); + if ($this->openString(")", $string) && $this->literal(")")) { + $func = array('function', $fname, $string); + return true; + } + } + } + + $this->seek($s); + return false; + } + + // consume a less variable + protected function variable(&$name) { + $s = $this->seek(); + if ($this->literal($this->lessc->vPrefix, false) && + ($this->variable($sub) || $this->keyword($name))) + { + if (!empty($sub)) { + $name = array('variable', $sub); + } else { + $name = $this->lessc->vPrefix.$name; + } + return true; + } + + $name = null; + $this->seek($s); + return false; + } + + /** + * Consume an assignment operator + * Can optionally take a name that will be set to the current property name + */ + protected function assign($name = null) { + if ($name) $this->currentProperty = $name; + return $this->literal(':') || $this->literal('='); + } + + // consume a keyword + protected function keyword(&$word) { + if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) { + $word = $m[1]; + return true; + } + return false; + } + + // consume an end of statement delimiter + protected function end() { + if ($this->literal(';', false)) { + return true; + } elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') { + // if there is end of file or a closing block next then we don't need a ; + return true; + } + return false; + } + + protected function guards(&$guards) { + $s = $this->seek(); + + if (!$this->literal("when")) { + $this->seek($s); + return false; + } + + $guards = array(); + + while ($this->guardGroup($g)) { + $guards[] = $g; + if (!$this->literal(",")) break; + } + + if (count($guards) == 0) { + $guards = null; + $this->seek($s); + return false; + } + + return true; + } + + // a bunch of guards that are and'd together + // TODO rename to guardGroup + protected function guardGroup(&$guardGroup) { + $s = $this->seek(); + $guardGroup = array(); + while ($this->guard($guard)) { + $guardGroup[] = $guard; + if (!$this->literal("and")) break; + } + + if (count($guardGroup) == 0) { + $guardGroup = null; + $this->seek($s); + return false; + } + + return true; + } + + protected function guard(&$guard) { + $s = $this->seek(); + $negate = $this->literal("not"); + + if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) { + $guard = $exp; + if ($negate) $guard = array("negate", $guard); + return true; + } + + $this->seek($s); + return false; + } + + /* raw parsing functions */ + + protected function literal($what, $eatWhitespace = null) { + if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault; + + // shortcut on single letter + if (!isset($what[1]) && isset($this->buffer[$this->count])) { + if ($this->buffer[$this->count] == $what) { + if (!$eatWhitespace) { + $this->count++; + return true; + } + // goes below... + } else { + return false; + } + } + + if (!isset(self::$literalCache[$what])) { + self::$literalCache[$what] = LessStyleSheetCompiler::preg_quote($what); + } + + return $this->match(self::$literalCache[$what], $m, $eatWhitespace); + } + + protected function genericList(&$out, $parseItem, $delim="", $flatten=true) { + $s = $this->seek(); + $items = array(); + while ($this->$parseItem($value)) { + $items[] = $value; + if ($delim) { + if (!$this->literal($delim)) break; + } + } + + if (count($items) == 0) { + $this->seek($s); + return false; + } + + if ($flatten && count($items) == 1) { + $out = $items[0]; + } else { + $out = array("list", $delim, $items); + } + + return true; + } + + + // advance counter to next occurrence of $what + // $until - don't include $what in advance + // $allowNewline, if string, will be used as valid char set + protected function to($what, &$out, $until = false, $allowNewline = false) { + if (is_string($allowNewline)) { + $validChars = $allowNewline; + } else { + $validChars = $allowNewline ? "." : "[^\n]"; + } + if (!$this->match('('.$validChars.'*?)'.LessStyleSheetCompiler::preg_quote($what), $m, !$until)) return false; + if ($until) $this->count -= strlen($what); // give back $what + $out = $m[1]; + return true; + } + + // try to match something on head of buffer + protected function match($regex, &$out, $eatWhitespace = null) { + if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault; + + $r = '/'.$regex.($eatWhitespace && !$this->writeComments ? '\s*' : '').'/Ais'; + if (preg_match($r, $this->buffer, $out, null, $this->count)) { + $this->count += strlen($out[0]); + if ($eatWhitespace && $this->writeComments) $this->whitespace(); + return true; + } + return false; + } + + // match some whitespace + protected function whitespace() { + if ($this->writeComments) { + $gotWhite = false; + while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) { + if (isset($m[1]) && empty($this->seenComments[$this->count])) { + $this->append(array("comment", $m[1])); + $this->seenComments[$this->count] = true; + } + $this->count += strlen($m[0]); + $gotWhite = true; + } + return $gotWhite; + } else { + $this->match("", $m); + return strlen($m[0]) > 0; + } + } + + // match something without consuming it + protected function peek($regex, &$out = null, $from=null) { + if (is_null($from)) $from = $this->count; + $r = '/'.$regex.'/Ais'; + $result = preg_match($r, $this->buffer, $out, null, $from); + + return $result; + } + + // seek to a spot in the buffer or return where we are on no argument + protected function seek($where = null) { + if ($where === null) return $this->count; + else $this->count = $where; + return true; + } + + /* misc functions */ + + public function throwError($msg = "parse error", $count = null) { + $count = is_null($count) ? $this->count : $count; + + $line = $this->line + + substr_count(substr($this->buffer, 0, $count), "\n"); + + if (!empty($this->sourceName)) { + $loc = "$this->sourceName on line $line"; + } else { + $loc = "line: $line"; + } + + // TODO this depends on $this->count + if ($this->peek("(.*?)(\n|$)", $m, $count)) { + throw new exception("$msg: failed at `$m[1]` $loc"); + } else { + throw new exception("$msg: $loc"); + } + } + + protected function pushBlock($selectors=null, $type=null) { + $b = new stdclass; + $b->parent = $this->env; + + $b->type = $type; + $b->id = self::$nextBlockId++; + + $b->isVararg = false; // TODO: kill me from here + $b->tags = $selectors; + + $b->props = array(); + $b->children = array(); + + $this->env = $b; + return $b; + } + + // push a block that doesn't multiply tags + protected function pushSpecialBlock($type) { + return $this->pushBlock(null, $type); + } + + // append a property to the current block + protected function append($prop, $pos = null) { + if ($pos !== null) $prop[-1] = $pos; + $this->env->props[] = $prop; + } + + // pop something off the stack + protected function pop() { + $old = $this->env; + $this->env = $this->env->parent; + return $old; + } + + // remove comments from $text + // todo: make it work for all functions, not just url + protected function removeComments($text) { + $look = array( + 'url(', '//', '/*', '"', "'" + ); + + $out = ''; + $min = null; + while (true) { + // find the next item + foreach ($look as $token) { + $pos = strpos($text, $token); + if ($pos !== false) { + if (!isset($min) || $pos < $min[1]) $min = array($token, $pos); + } + } + + if (is_null($min)) break; + + $count = $min[1]; + $skip = 0; + $newlines = 0; + switch ($min[0]) { + case 'url(': + if (preg_match('/url\(.*?\)/', $text, $m, 0, $count)) + $count += strlen($m[0]) - strlen($min[0]); + break; + case '"': + case "'": + if (preg_match('/'.$min[0].'.*?(? \ No newline at end of file diff --git a/app/lib/phast/Conditionals/ConditionalComparison.inc.php b/app/lib/phast/Conditionals/ConditionalComparison.inc.php new file mode 100644 index 0000000..258bbf8 --- /dev/null +++ b/app/lib/phast/Conditionals/ConditionalComparison.inc.php @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/app/lib/phast/Conditionals/ConditionalStatement.inc.php b/app/lib/phast/Conditionals/ConditionalStatement.inc.php new file mode 100644 index 0000000..595a255 --- /dev/null +++ b/app/lib/phast/Conditionals/ConditionalStatement.inc.php @@ -0,0 +1,17 @@ +Name = $name; + $this->Comparison = $comparison; + $this->Value = $value; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/Configuration/ConfigurationParser.inc.php b/app/lib/phast/Configuration/ConfigurationParser.inc.php new file mode 100644 index 0000000..ad1fa10 --- /dev/null +++ b/app/lib/phast/Configuration/ConfigurationParser.inc.php @@ -0,0 +1,183 @@ +CommonFlavor = new Flavor(); + $this->Flavors = array(); + } + + public function RetrieveGroup($path, $pathSeparator = ".") + { + + } + public function RetrieveGroupExact($path, $pathSeparator = ".") + { + + } + + /** + * Retrieves the property defined by the specified path for the currently + * active flavor. All path parts before the last path part are considered to be groups. + * @param string $name The name of the property to search for. + * @param mixed $defaultValue The value to return if the property cannot be found. + */ + public function RetrieveProperty($name, $defaultValue = null) + { + $flavor = $this->GetCurrentFlavor(); + if ($flavor == null) return new Property($name, $defaultValue); + + $property = $flavor->RetrieveProperty($name, $defaultValue); + if ($property == null) + { + // property was not found in the active flavor; try the common + // flavor + $flavor = $this->CommonFlavor; + $property = $flavor->RetrieveProperty($name, $defaultValue); + } + return $property; + } + /** + * Retrieves the currently-active configuration flavor. + * @return Flavor|NULL The currently-active configuration flavor, or NULL if no configuration flavor is currently active. + */ + public function GetCurrentFlavor() + { + foreach ($this->Flavors as $flavor) + { + if ($flavor->HostName != null && $flavor->HostName == $_SERVER["SERVER_NAME"]) + { + return $flavor; + } + } + return null; + } + + /** + * Loads the group from the given MarkupTagElement into the specified parent. + * @param MarkupTagElement $tag + * @param Group|ConfigurationParser $parent + * @return boolean True if the load was successful; false if an error occurred. + */ + private function LoadGroup($tag, $parent) + { + if (!$tag->HasAttribute("ID")) return false; + + $group = new Group(); + $group->Name = $tag->GetAttribute("ID")->Value; + + $elems = $tag->GetElements(); + foreach ($elems as $elem) + { + $this->LoadTag($elem, $group); + } + + $parent->Groups[] = $group; + return true; + } + private function LoadProperty($tag, $parent) + { + if (!$tag->HasAttribute("ID")) return false; + + $id = $tag->GetAttribute("ID")->Value; + if ($tag->HasAttribute("Value")) + { + $parent->UpdatePropertyValue($id, $tag->GetAttribute("Value")->Value); + return true; + } + else if (count($tag->Elements) == 1) + { + $parent->UpdatePropertyValue($id, $tag->Elements[0]->GetInnerMarkup()); + return true; + } + else + { + $parent->UpdatePropertyValue($id, $tag->GetInnerMarkup()); + return true; + } + return false; + } + /** + * + * @param MarkupTagElement $tag + * @param ConfigurationParser|Group $parent + */ + private function LoadTag($tag, $parent) + { + if ($tag->Name == "Group") + { + $this->LoadGroup($tag, $parent); + } + else if ($tag->Name == "Property") + { + $this->LoadProperty($tag, $parent); + } + } + + public function LoadFile($filename) + { + $parser = new XMLParser(); + + $markup = $parser->LoadFile($filename); + + $tagWebsite = $markup->GetElement("Website"); + $tagCommon = $tagWebsite->GetElement("Common"); + if ($tagCommon != null) + { + $tagConfiguration = $tagCommon->GetElement("Configuration"); + if ($tagConfiguration != null) + { + $tags = $tagConfiguration->GetElements(); + foreach ($tags as $tag) + { + $this->LoadTag($tag, $this->CommonFlavor); + } + } + } + + $tagFlavors = $tagWebsite->GetElement("Flavors"); + if ($tagFlavors != null) + { + foreach ($tagFlavors->Elements as $elem) + { + $flavor = new Flavor(); + $flavor->Name = $elem->GetAttribute("ID")->Value; + + if ($elem->HasAttribute("HostName")) + { + $flavor->HostName = $elem->GetAttribute("HostName")->Value; + } + + $tagConfiguration = $elem->GetElement("Configuration"); + if ($tagConfiguration != null) + { + $tags = $tagConfiguration->GetElements(); + foreach ($tags as $tag) + { + $this->LoadTag($tag, $flavor); + } + } + $this->Flavors[] = $flavor; + } + } + + $this->GetCurrentFlavor(); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/Configuration/Flavor.inc.php b/app/lib/phast/Configuration/Flavor.inc.php new file mode 100644 index 0000000..b4e37ef --- /dev/null +++ b/app/lib/phast/Configuration/Flavor.inc.php @@ -0,0 +1,61 @@ +Groups = array(); + $this->Properties = array(); + $this->HostName = null; + } + + public function RetrieveGroup($name) + { + foreach ($this->Groups as $group) + { + if ($group->Name == $name) return $group; + } + + $group = new Group(); + $group->Name = $name; + $this->Groups[] = $name; + return $group; + } + public function RetrieveProperty($name, $defaultValue = null) + { + foreach ($this->Properties as $property) + { + if ($property->Name == $name) return $property; + } + + $property = new Property(); + $property->Name = $name; + $property->Value = $defaultValue; + $this->Properties[] = $property; + return $property; + } + + public function RetrievePropertyValue($name, $defaultValue = null) + { + $property = $this->RetrieveProperty($name, $defaultValue); + return $property->Value; + } + public function UpdatePropertyValue($name, $value) + { + $property = $this->RetrieveProperty($name, $value); + $property->Value = $value; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/Configuration/Group.inc.php b/app/lib/phast/Configuration/Group.inc.php new file mode 100644 index 0000000..138184f --- /dev/null +++ b/app/lib/phast/Configuration/Group.inc.php @@ -0,0 +1,54 @@ +Groups = array(); + $this->Properties = array(); + } + + public function RetrieveGroup($name) + { + foreach ($this->Groups as $group) + { + if ($group->Name == $name) return $group; + } + + $group = new Group(); + $group->Name = $name; + $this->Groups[] = $name; + return $group; + } + public function RetrieveProperty($name, $defaultValue = null) + { + foreach ($this->Properties as $property) + { + if ($property->Name == $name) return $property; + } + + $property = new Property(); + $property->Name = $name; + $property->Value = $defaultValue; + $this->Properties[] = $property; + return $property; + } + + public function RetrievePropertyValue($name, $defaultValue = null) + { + $property = $this->RetrieveProperty($name, $defaultValue); + return $property->Value; + } + public function UpdatePropertyValue($name, $value) + { + $property = $this->RetrieveProperty($name, $value); + $property->Value = $value; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/Configuration/Property.inc.php b/app/lib/phast/Configuration/Property.inc.php new file mode 100644 index 0000000..1b2698f --- /dev/null +++ b/app/lib/phast/Configuration/Property.inc.php @@ -0,0 +1,23 @@ +ID = $id; + $this->Value = $value; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/Configuration/V1/ConfigurationComment.inc.php b/app/lib/phast/Configuration/V1/ConfigurationComment.inc.php new file mode 100644 index 0000000..7303d3f --- /dev/null +++ b/app/lib/phast/Configuration/V1/ConfigurationComment.inc.php @@ -0,0 +1,13 @@ +Value = $value; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/Configuration/V1/ConfigurationItem.inc.php b/app/lib/phast/Configuration/V1/ConfigurationItem.inc.php new file mode 100644 index 0000000..7d4ac06 --- /dev/null +++ b/app/lib/phast/Configuration/V1/ConfigurationItem.inc.php @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/app/lib/phast/Configuration/V1/ConfigurationProperty.inc.php b/app/lib/phast/Configuration/V1/ConfigurationProperty.inc.php new file mode 100644 index 0000000..8e44067 --- /dev/null +++ b/app/lib/phast/Configuration/V1/ConfigurationProperty.inc.php @@ -0,0 +1,15 @@ +ID = $id; + $this->Value = $value; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/Configuration/V1/ConfigurationSpacer.inc.php b/app/lib/phast/Configuration/V1/ConfigurationSpacer.inc.php new file mode 100644 index 0000000..35bd0c8 --- /dev/null +++ b/app/lib/phast/Configuration/V1/ConfigurationSpacer.inc.php @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/app/lib/phast/Data/Column.inc.php b/app/lib/phast/Data/Column.inc.php new file mode 100644 index 0000000..c61268a --- /dev/null +++ b/app/lib/phast/Data/Column.inc.php @@ -0,0 +1,67 @@ +Name = $name; + $this->DataType = $dataType; + $this->Size = $size; + $this->DefaultValue = $defaultValue; + $this->AllowNull = $allowNull; + $this->PrimaryKey = $primaryKey; + $this->AutoIncrement = $autoIncrement; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/Data/DataObject.inc.php b/app/lib/phast/Data/DataObject.inc.php new file mode 100644 index 0000000..e626059 --- /dev/null +++ b/app/lib/phast/Data/DataObject.inc.php @@ -0,0 +1,107 @@ + 1) + { + $realName = $nses[$nsc - 1]; + $ns = ""; + for ($i = 0; $i < $nsc - 1; $i++) + { + $ns .= $nses[$i]; + if ($i < $nsc - 2) $ns .= "\\"; + } + $classDeclaration = "namespace " . $ns . "; \n"; + } + + // Generate the code of the class for this enumeration + $classDeclaration .= "class " . $realName . " { \n"; + foreach ($args as $arg) + { + $classDeclaration .= "\tpublic \$" . $arg->Name . ";\n"; + } + + $classDeclaration .= "\n\tpublic static function GetByAssoc(\$values)\n\t{\n"; + $classDeclaration .= "\t\t\$item = new " . $name . "();\n\n"; + foreach($args as $arg) + { + $classDeclaration .= "\t\t\$item->" . $arg->Name . " = \$values[\"" . $prefix . $arg->Name . "\"];\n"; + } + $classDeclaration .= "\n\t\treturn \$item;\n"; + $classDeclaration .= "\t}\n"; + /* + public function ToJSON() + { + $json = "{"; + $json .= "\"ID\":" . $this->ID . ","; + $json .= "\"Title\":\"" . \JH\Utilities::JavaScriptDecode($this->Title,"\"") . "\","; + $json .= "\"URL\":\"" . \JH\Utilities::JavaScriptDecode($this->URL,"\"") . "\""; + $json .= "}"; + return $json; + } + */ + $classDeclaration .= "\n\tpublic function ToJSON()\n\t{\n"; + $classDeclaration .= "\t\t\$json = \"{\";\n\n"; + foreach($args as $arg) + { + $classDeclaration .= "\t\t\$json .= \"\\\"" . $arg->Name . "\\\":\";\n"; + $classDeclaration .= "\t\tif (gettype(\$this->" . $arg->Name . ") == \"string\")\n\t\t{"; + $classDeclaration .= "\n\t\t\t\$json .= \"\\\"\" . \$MySQL->real_escape_string(\$this->" . $arg->Name . ") . \"\\\"\";\n\t\t"; + $classDeclaration .= "}\n\t\telse\n\t\t{"; + $classDeclaration .= "\n\t\t\t\$json .= \$this->" . $arg->Name . ";\n\t\t"; + $classDeclaration .= "}\n\n "; + + // .= \" . \\JH\\Utilities::JavaScriptDecode(\$this->" . $arg . ",\"\\\") . "\"\\\",\"">" . $arg . " = \$values[\$this->GetTablePrefix() . \"" . $arg . "\"];\n"; + } + $classDeclaration .= "\t\t\$json .= \"}\";\n\n"; + $classDeclaration .= "\n\t\treturn \$json;\n"; + $classDeclaration .= "\t}\n"; + + + + $classDeclaration .= "\n\tpublic function Get(\$max = null)\n\t{\n"; + $classDeclaration .= "\t\tglobal \$MySQL;\n"; + $classDeclaration .= "\t\t\$query = \"SELECT * FROM \" . System::GetConfigurationValue(\"Database.TablePrefix\") . \"StartPages\";\n"; + $classDeclaration .= "\t\tif (is_numeric(\$max)) \$query .= \" LIMIT \" . \$max;\n"; + $classDeclaration .= "\t\t\$result = \$MySQL->query(\$query);\n"; + $classDeclaration .= "\t\t\$count = \$result->num_rows;\n"; + $classDeclaration .= "\t\t\$retval = array();\n"; + $classDeclaration .= "\t\tfor (\$i = 0; \$i < \$count; \$i++)\n"; + $classDeclaration .= "\t\t{\n"; + $classDeclaration .= "\t\t\t\$values = \$result->fetch_assoc();\n"; + $classDeclaration .= "\t\t\t\$retval[] = " . $name . "::GetByAssoc(\$values);\n"; + $classDeclaration .= "\t\t}\n"; + $classDeclaration .= "\t\treturn \$retval;\n"; + $classDeclaration .= "\t}\n"; + + $classDeclaration .= "}"; + + // Create the class for this enumeration + eval($classDeclaration); + + echo($classDeclaration); + + // Create the class for this enumeration + // eval($classDeclaration); + } + + } +?> \ No newline at end of file diff --git a/app/lib/phast/Data/DataSystem.inc.php b/app/lib/phast/Data/DataSystem.inc.php new file mode 100644 index 0000000..dcaf55c --- /dev/null +++ b/app/lib/phast/Data/DataSystem.inc.php @@ -0,0 +1,95 @@ +Code = $code; + $this->Message = $message; + $this->Query = $query; + } + } + class DataErrorCollection + { + public function __construct() + { + $this->Clear(); + } + + /** + * + * @var DataError[] + */ + public $Items; + public function Add($item) + { + $this->Items[] = $item; + } + public function Clear() + { + $this->Items = array(); + } + } +?> diff --git a/app/lib/phast/Data/DatabaseOperationResult.inc.php b/app/lib/phast/Data/DatabaseOperationResult.inc.php new file mode 100644 index 0000000..f504912 --- /dev/null +++ b/app/lib/phast/Data/DatabaseOperationResult.inc.php @@ -0,0 +1,23 @@ +AffectedRowCount = $affectedRowCount; + } + } + class InsertResult extends DatabaseOperationResult + { + public $LastInsertID; + + public function __construct($affectedRowCount, $lastInsertId) + { + parent::__construct($affectedRowCount); + $this->LastInsertID = $lastInsertId; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/Data/Record.inc.php b/app/lib/phast/Data/Record.inc.php new file mode 100644 index 0000000..e242ba0 --- /dev/null +++ b/app/lib/phast/Data/Record.inc.php @@ -0,0 +1,42 @@ +Columns as $column) + { + if ($column->Name == $name) return $column; + } + return null; + } + + /** + * Creates a Record object with the specified column values, but does not insert the record into a table. + * @param RecordColumn[] $columns The column values to associate with this Record. + */ + public function __construct($columns = null) + { + if ($columns == null) $columns = array(); + $this->Columns = $columns; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/Data/RecordColumn.inc.php b/app/lib/phast/Data/RecordColumn.inc.php new file mode 100644 index 0000000..cdf7c99 --- /dev/null +++ b/app/lib/phast/Data/RecordColumn.inc.php @@ -0,0 +1,33 @@ +Name = $name; + $this->Value = $value; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/Data/SelectResult.inc.php b/app/lib/phast/Data/SelectResult.inc.php new file mode 100644 index 0000000..9d15cf5 --- /dev/null +++ b/app/lib/phast/Data/SelectResult.inc.php @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/app/lib/phast/Data/Table.inc.php b/app/lib/phast/Data/Table.inc.php new file mode 100644 index 0000000..4093858 --- /dev/null +++ b/app/lib/phast/Data/Table.inc.php @@ -0,0 +1,610 @@ +Name = $name; + $this->ColumnPrefix = $columnPrefix; + $this->Columns = $columns; + + if ($records == null) $records = array(); + $this->Records = $records; + + $this->PrimaryKey = null; + $this->UniqueKeys = array(); + $this->ForeignKeys = array(); + } + + /** + * Gets the table with the specified name from the database. + * @param string $name The name of the table to search for. + * @param string $columnPrefix The column prefix for the columns in the table. Columns that begin with this prefix will be populated with the prefix stripped. + * @return Table The table with the specified name. + */ + public static function Get($name, $columnPrefix = null) + { + $pdo = DataSystem::GetPDO(); + $query = "SHOW COLUMNS FROM " . System::GetConfigurationValue("Database.TablePrefix") . $name; + $statement = $pdo->prepare($query); + $result = $statement->execute(); + + $count = $statement->rowCount(); + $columns = array(); + for ($i = 0; $i < $count; $i++) + { + $values = $statement->fetch(PDO::FETCH_ASSOC); + + $columnName = $values["Field"]; + if (substr($columnName, 0, strlen($columnPrefix)) == $columnPrefix) + { + $columnName = substr($columnName, strlen($columnPrefix)); + } + $dataTypeNameAndSize = $values["Type"]; + $dataTypeName = substr($dataTypeNameAndSize, 0, strpos($dataTypeNameAndSize, "(")); + $dataTypeSize = substr($dataTypeNameAndSize, strpos($dataTypeNameAndSize, "("), strlen($dataTypeNameAndSize) - strpos($dataTypeNameAndSize, "(") - 2); + $defaultValue = $values["Default"]; + $allowNull = ($values["Null"] == "YES"); + $primaryKey = ($values["Key"] == "PRI"); + $autoIncrement = ($values["Extra"] == "auto_increment"); + + $columns[] = new Column($columnName, $dataTypeName, $dataTypeSize, $defaultValue, $allowNull, $primaryKey, $autoIncrement); + } + + return new Table($name, $columnPrefix, $columns); + } + + /** + * Creates the table on the database. + * @return boolean True if the table was created successfully; false if an error occurred. + */ + public function Create() + { + $pdo = DataSystem::GetPDO(); + $query = "CREATE TABLE " . System::$Configuration["Database.TablePrefix"] . $this->Name; + + $query .= "("; + $count = count($this->Columns); + for ($i = 0; $i < $count; $i++) + { + $column = $this->Columns[$i]; + $query .= ($this->ColumnPrefix . $column->Name) . " " . $column->DataType; + if ($column->Size != null) + { + $query .= "(" . $column->Size . ")"; + } + if ($column->AllowNull == false) + { + $query .= " NOT NULL"; + } + if ($column->DefaultValue != null) + { + $query .= " DEFAULT "; + if ($column->DefaultValue === ColumnValue::Undefined) + { + $query .= "NULL"; + } + else if ($column->DefaultValue === ColumnValue::CurrentTimestamp) + { + $query .= "CURRENT_TIMESTAMP"; + } + else if (is_string($column->DefaultValue)) + { + $query .= "\"" . $column->DefaultValue . "\""; + } + else + { + $query .= $column->DefaultValue; + } + } + if ($column->PrimaryKey) + { + $query .= " PRIMARY KEY"; + } + if ($column->AutoIncrement) + { + $query .= " AUTO_INCREMENT"; + } + if ($i < $count - 1) $query .= ", "; + } + + $count = count($this->ForeignKeys); + if ($count > 0) + { + $query .= ", "; + for ($i = 0; $i < $count; $i++) + { + $fk = $this->ForeignKeys[$i]; + $query .= "FOREIGN KEY "; + if ($fk->ID != null) + { + $query .= $fk->ID . " "; + } + $query .= "("; + if (is_array($fk->ColumnName)) + { + $columnNameCount = count($fk->ColumnName); + for ($j = 0; $j < $columnNameCount; $j++) + { + $query .= $this->ColumnPrefix . $fk->ColumnName[$j]; + if ($j < $columnNameCount - 1) $query .= ", "; + } + } + else + { + $query .= $this->ColumnPrefix . $fk->ColumnName; + } + $query .= ")"; + $query .= " REFERENCES " . System::GetConfigurationValue("Database.TablePrefix") . $fk->ForeignColumnReference->Table->Name . " ("; + if (is_array($fk->ForeignColumnReference->Column)) + { + $foreignColumnReferenceCount = count($fk->ForeignColumnReference->Column); + for ($j = 0; $j < $foreignColumnReferenceCount; $j++) + { + $query .= $fk->ForeignColumnReference->Table->ColumnPrefix; + if (is_string($fk->ForeignColumnReference->Column[$j])) + { + $query .= $fk->ForeignColumnReference->Column[$j]; + } + else + { + $query .= $fk->ForeignColumnReference->Column[$j]->Name; + } + if ($j < $foreignColumnReferenceCount - 1) $query .= ", "; + } + } + else + { + $query .= $fk->ForeignColumnReference->Table->ColumnPrefix . $fk->ForeignColumnReference->Column->Name; + } + $query .= ")"; + + $query .= " ON DELETE "; + switch ($fk->DeleteAction) + { + case TableForeignKeyReferenceOption::Restrict: + { + $query .= "RESTRICT"; + break; + } + case TableForeignKeyReferenceOption::Cascade: + { + $query .= "CASCADE"; + break; + } + case TableForeignKeyReferenceOption::SetNull: + { + $query .= "SET NULL"; + break; + } + case TableForeignKeyReferenceOption::NoAction: + { + $query .= "NO ACTION"; + break; + } + } + + $query .= " ON UPDATE "; + switch ($fk->DeleteAction) + { + case TableForeignKeyReferenceOption::Restrict: + { + $query .= "RESTRICT"; + break; + } + case TableForeignKeyReferenceOption::Cascade: + { + $query .= "CASCADE"; + break; + } + case TableForeignKeyReferenceOption::SetNull: + { + $query .= "SET NULL"; + break; + } + case TableForeignKeyReferenceOption::NoAction: + { + $query .= "NO ACTION"; + break; + } + } + + if ($i < $count - 1) $query .= ", "; + } + } + + $query .= ")"; + + $statement = $pdo->prepare($query); + $result = $statement->execute(); + + if ($result === false) + { + $ei = $statement->errorInfo(); + trigger_error("DataSystem error: (" . $ei[1] . ") " . $ei[2]); + trigger_error("DataSystem query: " . $query); + DataSystem::$Errors->Clear(); + DataSystem::$Errors->Add(new DataError($ei[1], $ei[2], $query)); + return false; + } + + if ($this->PrimaryKey != null) + { + $key = $this->PrimaryKey; + $query = "ALTER TABLE `" . System::$Configuration["Database.TablePrefix"] . $this->Name . "` ADD PRIMARY KEY ("; + $count = count($key->Columns); + for ($i = 0; $i < $count; $i++) + { + $col = $key->Columns[$i]; + $query .= "`" . $this->ColumnPrefix . $col->Name . "`"; + if ($i < $count - 1) + { + $query .= ", "; + } + } + $query .= ");"; + + $statement = $pdo->prepare($query); + $result = $statement->execute(); + if ($result === false) + { + $ei = $statement->errorInfo(); + trigger_error("DataSystem error: (" . $ei[1] . ") " . $ei[2]); + trigger_error("DataSystem query: " . $query); + DataSystem::$Errors->Clear(); + DataSystem::$Errors->Add(new DataError($ei[1], $ei[2], $query)); + return false; + } + } + foreach ($this->UniqueKeys as $key) + { + $query = "ALTER TABLE `" . System::$Configuration["Database.TablePrefix"] . $this->Name . "` ADD UNIQUE ("; + $count = count($key->Columns); + for ($i = 0; $i < $count; $i++) + { + $col = $key->Columns[$i]; + $query .= "`" . $this->ColumnPrefix . $col->Name . "`"; + if ($i < $count - 1) + { + $query .= ", "; + } + } + $query .= ")"; + + $statement = $pdo->prepare($query); + $result = $statement->execute(); + if ($result === false) + { + $ei = $statement->errorInfo(); + trigger_error("DataSystem error: (" . $ei[1] . ") " . $ei[2]); + trigger_error("DataSystem query: " . $query); + DataSystem::$Errors->Clear(); + DataSystem::$Errors->Add(new DataError($ei[1], $ei[2], $query)); + return false; + } + } + + if ($this->Records != null) + { + $result = $this->Insert($this->Records); + if ($result == null) + { + return false; + } + } + + return true; + } + + public function Select(TableSelectCriteria $criteria) + { + $columnNames = "*"; + if (is_array($criteria->ColumnNames)) + { + $count = count($criteria->ColumnNames); + if ($count > 0) + { + $columnNames = ""; + for ($i = 0; $i < $count; $i++) + { + $columnNames .= $criteria->ColumnNames[$i]; + if ($i < $count - 1) $columnNames .= ", "; + } + } + } + + $query = "SELECT " . $columnNames . " FROM " . System::GetConfigurationValue("Database.TablePrefix") . $this->Name; + if (is_array($criteria->Conditions)) + { + $count = count($criteria->Conditions); + if ($count > 0) + { + $query .= " WHERE "; + for ($i = 0; $i < $count; $i++) + { + $conditionalStatement = $criteria->Conditions[$i]; + + $query .= "("; + $query .= $this->ColumnPrefix . $conditionalStatement->Name . " "; + switch ($conditionalStatement->Comparison) + { + case ConditionalComparison::Equals: + { + $query .= "="; + break; + } + } + $query .= " "; + $query .= ":conditionalStatement" . $i; + $query .= ")"; + + if ($i < $count - 1) + { + $query .= " AND "; + } + } + } + } + + $conditionalStatementCriteria = array(); + $count = count($criteria->Conditions); + for ($i = 0; $i < $count; $i++) + { + $conditionalStatement = $criteria->Conditions[$i]; + $conditionalStatementCriteria[":conditionalStatement" . $i] = $conditionalStatement->Value; + } + + $pdo = DataSystem::GetPDO(); + $statement = $pdo->prepare($query); + $result = $statement->execute($conditionalStatementCriteria); + if ($result === false) + { + $ei = $statement->errorInfo(); + trigger_error("DataSystem error: (" . $ei[1] . ") " . $ei[2]); + trigger_error("DataSystem query: " . $query); + DataSystem::$Errors->Clear(); + DataSystem::$Errors->Add(new DataError($ei[1], $ei[2], $query)); + return false; + } + + $count = $statement->rowCount(); + $selectResult = new SelectResult(); + for ($i = 0; $i < $count; $i++) + { + $record = new Record(); + + $values = $statement->fetch(PDO::FETCH_ASSOC); + foreach ($values as $key => $value) + { + if (StringMethods::StartsWith($key, $this->ColumnPrefix)) $key = substr($key, strlen($this->ColumnPrefix)); + $record->Columns[] = new RecordColumn($key, $value); + } + + $selectResult->Records[] = $record; + } + return $selectResult; + } + + /** + * + * @param Record[] $records The record(s) to insert into the table. + * @param boolean $stopOnError True if processing of the records should stop if an error occurs; false to continue. + * @return NULL|InsertResult + */ + public function Insert($records, $stopOnError = true) + { + DataSystem::$Errors->Clear(); + + $pdo = DataSystem::GetPDO(); + $rowCount = 0; + $lastInsertId = 0; + + if (!is_array($records)) + { + if (get_class($records) == "Phast\\Data\\Record") + { + // single record + $records = array($records); + } + else + { + trigger_error("Table::Insert() called, but $records is not an array or single Record!"); + return false; + } + } + + foreach ($records as $record) + { + $query = "INSERT INTO " . System::GetConfigurationValue("Database.TablePrefix") . $this->Name; + $query .= " ("; + $count = count($record->Columns); + for ($i = 0; $i < $count; $i++) + { + $column = $record->Columns[$i]; + $query .= ($this->ColumnPrefix . $column->Name); + if ($i < $count - 1) $query .= ", "; + } + $query .= " ) VALUES ( "; + for ($i = 0; $i < $count; $i++) + { + $query .= ":" . $record->Columns[$i]->Name; + if ($i < $count - 1) $query .= ", "; + } + $query .= ")"; + + $statement = $pdo->prepare($query); + + $values = array(); + for ($i = 0; $i < $count; $i++) + { + $column = $record->Columns[$i]; + $name = ":" . $column->Name; + if ($column->Value === null || $column->Value === ColumnValue::Undefined) + { + $values[$name] = null; + } + else if ($column->Value === ColumnValue::Now) + { + $values[$name] = "NOW()"; + } + else if ($column->Value === ColumnValue::CurrentTimestamp) + { + $values[$name] = "CURRENT_TIMESTAMP"; + } + else if ($column->Value === ColumnValue::Today) + { + $values[$name] = "TODAY()"; + } + else if (gettype($column->Value) == "string") + { + $values[$name] = $column->Value; + } + else if (gettype($column->Value) == "object") + { + if (get_class($column->Value) == "DateTime") + { + $values[$name] = date_format($column->Value, "Y-m-d H:i:s"); + } + else + { + $values[$name] = $column->Value; + } + } + else + { + $values[$name] = $column->Value; + } + } + + $result = $statement->execute($values); + + if ($result === false) + { + $ei = $statement->errorInfo(); + trigger_error("DataSystem error: (" . $ei[1] . ") " . $ei[2]); + trigger_error("DataSystem query: " . $query); + DataSystem::$Errors->Clear(); + DataSystem::$Errors->Add(new DataError($ei[1], $ei[2], $query)); + if ($stopOnError) return null; + } + else + { + $rowCount += $statement->rowCount(); + $lastInsertId = $pdo->lastInsertId(); + } + } + return new InsertResult($rowCount, $lastInsertId); + } + + /** + * Deletes this table from the database. + * @return boolean True if the table was deleted successfully; false otherwise. + */ + public function Delete() + { + $pdo = DataSystem::GetPDO(); + $query = "DROP TABLE " . System::$Configuration["Database.TablePrefix"] . $this->Name; + $statement = $pdo->prepare($query); + $result = $statement->execute(); + if ($result === false) + { + $ei = $statement->errorInfo(); + trigger_error("DataSystem error: (" . $ei[1] . ") " . $ei[2]); + trigger_error("DataSystem query: " . $query); + DataSystem::$Errors->Clear(); + DataSystem::$Errors->Add(new DataError($ei[1], $ei[2], $query)); + return false; + } + return true; + } + + /** + * Determines if this table exists on the database. + * @return boolean True if this table exists; false otherwise. + */ + public function Exists() + { + $pdo = DataSystem::GetPDO(); + $query = "SHOW TABLES LIKE '" . System::$Configuration["Database.TablePrefix"] . $this->Name . "'"; + $statement = $pdo->prepare($query); + $result = $statement->execute(); + if ($result !== false) + { + return ($statement->rowCount() > 0); + } + return false; + } + + /** + * Retrieves the Column with the given name on this Table. + * @param string $name The name of the column to search for. + * @return Column|NULL The column with the given name, or NULL if no columns with the given name were found. + */ + public function GetColumnByName($name) + { + foreach ($this->Columns as $column) + { + if ($column->Name == $name) return $column; + } + return null; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/Data/TableForeignKey.inc.php b/app/lib/phast/Data/TableForeignKey.inc.php new file mode 100644 index 0000000..a36fdf5 --- /dev/null +++ b/app/lib/phast/Data/TableForeignKey.inc.php @@ -0,0 +1,94 @@ +ID = $id; + $this->ColumnName = $columnName; + $this->ForeignColumnReference = $foreignColumnReference; + + if ($deleteAction == null) $deleteAction = TableForeignKeyReferenceOption::Restrict; + $this->DeleteAction = $deleteAction; + if ($updateAction == null) $updateAction = TableForeignKeyReferenceOption::Restrict; + $this->UpdateAction = $updateAction; + } + } + class TableForeignKeyColumn + { + /** + * The table that contains the foreign column. + * @var Table + */ + public $Table; + /** + * A reference to the foreign column(s), or the name of the foreign column(s). + * @var string|Column|string[]|Column[] + */ + public $Column; + + /** + * Creates a TableForeignKeyColumn with the given parameters. + * @param Table $table The table that contains the foreign column. + * @param string|Column $column A reference to the foreign column, or the name of the foreign column. + */ + public function __construct($table, $column) + { + $this->Table = $table; + if (is_string($column)) + { + $column = $table->GetColumnByName($column); + } + $this->Column = $column; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/Data/TableKey.inc.php b/app/lib/phast/Data/TableKey.inc.php new file mode 100644 index 0000000..8907e04 --- /dev/null +++ b/app/lib/phast/Data/TableKey.inc.php @@ -0,0 +1,30 @@ +Columns = $columns; + } + else + { + $this->Columns = array(); + } + } + } + class TableKeyColumn + { + public $Name; + + public function __construct($name) + { + $this->Name = $name; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/Data/TableSelectCriteria.inc.php b/app/lib/phast/Data/TableSelectCriteria.inc.php new file mode 100644 index 0000000..2562211 --- /dev/null +++ b/app/lib/phast/Data/TableSelectCriteria.inc.php @@ -0,0 +1,20 @@ + \ No newline at end of file diff --git a/app/lib/phast/Data/Traits/DataObject.inc.php b/app/lib/phast/Data/Traits/DataObject.inc.php new file mode 100644 index 0000000..f99fc46 --- /dev/null +++ b/app/lib/phast/Data/Traits/DataObject.inc.php @@ -0,0 +1,68 @@ +BindDataColumn($realColumnName, $values[$columnName])) + { + $item->{$realColumnName} = $values[$columnName]; + } + } + return $item; + } + + /** + * Returns all the instances of this DataObject. + */ + public static function Get() + { + $pdo = DataSystem::GetPDO(); + $query = "SELECT * FROM " . (System::GetConfigurationValue("Database.TablePrefix") . self::$DataObjectTableName); + $statement = $pdo->prepare($query); + $statement->execute(); + $count = $statement->rowCount(); + $retval = array(); + + for ($i = 0; $i < $count; $i++) + { + $values = $statement->fetch(PDO::FETCH_ASSOC); + $item = self::GetByAssoc($values); + $retval[] = $item; + } + return $retval; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/Enumeration.inc.php b/app/lib/phast/Enumeration.inc.php new file mode 100644 index 0000000..74d1c8f --- /dev/null +++ b/app/lib/phast/Enumeration.inc.php @@ -0,0 +1,56 @@ +getConstants(); + } + return self::$constCacheArray[$calledClass]; + } + + /** + * Determines if this enumeration contains the specified name. + * @param string $name The name of the enum value to search for. + * @param boolean $caseSensitive True if the search should be case-sensitive; false otherwise. + */ + public static function ContainsName($name, $caseSensitive = false) + { + $constants = self::GetValues(); + + if ($caseSensitive) + { + return array_key_exists($name, $constants); + } + + $keys = array_map('strtolower', array_keys($constants)); + return in_array(strtolower($name), $keys); + } + /** + * Determines if this enumeration contains the specified value. + * @param unknown $value The enum value to search for. + */ + public static function ContainsValue($value) + { + $values = array_values(self::GetValues()); + return in_array($value, $values, $strict = true); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/EventArgs.inc.php b/app/lib/phast/EventArgs.inc.php new file mode 100644 index 0000000..d01c1c2 --- /dev/null +++ b/app/lib/phast/EventArgs.inc.php @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/app/lib/phast/HTMLControl.inc.php b/app/lib/phast/HTMLControl.inc.php new file mode 100644 index 0000000..2256c67 --- /dev/null +++ b/app/lib/phast/HTMLControl.inc.php @@ -0,0 +1,21 @@ +TagName = $tagName; + } + + protected function RenderContent() + { + echo($this->InnerHTML); + } + } + + require_once("HTMLControls/Literal.inc.php"); +?> \ No newline at end of file diff --git a/app/lib/phast/HTMLControls/Anchor.inc.php b/app/lib/phast/HTMLControls/Anchor.inc.php new file mode 100644 index 0000000..e68588a --- /dev/null +++ b/app/lib/phast/HTMLControls/Anchor.inc.php @@ -0,0 +1,61 @@ + HTML tag. + * @author Michael Becker + */ + class Anchor extends HTMLControl + { + public function __construct() + { + parent::__construct(); + + $this->TagName = "a"; + } + + /** + * The URL to navigate to when this anchor is activated. + * @var string + */ + public $TargetURL; + /** + * The script to execute when this anchor is activated. + * @var string + */ + public $TargetScript; + /** + * The frame in which to open the associated TargetURL. + * @var string + */ + public $TargetFrame; + + protected function RenderBeginTag() + { + if ($this->TargetURL != null) + { + $this->Attributes[] = new WebControlAttribute("href", System::ExpandRelativePath($this->TargetURL)); + if ($this->TargetFrame != null) + { + $this->Attributes[] = new WebControlAttribute("target", $this->TargetFrame); + } + } + else + { + $this->Attributes[] = new WebControlAttribute("href", "#"); + } + if ($this->TargetScript != null) + { + $this->Attributes[] = new WebControlAttribute("onclick", $this->TargetScript); + } + parent::RenderBeginTag(); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/HTMLControls/Footer.inc.php b/app/lib/phast/HTMLControls/Footer.inc.php new file mode 100644 index 0000000..0afa9d1 --- /dev/null +++ b/app/lib/phast/HTMLControls/Footer.inc.php @@ -0,0 +1,15 @@ +TagName = "footer"; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/HTMLControls/Form.inc.php b/app/lib/phast/HTMLControls/Form.inc.php new file mode 100644 index 0000000..53e42e5 --- /dev/null +++ b/app/lib/phast/HTMLControls/Form.inc.php @@ -0,0 +1,108 @@ +TagName = "form"; + $this->Method = $method; + } + + /** + * The URL to direct the user to upon form submission. + * @var string + */ + public $Action; + /** + * The method of form submission. + * @var FormMethod + */ + public $Method; + /** + * The type of encoding used to submit this form. Known values are "application/x-www-form-urlencoded" (the default) and "multipart/form-data" (used in combination with the file input element. + * @var string + */ + public $EncodingType; + + protected function RenderBeginTag() + { + if ($this->Action != null) + { + $this->Attributes[] = new WebControlAttribute("action", System::ExpandRelativePath($this->Action)); + } + if ($this->EncodingType != null) + { + $this->Attributes[] = new WebControlAttribute("enctype", $this->EncodingType); + } + if (is_string($this->Method)) + { + switch (strtolower($this->Method)) + { + case "get": + { + $this->Method = FormMethod::Get; + break; + } + case "post": + { + $this->Method = FormMethod::Post; + break; + } + } + } + if ($this->Method != FormMethod::None) + { + $methodstr = ""; + switch ($this->Method) + { + case FormMethod::Get: + { + $methodstr = "GET"; + break; + } + case FormMethod::Post: + { + $methodstr = "POST"; + break; + } + } + $this->Attributes[] = new WebControlAttribute("method", $methodstr); + } + parent::RenderBeginTag(); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/HTMLControls/HTMLControlSelect.inc.php b/app/lib/phast/HTMLControls/HTMLControlSelect.inc.php new file mode 100644 index 0000000..95407dc --- /dev/null +++ b/app/lib/phast/HTMLControls/HTMLControlSelect.inc.php @@ -0,0 +1,52 @@ +Title = $title; + $this->Value = $value; + $this->Selected = $selected; + } + } + class HTMLControlSelect extends HTMLControl + { + public $Items; + + public function __construct() + { + parent::__construct(); + $this->TagName = "select"; + $this->Items = array(); + } + + protected function RenderContent() + { + foreach ($this->Items as $item) + { + $tag = new HTMLControl(); + $tag->TagName = "option"; + if ($item->Value != null) + { + $tag->Attributes[] = new WebControlAttribute("value", $item->Value); + } + $tag->InnerHTML = $item->Title; + if ($item->Selected) + { + $tag->Attributes[] = new WebControlAttribute("selected", "selected"); + } + $tag->Render(); + } + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/HTMLControls/HTMLControlTable.inc.php b/app/lib/phast/HTMLControls/HTMLControlTable.inc.php new file mode 100644 index 0000000..03d6323 --- /dev/null +++ b/app/lib/phast/HTMLControls/HTMLControlTable.inc.php @@ -0,0 +1,61 @@ +TagName = "table"; + } + + public function BeginHeader() + { + echo(""); + } + public function EndHeader() + { + echo(""); + } + + public function BeginBody() + { + echo(""); + } + public function EndBody() + { + echo(""); + } + + public function BeginRow($namedParameters = null) + { + WebControl::BeginTag("tr", $namedParameters); + } + public function EndRow() + { + WebControl::EndTag("tr"); + } + + public function BeginCell($namedParameters = null) + { + WebControl::BeginTag("td", $namedParameters); + } + public function EndCell() + { + WebControl::EndTag("td"); + } + + public function BeginHeaderCell($namedParameters = null) + { + WebControl::BeginTag("th", $namedParameters); + } + public function EndHeaderCell() + { + WebControl::EndTag("th"); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/HTMLControls/HTMLControlTextArea.inc.php b/app/lib/phast/HTMLControls/HTMLControlTextArea.inc.php new file mode 100644 index 0000000..814b6bb --- /dev/null +++ b/app/lib/phast/HTMLControls/HTMLControlTextArea.inc.php @@ -0,0 +1,42 @@ +TagName = "textarea"; + $this->HasContent = true; + } + + public $Name; + public $Value; + public $PlaceholderText; + + public $Rows; + public $Columns; + + protected function RenderBeginTag() + { + if (isset($this->ID)) $this->Attributes[] = new WebControlAttribute("id", $this->ID); + if (isset($this->Name)) $this->Attributes[] = new WebControlAttribute("name", $this->Name); + if (isset($this->PlaceholderText)) $this->Attributes[] = new WebControlAttribute("placeholder", $this->PlaceholderText); + if (isset($this->Rows)) $this->Attributes[] = new WebControlAttribute("rows", $this->Rows); + if (isset($this->Columns)) $this->Attributes[] = new WebControlAttribute("cols", $this->Columns); + parent::RenderBeginTag(); + } + protected function RenderContent() + { + if (isset($this->Value)) + { + echo($this->Value); + } + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/HTMLControls/Header.inc.php b/app/lib/phast/HTMLControls/Header.inc.php new file mode 100644 index 0000000..519bfd7 --- /dev/null +++ b/app/lib/phast/HTMLControls/Header.inc.php @@ -0,0 +1,15 @@ +TagName = "header"; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/HTMLControls/Heading.inc.php b/app/lib/phast/HTMLControls/Heading.inc.php new file mode 100644 index 0000000..6a92ccd --- /dev/null +++ b/app/lib/phast/HTMLControls/Heading.inc.php @@ -0,0 +1,22 @@ +Level = 1; + } + + public function RenderBeginTag() + { + $this->TagName = "h" . $this->Level; + parent::RenderBeginTag(); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/HTMLControls/Image.inc.php b/app/lib/phast/HTMLControls/Image.inc.php new file mode 100644 index 0000000..61b62bb --- /dev/null +++ b/app/lib/phast/HTMLControls/Image.inc.php @@ -0,0 +1,33 @@ +TagName = "img"; + $this->HasContent = false; + } + + protected function OnInitialize() + { + if ($this->AlternateText != "") + { + $this->Attributes[] = new WebControlAttribute("alt", $this->AlternateText); + } + if ($this->ImageUrl != "") + { + $this->Attributes[] = new WebControlAttribute("src", System::ExpandRelativePath($this->ImageUrl)); + } + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/HTMLControls/Input.inc.php b/app/lib/phast/HTMLControls/Input.inc.php new file mode 100644 index 0000000..b81781d --- /dev/null +++ b/app/lib/phast/HTMLControls/Input.inc.php @@ -0,0 +1,125 @@ +TagName = "input"; + $this->HasContent = false; + } + + public $Name; + public $Type; + public $Value; + public $PlaceholderText; + + protected function RenderBeginTag() + { + switch ($this->Type) + { + case "Text": + case InputType::Text: + { + $this->Attributes[] = new WebControlAttribute("type", "text"); + break; + } + case "Password": + case InputType::Password: + { + $this->Attributes[] = new WebControlAttribute("type", "password"); + break; + } + case "CheckBox": + case InputType::CheckBox: + { + $this->Attributes[] = new WebControlAttribute("type", "checkbox"); + break; + } + case "RadioButton": + case InputType::RadioButton: + { + $this->Attributes[] = new WebControlAttribute("type", "radio"); + break; + } + case "Number": + case InputType::Number: + { + $this->Attributes[] = new WebControlAttribute("type", "number"); + break; + } + case "Hidden": + case InputType::Hidden: + { + $this->Attributes[] = new WebControlAttribute("type", "hidden"); + break; + } + case "Submit": + case InputType::Submit: + { + $this->Attributes[] = new WebControlAttribute("type", "submit"); + break; + } + } + if (isset($this->Name)) $this->Attributes[] = new WebControlAttribute("name", $this->Name); + if (isset($this->Value)) $this->Attributes[] = new WebControlAttribute("value", $this->Value); + if (isset($this->PlaceholderText)) $this->Attributes[] = new WebControlAttribute("placeholder", $this->PlaceholderText); + parent::RenderBeginTag(); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/HTMLControls/Layer.inc.php b/app/lib/phast/HTMLControls/Layer.inc.php new file mode 100644 index 0000000..a2a615d --- /dev/null +++ b/app/lib/phast/HTMLControls/Layer.inc.php @@ -0,0 +1,14 @@ +TagName = "div"; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/HTMLControls/Literal.inc.php b/app/lib/phast/HTMLControls/Literal.inc.php new file mode 100644 index 0000000..50e46dc --- /dev/null +++ b/app/lib/phast/HTMLControls/Literal.inc.php @@ -0,0 +1,27 @@ +Value = $value; + } + + protected function RenderContent() + { + echo(System::ExpandRelativePath($this->Value)); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/HTMLControls/Navigation.inc.php b/app/lib/phast/HTMLControls/Navigation.inc.php new file mode 100644 index 0000000..3324bff --- /dev/null +++ b/app/lib/phast/HTMLControls/Navigation.inc.php @@ -0,0 +1,15 @@ +TagName = "nav"; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/HTMLControls/Paragraph.inc.php b/app/lib/phast/HTMLControls/Paragraph.inc.php new file mode 100644 index 0000000..e9c4412 --- /dev/null +++ b/app/lib/phast/HTMLControls/Paragraph.inc.php @@ -0,0 +1,15 @@ +TagName = "p"; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/Orientation.inc.php b/app/lib/phast/Orientation.inc.php new file mode 100644 index 0000000..0cba6eb --- /dev/null +++ b/app/lib/phast/Orientation.inc.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/app/lib/phast/Pages/ErrorPage.inc.php b/app/lib/phast/Pages/ErrorPage.inc.php new file mode 100644 index 0000000..e036554 --- /dev/null +++ b/app/lib/phast/Pages/ErrorPage.inc.php @@ -0,0 +1,23 @@ + +
 
+
Application error
+ + \ No newline at end of file diff --git a/app/lib/phast/Pages/MessagePage.inc.php b/app/lib/phast/Pages/MessagePage.inc.php new file mode 100644 index 0000000..b986e1f --- /dev/null +++ b/app/lib/phast/Pages/MessagePage.inc.php @@ -0,0 +1,21 @@ +
Message); ?>
\ No newline at end of file diff --git a/app/lib/phast/Parser/ControlLoader.inc.php b/app/lib/phast/Parser/ControlLoader.inc.php new file mode 100644 index 0000000..bc2da10 --- /dev/null +++ b/app/lib/phast/Parser/ControlLoader.inc.php @@ -0,0 +1,301 @@ +Elements)) + { + foreach ($elem->Elements as $elem1) + { + if (get_class($elem1) != "UniversalEditor\\ObjectModels\\Markup\\MarkupTagElement") continue; + + if (!is_array($elem1->Elements)) + { + trigger_error("\$elem1->Elements not array for tag '" . $elem1->Name . "'"); + continue; + } + + foreach ($elem1->Elements as $elem2) + { + if (get_class($elem2) != "UniversalEditor\\ObjectModels\\Markup\\MarkupTagElement") continue; + + $i = stripos($elem2->Name, ":"); + if ($i === false) + { + $prefix = ""; + $name = $elem2->Name; + } + else + { + $prefix = substr($elem2->Name, 0, $i); + $name = substr($elem2->Name, $i + 1); + } + + if (isset(ControlLoader::$Namespaces[$prefix]) && ControlLoader::$Namespaces[$prefix] != "") + { + $realname = ControlLoader::$Namespaces[$prefix] . "\\" . $name; + } + else + { + $realname = $name; + } + + if ($prefix == "") + { + // assume regular HTML control + $obj1 = new HTMLControl($name); + foreach ($elem2->Attributes as $att) + { + $obj1->Attributes[] = new WebControlAttribute($att->Name, $att->Value); + } + if ($elem2->GetInnerMarkup() == "") + { + // we have to make some compromises; AFAIK the SCRIPT tag is the only + // tag that doesn't know how to properly handle close /> tag + $obj1->HasContent = false; + } + } + else + { + if (class_exists($realname)) + { + $obj1 = new $realname(); + } + else + { + ControlLoader::$Messages[] = new WebPageMessage("Unknown class " . $realname . " (" . $prefix . ":" . $name . ")", WebPageMessageSeverity::Error); + System::WriteErrorLog("Unknown class " . $realname . " (" . $prefix . ":" . $name . ")"); + continue; + } + } + + ControlLoader::LoadAttributes($elem2, $obj1); + + $parseChildElements = false; + if (isset($obj1->ParseChildElements)) + { + $parseChildElements = $obj1->ParseChildElements; + } + + if ($parseChildElements) + { + ControlLoader::ParseChildren($elem2, $obj1); + } + else + { + if (is_array($elem2->Elements)) + { + foreach ($elem2->Elements as $elem3) + { + ControlLoader::LoadControl($elem3, $obj1); + } + } + } + + $obj->{$elem1->Name}[] = $obj1; + } + } + } + } + public static function LoadAttributes($elem, &$obj) + { + if (is_array($elem->Attributes)) + { + foreach ($elem->Attributes as $attr) + { + $obj->{$attr->Name} = $attr->Value; + } + } + } + + public static function GetPHPXAttributes($elem) + { + $attrs = array(); + if (is_array($elem->Attributes)) + { + foreach ($elem->Attributes as $attr) + { + $attrs[$attr->Name] = $attr->Value; + } + } + if (count($elem->Elements) == 1 && get_class($elem->Elements[0]) == "UniversalEditor\\ObjectModels\\Markup\\MarkupLiteralElement") + { + $attrs["Content"] = $elem->Elements[0]->Value; + $elem->Elements = array(); + } + return $attrs; + } + public static function LoadPHPXControlAttributes($attrs, &$obj) + { + foreach ($obj->Attributes as $att) + { + foreach ($attrs as $name => $value) + { + if (is_string($att->Value) && stripos($att->Value, "\$(Control:" . $name . ")") !== false) + { + $att->Value = str_replace("\$(Control:" . $name . ")", $value, $att->Value); + } + } + } + + foreach ($obj as $key => $val) + { + foreach ($attrs as $name => $value) + { + if (is_string($val) && stripos($val, "\$(Control:" . $name . ")") !== false) + { + $obj->{$key} = str_replace("\$(Control:" . $name . ")", $value, $val); + } + } + } + + foreach ($obj->Controls as $ctl) + { + ControlLoader::LoadPHPXControlAttributes($attrs, $ctl); + } + } + /** + * Creates a WebControl from markup in a MarkupElement. + * @param MarkupElement $elem The MarkupElement to parse. + * @param WebControl $parent The WebControl that owns this control. + */ + public static function LoadControl($elem, $parent) + { + if (get_class($elem) == "UniversalEditor\\ObjectModels\\Markup\\MarkupTagElement") + { + $i = stripos($elem->Name, ":"); + if ($i !== false) + { + $prefix = substr($elem->Name, 0, $i); + $name = substr($elem->Name, $i + 1); + + if (isset(ControlLoader::$Namespaces[$prefix]) && ControlLoader::$Namespaces[$prefix] != "") + { + $realname = ControlLoader::$Namespaces[$prefix] . "\\" . $name; + } + else + { + $realname = $name; + } + + if (class_exists($realname)) + { + $obj = new $realname(); + } + else + { + $basectl = System::$Parser->GetControlByVirtualTagPath($realname); + if ($basectl !== null) + { + $ctl = clone $basectl; + $obj = $ctl; + } + else + { + ControlLoader::$Messages[] = new WebPageMessage("Unknown class " . $realname . " (" . $prefix . ":" . $name . ")", WebPageMessageSeverity::Error); + return; + } + } + + $loadedFromVTP = false; + if (isset($obj->VirtualTagPath)) + { + if ($obj->VirtualTagPath != null) + { + $attrs = ControlLoader::GetPHPXAttributes($elem); + $loadedFromVTP = true; + } + } + + if (!$loadedFromVTP) + { + ControlLoader::LoadAttributes($elem, $obj); + } + + if (is_subclass_of($obj, "Phast\\WebControl") && $obj->ParseChildElements) + { + ControlLoader::ParseChildren($elem, $obj); + } + else + { + if (is_array($elem->Elements)) + { + foreach ($elem->Elements as $elem1) + { + ControlLoader::LoadControl($elem1, $obj); + } + } + } + + if (isset($obj->VirtualTagPath)) + { + if ($obj->VirtualTagPath != null) + { + ControlLoader::LoadPHPXControlAttributes($attrs, $obj); + } + } + + $obj->ParentObject = $parent; + $parent->Controls[] = $obj; + } + else + { + $ctl = new HTMLControl(); + $ctl->TagName = $elem->Name; + if (is_array($elem->Attributes)) + { + foreach ($elem->Attributes as $attr) + { + $ctl->Attributes[] = new WebControlAttribute($attr->Name, $attr->Value); + } + } + if (is_array($elem->Elements) && count($elem->Elements) > 0) + { + foreach ($elem->Elements as $elem1) + { + ControlLoader::LoadControl($elem1, $ctl); + } + } + else + { + if ($ctl->TagName != "script") + { + $ctl->HasContent = false; + } + } + $ctl->ParentObject = $parent; + $parent->Controls[] = $ctl; + } + } + else if (get_class($elem) == "UniversalEditor\\ObjectModels\\Markup\\MarkupLiteralElement") + { + $parent->Controls[] = new Literal($elem->Value); + } + } + } + ControlLoader::$Messages = array(); + +?> \ No newline at end of file diff --git a/app/lib/phast/Parser/PhastParser.inc.php b/app/lib/phast/Parser/PhastParser.inc.php new file mode 100644 index 0000000..a9b3f85 --- /dev/null +++ b/app/lib/phast/Parser/PhastParser.inc.php @@ -0,0 +1,193 @@ +Controls as $ctl) + { + if ($ctl->VirtualTagPath == $path) return $ctl; + } + return null; + } + public function GetMasterPageByFileName($filename) + { + foreach ($this->MasterPages as $page) + { + if ($page->FileName == $filename) return $page; + } + return null; + } + public function GetPageByFileName($filename) + { + foreach ($this->Pages as $page) + { + if ($page->FileName == $filename) return $page; + } + return null; + } + + public function __construct() + { + $this->Clear(); + } + + public function Clear() + { + $this->Controls = array(); + $this->MasterPages = array(); + $this->Pages = array(); + } + + /** + * Loads an XML file describing a portion or portions of the Phast environment. + * @param string $filename The name of the XML file to load into the environment. + */ + public function LoadFile($filename) + { + $markup = MarkupObjectModel::FromFile($filename); + + $tagWebsite = $markup->GetElement("Website"); + if ($tagWebsite == null) return; + + $tagControls = $tagWebsite->GetElement("Controls"); + if ($tagControls != null) + { + foreach ($tagControls->Elements as $element) + { + if (get_class($element) != "UniversalEditor\\ObjectModels\\Markup\\MarkupTagElement") continue; + if ($element->Name == "Control") + { + $ctl = WebControl::FromMarkup($element, $this); + $ctl->PhysicalFileName = $filename; + $this->Controls[] = $ctl; + } + } + } + + $tagMasterPages = $tagWebsite->GetElement("MasterPages"); + if ($tagMasterPages != null) + { + foreach ($tagMasterPages->Elements as $element) + { + if (get_class($element) != "UniversalEditor\\ObjectModels\\Markup\\MarkupTagElement") continue; + if ($element->Name == "MasterPage") + { + $page = WebPage::FromMarkup($element, $this); + $page->PhysicalFileName = $filename; + $this->MasterPages[] = $page; + } + } + } + + $tagPages = $tagWebsite->GetElement("Pages"); + if ($tagPages != null) + { + foreach ($tagPages->Elements as $element) + { + if (get_class($element) != "UniversalEditor\\ObjectModels\\Markup\\MarkupTagElement") continue; + if ($element->Name == "Page") + { + $page = WebPage::FromMarkup($element, $this); + $page->PhysicalFileName = $filename; + $this->Pages[] = $page; + } + } + } + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/Parser/XMLParser.inc.php b/app/lib/phast/Parser/XMLParser.inc.php new file mode 100644 index 0000000..c7043b0 --- /dev/null +++ b/app/lib/phast/Parser/XMLParser.inc.php @@ -0,0 +1,359 @@ +Prefix != "") + { + return $this->Prefix . ":" . $this->Name; + } + else + { + return $this->Name; + } + } + public function SetFullName($name) + { + $names = explode(":", $name); + if (count($names) > 1) + { + $this->Prefix = $names[0]; + $this->Name = $names[1]; + } + else if (count($names) == 1) + { + $this->Prefix = ""; + $this->Name = $names[0]; + } + } + + public function __construct($name, $value, $prefix = null) + { + if ($prefix == null) + { + $this->SetFullName($name); + } + else + { + $this->Prefix = $prefix; + $this->Name = $name; + } + $this->Value = $value; + } + } + abstract class MarkupElement + { + public abstract function GetOuterMarkup(); + public abstract function GetInnerMarkup(); + + public static function FromArray($array) + { + if (!isset($array["Type"])) return null; + + switch ($array["Type"]) + { + case "MarkupTagElement": + { + $element = new MarkupTagElement(); + $element->Name = $array["Name"]; + + if (isset($array["Attributes"])) + { + $attributes = $array["Attributes"]; + foreach ($attributes as $name => $value) + { + $element->Attributes[] = new MarkupAttribute($name, $value); + } + } + if (isset($array["Elements"])) + { + $elements = $array["Elements"]; + foreach ($elements as $elem) + { + $element->Elements[] = MarkupElement::FromArray($elem); + } + } + if (isset($array["Value"])) + { + $element->Value = $array["Value"]; + } + + return $element; + } + case "MarkupLiteralElement": + { + $element = new MarkupLiteralElement(); + $element->Value = $array["Value"]; + return $element; + } + } + return null; + } + } + class MarkupLiteralElement extends MarkupElement + { + public $Value; + + public function GetOuterMarkup() + { + return $this->GetInnerMarkup(); + } + public function GetInnerMarkup() + { + return $this->Value; + } + } + class MarkupTagElement extends MarkupElement + { + /** + * The name of this MarkupTagElement. + * @var string + */ + public $Name; + + /** + * The MarkupAttributes associated with this MarkupTagElement. + * @var MarkupAttribute[] + */ + public $Attributes; + /** + * The child MarkupElements of this MarkupTagElement. + * @var MarkupTagElement[] + */ + public $Elements; + + public function __construct() + { + $this->Attributes = array(); + $this->Elements = array(); + } + + public function GetOuterMarkup() + { + $str = "<" . $this->Name; + if (count($this->Attributes) != 0) + { + foreach ($this->Attributes as $attr) + { + $str .= " " . $attr->Name . "=\"" . $attr->Value . "\""; + } + } + if (count($this->Elements) == 0) + { + $str .= " />"; + } + else + { + $str .= ">" . $this->GetInnerMarkup() . "Name . ">"; + } + return $str; + } + public function GetInnerMarkup() + { + $str = ""; + foreach ($this->Elements as $elem) + { + $str .= $elem->GetOuterMarkup(); + } + return $str; + } + + public function GetAttribute($name, $index = 0) + { + $i = 0; + $last = null; + foreach ($this->Attributes as $attribute) + { + if ($attribute->Name == $name) + { + $last = $attribute; + $i++; + if ($i == $index) return $attribute; + } + } + return $last; + } + public function GetAttributes() + { + return $this->Attributes; + } + + public function HasAttribute($name) + { + return ($this->GetAttribute($name) != null); + } + + /** + * Gets a MarkupElement with the specified name. + * @param string $name The name of the element to retrieve. + * @param number $index The index of the element to retrieve if there is more than one element with the specified name. + * @return MarkupTagElement|MarkupLiteralElement|MarkupElement|null + */ + public function GetElement($name, $index = 0) + { + $i = 0; + $last = null; + foreach ($this->Elements as $element) + { + if (is_numeric($name)) + { + if ($i == $name) return $element; + continue; + } + + if (get_class($element) == "UniversalEditor\\ObjectModels\\Markup\\MarkupLiteralElement") continue; + + if ($element->Name == $name) + { + $last = $element; + $i++; + if ($i == $index) return $element; + } + } + return $last; + } + public function GetElements() + { + return $this->Elements; + } + } + + class MarkupObjectModel + { + public $Elements; + + /** + * Gets a MarkupElement with the specified name. + * @param string $name The name of the element to retrieve. + * @param number $index The index of the element to retrieve if there is more than one element with the specified name. + * @return MarkupTagElement|MarkupLiteralElement|MarkupElement + */ + public function GetElement($name, $index = 0) + { + $i = 0; + $last = null; + foreach ($this->Elements as $element) + { + if ($element->Name == $name) + { + $last = $element; + $i++; + if ($i == $index) return $element; + } + } + return $last; + } + public function GetElements() + { + return $this->Elements; + } + + public static function FromFile($filename) + { + $parser = new XMLParser(); + $markup = $parser->LoadFile($filename); + return $markup; + } + + public function __construct() + { + $this->Elements = array(); + } + public static function FromArray($array) + { + $markup = new MarkupObjectModel(); + $markup->LoadArray($array); + return $markup; + } + public function LoadArray($array) + { + $count = count($array); + for ($i = 0; $i < $count; $i++) + { + $this->Elements[] = MarkupElement::FromArray($array[$i]); + } + } + } + + class XMLParser + { + public $RemoveWhitespace; + + private $mvarOutput; + var $resParser; + var $strXmlData; + + public function __construct() + { + $this->RemoveWhitespace = true; + } + + /** + * Loads an XML file and returns the MarkupObjectModel. + * @param string $filename + * @return MarkupObjectModel + */ + public function LoadFile($filename) + { + $file = fopen($filename, "r"); + $input = fread($file, filesize($filename)); + return $this->Load($input); + } + + public function Load($input) + { + $this->mvarOutput = array(); + + $input = html_entity_decode($input); + + $this->resParser = \xml_parser_create (); + \xml_parser_set_option($this->resParser, XML_OPTION_CASE_FOLDING, 0); + + \xml_set_object($this->resParser, $this); + \xml_set_element_handler($this->resParser, "tagOpen", "tagClosed"); + + \xml_set_character_data_handler($this->resParser, "tagData"); + + $this->strXmlData = \xml_parse($this->resParser, $input); + if(!$this->strXmlData) + { + $message = \xml_error_string(xml_get_error_code($this->resParser)); + $lineNumber = \xml_get_current_line_number($this->resParser); + die(sprintf("XML error: %s at line %d", $message, $lineNumber)); + } + + \xml_parser_free($this->resParser); + + return MarkupObjectModel::FromArray($this->mvarOutput); + } + + private function tagOpen($parser, $name, $attrs) + { + $tag = array("Type" => "MarkupTagElement", "Name" => $name, "Attributes" => $attrs); + array_push($this->mvarOutput,$tag); + } + + private function tagData($parser, $value) + { + if ($this->RemoveWhitespace && trim($value) == "") return; + $value = htmlentities($value); + + $tag = array("Type" => "MarkupLiteralElement", "Value" => $value); + array_push($this->mvarOutput, $tag); + $this->mvarOutput[count($this->mvarOutput) - 2]['Elements'][] = $this->mvarOutput[count($this->mvarOutput) - 1]; + array_pop($this->mvarOutput); + } + + function tagClosed($parser, $name) + { + $this->mvarOutput[count($this->mvarOutput) - 2]['Elements'][] = $this->mvarOutput[count($this->mvarOutput) - 1]; + array_pop($this->mvarOutput); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/RandomStringGenerator.inc.php b/app/lib/phast/RandomStringGenerator.inc.php new file mode 100644 index 0000000..52eed3b --- /dev/null +++ b/app/lib/phast/RandomStringGenerator.inc.php @@ -0,0 +1,51 @@ + \ No newline at end of file diff --git a/app/lib/phast/RenderEventArgs.inc.php b/app/lib/phast/RenderEventArgs.inc.php new file mode 100644 index 0000000..80d2abe --- /dev/null +++ b/app/lib/phast/RenderEventArgs.inc.php @@ -0,0 +1,40 @@ +RenderMode = $renderMode; + } + } + class RenderedEventArgs extends EventArgs + { + /** + * Describes whether this is a partial or complete render. + * @var RenderMode + */ + public $RenderMode; + + /** + * Creates a new instance of RenderedEventArgs with the specified renderMode. + * @param RenderMode $renderMode Describes whether this is a partial or complete render. Default RenderMode::Any. + */ + public function __construct($renderMode = null) + { + if ($renderMode == null) $renderMode = RenderMode::Any; + $this->RenderMode = $renderMode; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/RenderMode.inc.php b/app/lib/phast/RenderMode.inc.php new file mode 100644 index 0000000..1965014 --- /dev/null +++ b/app/lib/phast/RenderMode.inc.php @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/app/lib/phast/StringMethods.inc.php b/app/lib/phast/StringMethods.inc.php new file mode 100644 index 0000000..d1b5556 --- /dev/null +++ b/app/lib/phast/StringMethods.inc.php @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/app/lib/phast/System.inc.php b/app/lib/phast/System.inc.php new file mode 100644 index 0000000..5cd44bd --- /dev/null +++ b/app/lib/phast/System.inc.php @@ -0,0 +1,1022 @@ +Message = $message; + $this->ParentError = $parentError; + } + } + /** + * A code file that is included at the immediate start of the call to System::Execute(). + * @author Michael Becker + */ + class IncludeFile + { + /** + * The file name of the PHP code file to include. + * @var string + */ + public $FileName; + /** + * True if the file is required; false otherwise. + * @var boolean + */ + public $IsRequired; + + /** + * Creates a new IncludeFile with the given parameters. + * @param string $filename The file name of the PHP code file to include. + * @param boolean $isRequired True if the file is required; false otherwise. + */ + public function __construct($filename, $isRequired = false) + { + $this->FileName = $filename; + $this->IsRequired = $isRequired; + } + } + /** + * The class which contains all core functionality for the Phast system. + * @author Michael Becker + */ + class System + { + private static $RequestURL; + public static function GetRequestURL() + { + return System::$RequestURL; + } + + private static $ApplicationPath; + public static function GetApplicationPath() + { + return System::$ApplicationPath; + } + public static function SetApplicationPath($value) + { + if (System::$ApplicationPath != null) + { + die("application path already set - cannot be set again!"); + } + System::$ApplicationPath = $value; + } + + private static $SystemPath; + public static function GetSystemPath() + { + return System::$SystemPath; + } + public static function SetSystemPath($value) + { + if (System::$SystemPath != null) + { + die("system path already set - cannot be set again!"); + } + System::$SystemPath = $value; + } + + /** + * Array of global application configuration name/value pairs. + * @var array + */ + public static $Configuration; + /** + * Array of IncludeFiles which represent PHP code files to include before executing the application. + * @var IncludeFile[] + */ + public static $IncludeFiles; + /** + * True if tenanted hosting is enabled; false if this is a single-tenant application. + * @var boolean + */ + public static $EnableTenantedHosting; + /** + * The name of the currently-loaded tenant. + * @var string + */ + public static $TenantName; + + public static function GetTenantName() + { + if (System::$EnableTenantedHosting) + { + $path = System::GetVirtualPath(false); + if (count($path) == 0 || !isset($path[0])) + { + return System::GetConfigurationValue("Application.DefaultTenant"); + } + return $path[0]; + } + return ""; + } + + /** + * Error handler raised when the tenant name is unspecified in a multiple-tenant application. + * @var callable + */ + public static $UnspecifiedTenantErrorHandler; + + /** + * The format in which to serve WebPages. + * @var WebPageFormat + */ + public static $WebPageFormat; + + /** + * Global application variables + * @var string[] + */ + public static $Variables; + + public static $Tasks; + + public static function WriteErrorLog($message) + { + $caller = next(debug_backtrace()); + trigger_error($message . " (in '" . $caller['function'] . "' called from '" . $caller['file'] . "' on line " . $caller['line'] . ")"); + } + + /** + * Gets the relative path on the Web site for the current page. + * @return string $_SERVER["REQUEST_URI"] + */ + public static function GetCurrentRelativePath() + { + return $_SERVER["REQUEST_URI"]; + } + + /** + * Retrieves all key-value pairs associated with the global configuration property. + * @return array: + */ + public static function GetConfigurationValues() + { + return System::$Configuration; + } + + /** + * Retrieves the value of the global configuration property with the given key if it is defined, + * or the default value if it has not been defined. + * @param string $key The key of the configuration property to search for. + * @param string $defaultValue The value to return if the global configuration property with the specified key has not been defined. + * @return string The value of the global configuration property with the given key if defined; otherwise, defaultValue. + */ + public static function GetConfigurationValue($key, $defaultValue = null) + { + if (System::HasConfigurationValue($key)) + { + return System::$Configuration[$key]; + } + + $value = System::$ConfigurationParser->RetrieveProperty($key, $defaultValue); + if ($value != null) return $value->Value; + + return $defaultValue; + } + /** + * Sets the global configuration property with the given key to the specified value. + * @param string $key The key of the configuration property to set. + * @param string $value The value to which to set the property. + */ + public static function SetConfigurationValue($key, $value) + { + System::$Configuration[$key] = $value; + } + /** + * Clears the value of the global configuration property with the given key. + * @param string $key The key of the configuration property whose value will be cleared. + */ + public static function ClearConfigurationValue($key) + { + unset(System::$Configuration[$key]); + } + /** + * Determines whether a global configuration property with the given key is defined. + * @param string $key The key of the configuration property to search for. + * @return boolean True if the global configuration property exists; false otherwise. + */ + public static function HasConfigurationValue($key) + { + return isset(System::$Configuration[$key]); + } + + public static function SaveConfigurationFile() + { + $filename = System::GetApplicationPath() . "/Include/Configuration.inc.php"; + $file = fopen($filename, "w"); + if ($file === false) return false; + + $properties = array + ( + new ConfigurationComment("Whether we should enable users to run the setup application"), + new ConfigurationProperty("Setup.Enabled", "true"), + new ConfigurationSpacer(), + new ConfigurationComment("The base path of the Web site"), + new ConfigurationProperty("Application.BasePath", System::GetConfigurationValue("Application.BasePath")), + new ConfigurationSpacer(), + // new ConfigurationComment("The default tenant for the Web site"), + // new ConfigurationProperty("Application.DefaultTenant", "default"), + // new ConfigurationSpacer(), + new ConfigurationComment("The location of static Phast-related files (scripts, stylesheets, etc.)"), + new ConfigurationProperty("System.StaticPath", "//static.alcehosting.net/dropins/Phast") + ); + + $hasExtraProperties = false; + foreach (System::$Configuration as $key => $value) + { + if ($key == "Setup.Enabled" || $key == "Application.BasePath" || $key == "System.StaticPath") + { + continue; + } + + if (!$hasExtraProperties) + { + $hasExtraProperties = true; + $properties[] = new ConfigurationSpacer(); + } + $properties[] = new ConfigurationProperty($key, $value); + } + + fwrite($file, "ID . "\", \"" . $item->Value . "\");\r\n"); + } + else if (get_class($item) == "Phast\\Configuration\\V1\\ConfigurationComment") + { + fwrite($file, "\t// " . $item->Value . "\r\n"); + } + else if (get_class($item) == "Phast\\Configuration\\V1\\ConfigurationSpacer") + { + fwrite($file, "\t\r\n"); + } + } + fwrite($file, "?>\r\n"); + fclose($file); + + return true; + } + + /** + * The WebPageParser + * @var PhastParser + */ + public static $Parser; + + /** + * The ConfigurationParser + * @var ConfigurationParser + */ + public static $ConfigurationParser; + + /** + * The page that is currently being processed. + * @var WebPage + */ + public static $CurrentPage; + + /** + * The event handler that is called when an irrecoverable error occurs. + * @var callable + */ + public static $ErrorEventHandler; + + /** + * The event handler that is called before this application executes. + * @var callable + */ + public static $BeforeLaunchEventHandler; + /** + * The event handler that is called after this application executes. + * @var callable + */ + public static $AfterLaunchEventHandler; + + /** + * Redirects the user to the specified path via a Location header. + * @param string $path The expandable string path to navigate to. + */ + public static function Redirect($path) + { + $realpath = System::ExpandRelativePath($path); + header("Location: " . $realpath); + return; + } + /** + * Expands the given path by replacing the tilde character (~) with the value of the + * configuration property Application.BasePath. + * @param string $path The path to expand. + * @param boolean $includeServerInfo True if server information should be included in the response; false otherwise. + * @return string The expanded form of the given expandable string path. + */ + public static function ExpandRelativePath($path, $includeServerInfo = false) + { + $torepl = System::GetConfigurationValue("Application.BasePath"); + $torepl_nontenanted = $torepl; + $tenantName = System::GetTenantName(); + if ($tenantName != "") + { + $torepl .= "/" . $tenantName; + } + + $retval_nontenanted = str_replace("~", $torepl_nontenanted, $path); + $physicalFilePath = System::GetApplicationPath() . $retval_nontenanted; + + if (file_exists($physicalFilePath)) + { + $retval = $retval_nontenanted; + } + else + { + $retval = str_replace("~~", $torepl_nontenanted, $path); + $retval = str_replace("~", $torepl, $retval); + } + if ($includeServerInfo) + { + // from http://stackoverflow.com/questions/6768793/php-get-the-full-url + $sp = strtolower($_SERVER["SERVER_PROTOCOL"]); + $protocol = substr($sp, 0, strpos($sp, "/")) . $s; + $port = ($_SERVER["SERVER_PORT"] == "80") ? "" : (":".$_SERVER["SERVER_PORT"]); + $serverPath = $protocol . "://" . $_SERVER["SERVER_NAME"] . $port; + $retval = $serverPath . $retval; + } + + // parse the string for variables + $retval_len = strlen($retval); + $ret = ""; + for ($i = 0; $i < $retval_len; $i++) + { + $c = substr($retval, $i, 1); + + if ($c == "\$") + { + if ($i > 0 && substr($retval, $i - 1, 1) == "\\") + { + // literal $ + $ret .= "$"; + continue; + } + else if ($i < $retval_len - 2) + { + $x = stripos($retval, ")", $i + 1); + if ($x === false) + { + $ret .= $c; + continue; + } + + $varString = substr($retval, $i + 2, $x - ($i + 2)); + + $category = null; + $variable = $varString; + $defaultValue = null; + + $posCategoryValueSeparator = stripos($varString, ":"); + if ($posCategoryValueSeparator !== false) + { + $category = substr($varString, 0, $posCategoryValueSeparator); + $variable = substr($varString, $posCategoryValueSeparator + 1); + } + + $posValueDefaultSeparator = stripos($variable, "|"); + if ($posValueDefaultSeparator !== false) + { + $defaultValue = substr($variable, $posValueDefaultSeparator + 1); + $variable = substr($variable, 0, $posValueDefaultSeparator); + } + + $handled = false; + switch ($category) + { + case "Path": + { + if (System::$CurrentPage != null) + { + $ret .= System::$CurrentPage->GetPathVariableValue($variable, $defaultValue); + $handled = true; + } + break; + } + case "Configuration": + { + if (array_key_exists($variable, System::$Configuration)) + { + $ret .= System::$Configuration[$variable]; + $handled = true; + } + break; + } + case "Variables": + { + if (array_key_exists($variable, System::$Variables)) + { + foreach (System::$Variables as $varr) + { + if ($varr->Name == $variable) + { + $ret .= $varr->Value; + $handled = true; + break; + } + } + } + break; + } + } + if (!$handled) $ret .= $defaultValue; + $i += strlen($varString) + 2; // '(' + varString + ')'; the preceding '$' is taken care of by $i++ in the next loop iteration + continue; + } + else + { + $ret .= $c; + } + } + else if ($c == "\\") + { + continue; + } + else + { + $ret .= $c; + } + } + return $ret; + } + public static function RedirectToLoginPage() + { + if (System::$EnableTenantedHosting) + { + $_SESSION[System::$TenantName . ".System.LastRedirectURL"] = $_SERVER["REQUEST_URI"]; + } + else + { + $_SESSION["System.LastRedirectURL"] = $_SERVER["REQUEST_URI"]; + } + System::Redirect(System::GetConfigurationValue("LoginPageRedirectURL", "~/account/login")); + return; + } + public static function RedirectFromLoginPage() + { + $url = null; + if (System::$EnableTenantedHosting) + { + if ($_SESSION[System::$TenantName . ".System.LastRedirectURL"] != null) + { + $url = $_SESSION[System::$TenantName . ".System.LastRedirectURL"]; + } + } + else + { + if ($_SESSION["System.LastRedirectURL"] != null) + { + $url = $_SESSION["System.LastRedirectURL"]; + } + } + if ($url == null) $url = System::GetConfigurationValue("Application.DefaultURL", "~/"); + System::Redirect($url); + } + public static function GetVirtualPath($supportTenantedHosting = true) + { + if (isset($_GET["virtualpath"])) + { + if ($_GET["virtualpath"] != null) + { + $array = explode("/", $_GET["virtualpath"]); + + if ($supportTenantedHosting) + { + $tenantName = System::GetTenantName(); + if ($tenantName != "") + { + System::$TenantName = $array[0]; + array_shift($array); + } + } + return $array; + } + } + return array(); + } + public static function IncludeFile($filename, $isRequired) + { + $filename = str_replace("~/", System::GetApplicationPath() . "/", $filename); + if ($isRequired) + { + require_once($filename); + } + else + { + include_once($filename); + } + } + + public static function Initialize() + { + $RootPath = System::GetApplicationPath(); + + // require_once changed to include_once to ensure that PHP configuration is not required for Phast 2.0 (Website.xml) sites + include_once($RootPath . "/include/Configuration.inc.php"); + + // load the xml files in Configuration directory + $a = glob($RootPath . "/include/Configuration/*.xml"); + foreach ($a as $filename) + { + System::LoadXMLConfigurationFile($filename); + } + + // Local Objects loader + $a = glob($RootPath . "/include/Objects/*.inc.php"); + foreach ($a as $filename) + { + require_once($filename); + } + + // Local Controls loader + $a = glob($RootPath . "/include/WebControls/*.inc.php"); + foreach ($a as $filename) + { + require_once($filename); + } + + // Local WebControls PHPX Code-Behind loader + $a = glob($RootPath . "/include/WebControls/*.phpx.php"); + foreach ($a as $filename) + { + require_once($filename); + } + // Local WebControls PHPX loader + $a = glob($RootPath . "/include/WebControls/*.phpx"); + foreach ($a as $filename) + { + System::$Parser->LoadFile($filename); + } + + // Local MasterPages Code-Behind loader + $a = glob($RootPath . "/include/MasterPages/*.phpx.php"); + foreach ($a as $filename) + { + require_once($filename); + } + // Local MasterPages loader + $a = glob($RootPath . "/include/MasterPages/*.phpx"); + foreach ($a as $filename) + { + System::$Parser->LoadFile($filename); + } + + // Local Pages Code-Behind loader + $a = glob($RootPath . "/include/Pages/*.phpx.php"); + foreach ($a as $filename) + { + require_once($filename); + } + + // Local Pages loader + $pagesPaths = System::GetConfigurationValue("Paths.Pages", [ "/include/Pages" ]); + foreach ($pagesPaths as $pagesPath) + { + $a = glob($RootPath . $pagesPath . "/*.phpx"); + foreach ($a as $filename) + { + System::$Parser->LoadFile($filename); + } + } + + // Module Objects Code-Behind loader + $a = glob($RootPath . "/include/Modules/*/Objects/*.inc.php"); + foreach ($a as $filename) + { + require_once($filename); + } + + // Module WebControls PHPX Code-Behind loader + $a = glob($RootPath . "/include/Modules/*/WebControls/*.phpx.php"); + foreach ($a as $filename) + { + require_once($filename); + } + // Module WebControls PHPX loader + $a = glob($RootPath . "/include/Modules/*/WebControls/*.phpx"); + foreach ($a as $filename) + { + System::$Parser->LoadFile($filename); + } + + // Module Pages Code-Behind loader + $a = glob($RootPath . "/include/Modules/*/Pages/*.phpx.php"); + foreach ($a as $filename) + { + require_once($filename); + } + // Module Pages loader + $a = glob($RootPath . "/include/Modules/*/Pages/*.phpx"); + foreach ($a as $filename) + { + System::$Parser->LoadFile($filename); + } + + // Application code-behind (V2 style) + if (file_exists($RootPath . "/include/Application.inc.php")) + { + require_once($RootPath . "/include/Application.inc.php"); + } + + // Application PHPX and code-behind (V3 style) + if (file_exists($RootPath . "/include/Application.phpx")) + { + System::$Parser->LoadFile($RootPath . "/include/Application.phpx"); + } + if (file_exists($RootPath . "/include/Application.phpx.php")) + { + require_once($RootPath . "/include/Application.phpx.php"); + } + } + + /** + * Starts the Phast application. + * @return boolean True if the launch succeeded; false if a failure occurred. + */ + public static function Launch() + { + System::Initialize(); + + if (System::$EnableTenantedHosting) + { + // check to see if we have a default tenant; + // if we do, we should make it visible in the address bar + if (System::GetConfigurationValue("Application.AutoRedirectDefaultTenant") !== false) + { + $path = System::GetVirtualPath(false); + if (count($path) == 0 || !isset($path[0])) + { + System::Redirect("~~/" . System::GetConfigurationValue("Application.DefaultTenant")); + return false; + } + } + } + + $path = System::GetVirtualPath(); + if (is_callable(System::$BeforeLaunchEventHandler)) + { + $retval = call_user_func(System::$BeforeLaunchEventHandler, $path); + if (!$retval) return false; + } + $path = System::GetVirtualPath(); + + // strip path extension if there is one + $pathLast = $path[count($path) - 1]; + $ix = strripos($pathLast, "."); + if ($ix !== false) + { + $pathExt = substr($pathLast, $ix + 1); + $path[count($path) - 1] = substr($pathLast, 0, $ix); + + switch ($pathExt) + { + case "json": + { + System::$WebPageFormat = WebPageFormat::JSON; + break; + } + case "xml": + { + System::$WebPageFormat = WebPageFormat::XML; + break; + } + case "html": + { + System::$WebPageFormat = WebPageFormat::HTML; + break; + } + case "js": + { + System::$WebPageFormat = WebPageFormat::JavaScript; + break; + } + default: + { + if ($path[count($path) - 1] != "") + { + $path[count($path) - 1] = $path[count($path) - 1]; + System::Redirect("~/" . implode("/", $path)); + return; + } + } + } + } + + $success = false; + + $pathVars = array(); + + $actualPage = null; + + foreach (System::$Parser->Pages as $page) + { + if (!$page->Enabled) continue; + + $actualPathParts = $path; + // try to parse the path, for example: + // profile/$(username)/dashboard + + $pathParts = explode("/", $page->FileName); + $pathPartCount = count($pathParts); + $found = true; + for ($i = 0; $i < $pathPartCount; $i++) + { + $pathPart = $pathParts[$i]; + if (stripos($pathPart, "$(") == 0 && stripos($pathPart, ")") == strlen($pathPart) - 1) + { + $pathVarName = substr($pathPart, 2, strlen($pathPart) - 3); + $pathVars[$pathVarName] = $actualPathParts[$i]; + } + else + { + $app = ""; + if (isset($actualPathParts[$i])) $app = $actualPathParts[$i]; + + if ($app != $pathPart && (!($app == "" && $pathPart == ""))) + { + // a literal path string is broken; we can't use this + $found = false; + break; + } + } + } + if ($found) + { + $actualPage = $page; + break; + } + } + + if ($actualPage != null) + { + foreach ($pathVars as $key => $value) + { + $actualPage->PathVariables[] = new WebVariable($key, $value); + } + + System::$CurrentPage = $actualPage; + System::$RequestURL = "~/" . implode("/", $path); + + $actualPage->Render(); + + System::$CurrentPage = null; + System::$RequestURL = null; + + $success = true; + } + + if (is_callable(System::$AfterLaunchEventHandler)) + { + $retval = call_user_func(System::$AfterLaunchEventHandler); + if (!$retval) return false; + } + + if (!$success) + { + $retval = call_user_func(System::$ErrorEventHandler, new ErrorEventArgs("The specified resource is not available on this server. (" . (System::$EnableTenantedHosting ? ("Tenanted - " . System::GetTenantName()) : "Non-Tenanted") . ")")); + return false; + } + return true; + } + + public static function LoadXMLConfigurationFile($filename) + { + System::$ConfigurationParser->LoadFile($filename); + } + } + + require_once("Enumeration.inc.php"); + require_once("Orientation.inc.php"); + require_once("UUID.inc.php"); + + require_once("RandomStringGenerator.inc.php"); + + require_once("RenderMode.inc.php"); + + require_once("EventArgs.inc.php"); + require_once("CancelEventArgs.inc.php"); + require_once("RenderEventArgs.inc.php"); + + // require_once("Enum.inc.php"); + require_once("StringMethods.inc.php"); + // require_once("JH.Utilities.inc.php"); + + require_once("Configuration/Property.inc.php"); + require_once("Configuration/Group.inc.php"); + require_once("Configuration/Flavor.inc.php"); + require_once("Configuration/ConfigurationParser.inc.php"); + + require_once("Validator.inc.php"); + + /** + * Provides an enumeration of predefined values for horizontal alignment of content. + * @author Michael Becker + */ + abstract class HorizontalAlignment extends Enumeration + { + /** + * The horizontal alignment is not specified. + * @var int 0 + */ + const Inherit = 0; + /** + * The content is aligned to the left (near). + * @var int 1 + */ + const Left = 1; + /** + * The content is aligned in the center. + * @var int 2 + */ + const Center = 2; + /** + * The content is aligned to the right (far). + * @var int 3 + */ + const Right = 3; + } + /** + * Provides an enumeration of predefined values for vertical alignment of content. + * @author Michael Becker + */ + abstract class VerticalAlignment extends Enumeration + { + /** + * The vertical alignment is not specified. + * @var int 0 + */ + const Inherit = 0; + /** + * The content is aligned to the top (near). + * @var int 1 + */ + const Top = 1; + /** + * The content is aligned in the middle. + * @var int 2 + */ + const Middle = 2; + /** + * The content is aligned to the bottom (far). + * @var int 3 + */ + const Bottom = 3; + } + + require("Conditionals/ConditionalComparison.inc.php"); + require("Conditionals/ConditionalStatement.inc.php"); + + require_once("Utilities/Stopwatch.inc.php"); + + require_once("Compilers/StyleSheet/Internal/LessStyleSheetCompiler.inc.php"); + + require("WebApplication.inc.php"); + require("WebApplicationTask.inc.php"); + + require("WebNamespaceReference.inc.php"); + require("WebVariable.inc.php"); + + require("WebOpenGraphSettings.inc.php"); + require("WebResourceLink.inc.php"); + require("WebScript.inc.php"); + require("WebStyleSheet.inc.php"); + + require("WebControlAttribute.inc.php"); + require("WebControlClientIDMode.inc.php"); + require("WebControl.inc.php"); + + require("WebPageFormat.inc.php"); + + require("WebPage.inc.php"); + require("WebPageCommand.inc.php"); + require("WebPageMessage.inc.php"); + require("WebPageMetadata.inc.php"); + require("WebPageVariable.inc.php"); + + require("HTMLControl.inc.php"); + + require("Parser/ControlLoader.inc.php"); + require("Parser/PhastParser.inc.php"); + + require("Configuration/V1/ConfigurationItem.inc.php"); + + require("Configuration/V1/ConfigurationComment.inc.php"); + require("Configuration/V1/ConfigurationProperty.inc.php"); + require("Configuration/V1/ConfigurationSpacer.inc.php"); + + System::$Configuration = array(); + System::$EnableTenantedHosting = false; + + System::$IncludeFiles = array(); + System::$UnspecifiedTenantErrorHandler = function() + { + return call_user_func(System::$ErrorEventHandler, new ErrorEventArgs("No tenant name was specified for this tenanted hosting application.")); + }; + System::$ErrorEventHandler = function($e) + { + echo($e->Message); + }; + System::$Variables = array(); + System::$Parser = new PhastParser(); + + System::SetSystemPath(dirname(__FILE__)); + $PhastRootPath = System::GetSystemPath(); + + // Initialize the ConfigurationParser + if (System::$ConfigurationParser == null) System::$ConfigurationParser = new ConfigurationParser(); + + require_once("Data/DataSystem.inc.php"); + + // Global Controls loader + $a = glob($PhastRootPath . "/WebControls/*.inc.php"); + foreach ($a as $filename) + { + require_once($filename); + } + // Global WebControls PHPX Code-Behind loader + $a = glob($PhastRootPath . "/WebControls/*.phpx.php"); + foreach ($a as $filename) + { + require_once($filename); + } + // Global WebControls PHPX loader + $a = glob($PhastRootPath . "/WebControls/*.phpx"); + foreach ($a as $filename) + { + System::$Parser->LoadFile($filename); + } + + // Global HTMLControls loader + $a = glob($PhastRootPath . "/HTMLControls/*.inc.php"); + foreach ($a as $filename) + { + require_once($filename); + } + + $a = glob($PhastRootPath . "/Compilers/StyleSheet/Internal/Formatters/*.inc.php"); + foreach ($a as $filename) + { + require_once($filename); + } + + $a = glob($PhastRootPath . "/Validators/*.inc.php"); + foreach ($a as $filename) + { + require_once($filename); + } + + \date_default_timezone_set(System::GetConfigurationValue("System.DefaultTimezone", date_default_timezone_get())); + + session_start(); +?> diff --git a/app/lib/phast/UUID.inc.php b/app/lib/phast/UUID.inc.php new file mode 100644 index 0000000..319a598 --- /dev/null +++ b/app/lib/phast/UUID.inc.php @@ -0,0 +1,91 @@ +urand = @fopen ( '/dev/urandom', 'rb' ); + } + + public static function Generate() + { + $uuid = new UUID(); + return $uuid->get(); + } + + /** + * @brief Generates a Universally Unique IDentifier, version 4. + * + * This function generates a truly random UUID. The built in CakePHP String::uuid() function + * is not cryptographically secure. You should uses this function instead. + * + * @see http://tools.ietf.org/html/rfc4122#section-4.4 + * @see http://en.wikipedia.org/wiki/UUID + * @return string A UUID, made up of 32 hex digits and 4 hyphens. + */ + function get() + { + + $pr_bits = false; + if (is_a ( $this, 'uuid' )) + { + if (is_resource ( $this->urand )) + { + $pr_bits .= @fread ( $this->urand, 16 ); + } + } + if (! $pr_bits) + { + $fp = @fopen ( '/dev/urandom', 'rb' ); + if ($fp !== false) + { + $pr_bits .= @fread ( $fp, 16 ); + @fclose ( $fp ); + } + else + { + // If /dev/urandom isn't available (eg: in non-unix systems), use mt_rand(). + $pr_bits = ""; + for($cnt = 0; $cnt < 16; $cnt ++) + { + $pr_bits .= chr ( mt_rand ( 0, 255 ) ); + } + } + } + $time_low = bin2hex ( substr ( $pr_bits, 0, 4 ) ); + $time_mid = bin2hex ( substr ( $pr_bits, 4, 2 ) ); + $time_hi_and_version = bin2hex ( substr ( $pr_bits, 6, 2 ) ); + $clock_seq_hi_and_reserved = bin2hex ( substr ( $pr_bits, 8, 2 ) ); + $node = bin2hex ( substr ( $pr_bits, 10, 6 ) ); + + /** + * Set the four most significant bits (bits 12 through 15) of the + * time_hi_and_version field to the 4-bit version number from + * Section 4.1.3. + * @see http://tools.ietf.org/html/rfc4122#section-4.1.3 + */ + $time_hi_and_version = hexdec ( $time_hi_and_version ); + $time_hi_and_version = $time_hi_and_version >> 4; + $time_hi_and_version = $time_hi_and_version | 0x4000; + + /** + * Set the two most significant bits (bits 6 and 7) of the + * clock_seq_hi_and_reserved to zero and one, respectively. + */ + $clock_seq_hi_and_reserved = hexdec ( $clock_seq_hi_and_reserved ); + $clock_seq_hi_and_reserved = $clock_seq_hi_and_reserved >> 2; + $clock_seq_hi_and_reserved = $clock_seq_hi_and_reserved | 0x8000; + + return strtoupper( sprintf ( '%08s%04s%04x%04x%012s', $time_low, $time_mid, $time_hi_and_version, $clock_seq_hi_and_reserved, $node ) ); + } + + public static function format($input) + { + $output = $input; + $output = substr($output, 0, 8) . "-" . substr($output, 8, 4) . "-" . substr($output, 12, 4) . "-" . substr($output, 16, 4) . "-" . substr($output, 20); + return "{" . $output . "}"; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/Utilities/Stopwatch.inc.php b/app/lib/phast/Utilities/Stopwatch.inc.php new file mode 100644 index 0000000..2709cac --- /dev/null +++ b/app/lib/phast/Utilities/Stopwatch.inc.php @@ -0,0 +1,52 @@ +startTime = microtime(true); + $this->endTime = null; + } + public function stop() + { + $this->endTime = microtime(true); + } + public function reset() + { + $this->startTime = null; + $this->endTime = null; + } + + /** + * Get the elapsed time in seconds + * + * @param $timerName string The name of the timer to start + * @return float|bool The elapsed time since start() was called, or false if the timer is stopped; + */ + public function getElapsedTime() + { + if ($this->startTime === null) return false; + if ($this->endTime === null) return false; + + return ($this->endTime - $this->startTime); + } + + public function __construct() + { + $this->startTime = null; + $this->endTime = null; + } + } + +?> \ No newline at end of file diff --git a/app/lib/phast/Validator.inc.php b/app/lib/phast/Validator.inc.php new file mode 100644 index 0000000..04c031a --- /dev/null +++ b/app/lib/phast/Validator.inc.php @@ -0,0 +1,15 @@ +ValidateInternal($value); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/Validators/EmailAddressValidator.inc.php b/app/lib/phast/Validators/EmailAddressValidator.inc.php new file mode 100644 index 0000000..ad35020 --- /dev/null +++ b/app/lib/phast/Validators/EmailAddressValidator.inc.php @@ -0,0 +1,116 @@ +CheckDNSRecords = false; + $this->PreventThrowawayAddresses = false; + + $this->DomainBlockedMessage = EmailAddressValidator::$DefaultDomainBlockedMessage; + $this->EmailAddressInvalidMessage = EmailAddressValidator::$DefaultEmailAddressInvalidMessage; + + $this->ThrowawayAddresses = array(); + foreach (EmailAddressValidator::$DefaultThrowawayAddresses as $addr) + { + $this->ThrowawayAddresses[] = $addr; + } + } + + protected function ValidateInternal($value) + { + $domainPos = stripos($value, "@"); + $domain = $value; + if ($domainPos !== false) + { + $domain = substr($value, $domainPos + 1); + } + else + { + $this->Message = $this->EmailAddressInvalidMessage; + return false; + } + + if ($this->PreventThrowawayAddresses || $this->CheckDNSRecords) + { + // determine if domain is in the blacklist + if ($this->PreventThrowawayAddresses) + { + if (in_array($domain, $this->ThrowawayAddresses)) + { + $this->Message = $this->DomainBlockedMessage; + return false; + } + } + + // domain isn't, check DNS and see if the IP is blocked + $recs = dns_get_record($domain); + + if (count($recs) === 0) + { + $this->Message = $this->EmailAddressInvalidMessage; + return false; + } + + $ips = array(); + foreach ($recs as $rec) + { + switch ($rec["type"]) + { + case "AAAA": + { + $ips[] = $rec["ipv6"]; + break; + } + case "A": + { + $ips[] = $rec["ip"]; + break; + } + } + } + + foreach ($this->ThrowawayAddresses as $ip) + { + if (in_array($ip, $ips)) + { + $this->Message = $this->DomainBlockedMessage; + return false; + } + } + } + return true; + } + } + + EmailAddressValidator::$DefaultThrowawayAddresses = array + ( + // mailinator.com throwaway domains + "mailinator.com", + "mailinator.net", + "streetwisemail.com", + + // mailinator.com throwaway IP addresses + "2600:3c03::f03c:91ff:fe50:caa7", + "23.239.11.30" + ); + + EmailAddressValidator::$DefaultDomainBlockedMessage = "Your e-mail address has been blocked"; + EmailAddressValidator::$DefaultEmailAddressInvalidMessage = "Please enter a valid e-mail address"; +?> \ No newline at end of file diff --git a/app/lib/phast/WebApplication.inc.php b/app/lib/phast/WebApplication.inc.php new file mode 100644 index 0000000..1f74ae9 --- /dev/null +++ b/app/lib/phast/WebApplication.inc.php @@ -0,0 +1,20 @@ +Configuration = array(); + $this->Modules = array(); + } + public function Run() + { + echo(ROOT_PATH); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebApplicationTask.inc.php b/app/lib/phast/WebApplicationTask.inc.php new file mode 100644 index 0000000..e96a36c --- /dev/null +++ b/app/lib/phast/WebApplicationTask.inc.php @@ -0,0 +1,45 @@ +CheckAvailabilityFunction); + } + + public function __construct($id, $title = null, $taskType = null, $description = null, $targetURL = null, $targetScript = null, $targetFrame = null, $tasks = null) + { + if ($title == null) $title = $id; + if ($tasks == null) $tasks = array(); + + $this->ID = $id; + $this->Title = $title; + $this->TaskType = $taskType; + $this->Description = $description; + $this->TargetURL = $targetURL; + $this->TargetScript = $targetScript; + $this->TargetFrame = $targetFrame; + $this->Tasks = $tasks; + + $this->CheckAvailabilityFunction = function() + { + return true; + }; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControl.inc.php b/app/lib/phast/WebControl.inc.php new file mode 100644 index 0000000..ebb1a49 --- /dev/null +++ b/app/lib/phast/WebControl.inc.php @@ -0,0 +1,684 @@ +Controls; + } + + /** + * Retrieves the control with the specified ID in this control's child control collection. + * @param string $id The ID of the control to search for. + * @return WebControl|NULL The control with the specified ID, or null if no control with the specified ID was found. + */ + public function GetControlByID($id, $recurse = true) + { + $ctls = $this->GetAllControls(); + foreach ($ctls as $ctl) + { + if ($ctl->ID == $id) return $ctl; + if ($recurse) + { + $ctl1 = $ctl->GetControlByID($id, true); + if ($ctl1 != null) return $ctl1; + } + } + return null; + } + + public function FindParentPage() + { + $parent = $this->ParentObject; + while ($parent != null) + { + if (get_class($parent) == "Phast\\Parser\\Page" || get_class($parent) == "Phast\\Parser\\MasterPage") + { + return $parent; + } + $parent = $parent->ParentObject; + } + return null; + } + + /** + * Generates a random string of the specified length using the characters specified in the string valid_chars. + * @param string $valid_chars Set of characters used to build the resulting random string. + * @param int $length The length of the resulting random string. + * @return string The random string of the specified length using the specified character set. + */ + private static function GenerateRandomString($valid_chars, $length) + { + // start with an empty random string + $random_string = ""; + + // count the number of chars in the valid chars string so we know how many choices we have + $num_valid_chars = strlen($valid_chars); + + // repeat the steps until we've created a string of the right length + for ($i = 0; $i < $length; $i++) + { + // pick a random number from 1 up to the number of valid chars + $random_pick = mt_rand(1, $num_valid_chars); + + // take the random character out of the string of valid chars + // subtract 1 from $random_pick because strings are indexed starting at 0, and we started picking at 1 + $random_char = $valid_chars[$random_pick-1]; + + // add the randomly-chosen char onto the end of our string so far + $random_string .= $random_char; + } + + // return our finished random string + return $random_string; + } + + public function __construct() + { + $this->Enabled = true; + $this->Visible = true; + $this->HorizontalAlignment = HorizontalAlignment::Inherit; + $this->VerticalAlignment = VerticalAlignment::Inherit; + + $this->Controls = array(); + $this->EnableRender = true; + + $this->HasContent = true; + + $this->TagName = null; + $this->ClassList = array(); + $this->Attributes = array(); + $this->StyleRules = array(); + + $this->ParseChildElements = false; + + $this->ClientIDMode = WebControlClientIDMode::None; + } + + /** + * Retrieves a ClientProperty associated with this control via a browser cookie. + * @param string $name The name of the property to retrieve. + * @param string $defaultValue The value to retrieve if the ClientProperty has not been set. + * @return string The value of the property with the given name, or defaultValue if the property has not been set. + */ + public function GetClientProperty($name, $defaultValue = null) + { + if (!isset($_COOKIE[$this->ID . "__ClientProperty_" . $name])) return $defaultValue; + return $_COOKIE[$this->ID . "__ClientProperty_" . $name]; + } + /** + * Updates a ClientProperty associated with this control via a browser cookie. + * @param string $name The name of the property to update. + * @param string $value The value with which to update the property. + * @param string $expires Expiration data for the cookie associated with the ClientProperty. + */ + public function SetClientProperty($name, $value, $expires = null) + { + setcookie($this->ID . "__ClientProperty_" . $name, $value, $expires); + } + + private $Initialized; + + /** + * Initializes this control, calling the OnInitialize() function and initializing any child + * controls. + */ + public function Initialize() + { + $id = null; + $clientid = null; + + if ($this->ClientIDMode == WebControlClientIDMode::Automatic && $this->ID == null) + { + $parent = $this->ParentObject; + $clientid = ""; + + while ($parent != null) + { + $clientid = $parent->ID . "_" . $clientid; + $parent = $parent->ParentObject; + } + + $id = "WFX" . WebControl::GenerateRandomString("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", 10); + $clientid .= $id; + } + if ($id != null) $this->ID = $id; + if ($clientid != null) $this->ClientID = $clientid; + + if ($this->OnClientClick != null) + { + $this->Attributes[] = new WebControlAttribute("onclick", $this->OnClientClick); + } + + $this->OnInitialize(); + if (is_array($this->Controls)) + { + foreach ($this->Controls as $control) + { + $control->ParentObject = $this; + if (method_exists($control, "Initialize")) + { + $control->Initialize(); + } + } + } + else + { + trigger_error("Controls is not array in " . get_class($this) . " ; did you forget to call parent::__construct() ?"); + } + $this->Initialized = true; + } + + protected function OnInitialize() + { + + } + + protected function BeforeContent() + { + + } + protected function RenderContent() + { + + } + protected function AfterContent() + { + + } + + /** + * Renders an HTML beginning tag with the specified parameters. + * @param string $tagName The name of the tag to open. + * @param array $namedParameters Associative array that specifies ClassNames, Attributes, and StyleRules to render with the beginning tag. + */ + public static function BeginTag($tagName, $namedParameters) + { + echo("<" . $tagName); + + if (is_array($namedParameters)) + { + if (isset($namedParameters["ClassNames"])) $classNames = $namedParameters["ClassNames"]; + if (isset($namedParameters["Attributes"])) $attributes = $namedParameters["Attributes"]; + if (isset($namedParameters["StyleRules"])) $styleRules = $namedParameters["StyleRules"]; + } + + if (!isset($classNames) || !is_array($classNames)) $classNames = array(); + if (!isset($attributes) ||!is_array($attributes)) $attributes = array(); + if (!isset($styleRules) ||!is_array($styleRules)) $styleRules = array(); + + $count = count($classNames); + if ($count > 0) + { + echo(" class=\""); + for ($i = 0; $i < $count; $i++) + { + echo($classNames[$i]); + if ($i < $count - 1) echo(" "); + } + echo("\""); + } + + $count = count($styleRules); + if ($count > 0) + { + echo(" style=\""); + for ($i = 0; $i < $count; $i++) + { + $item = $styleRules[$i]; + echo($item->Name . ": " . $item->Value); + if ($i < $count - 1) echo("; "); + } + echo("\""); + } + + $count = count($attributes); + if ($count > 0) + { + echo(" "); + for ($i = 0; $i < $count; $i++) + { + $item = $attributes[$i]; + echo($item->Name . "=\"" . $item->Value . "\""); + if ($i < $count - 1) echo(" "); + } + } + echo(">"); + } + /** + * Renders an HTML ending tag with the given tag name. + * @param string $tagName The name of the tag to close. + */ + public static function EndTag($tagName) + { + echo(""); + } + + /** + * Renders the beginning tag of this WebControl, including any attribute, CSS class, or style + * information specified by the control author or the caller. + */ + protected function RenderBeginTag() + { + if ($this->TagName != "") + { + echo("<" . $this->TagName); + + $styleAttributeContent = ""; + $classAttributeContent = ""; + + $count = count($this->Attributes); + if ($count > 0) + { + $found = false; + foreach ($this->Attributes as $attr) + { + if (!(strtolower($attr->Name) == "style" || strtolower($attr->Name) == "class" || strtolower($attr->Name) == "disabled")) + { + $found = true; + break; + } + } + if ($found) echo(" "); + $i = 0; + foreach ($this->Attributes as $attr) + { + if (strtolower($attr->Name) == "style") + { + if (!StringMethods::EndsWith($attr->Value, ";")) + { + $styleAttributeContent .= $attr->Value . "; "; + } + else + { + $styleAttributeContent .= $attr->Value; + } + } + else if (strtolower($attr->Name) == "class") + { + $classAttributeContent .= $attr->Value; + } + else if (strtolower($attr->Name) == "disabled") + { + // we normalize the disabled attribute + $this->Enabled = false; + echo(" disabled=\"disabled\""); + } + else if (strtolower($attr->Name) == "id") + { + $this->ID = $attr->Value; + } + else + { + echo($attr->Name); + echo("=\""); + echo($attr->Value); + echo("\""); + if ($i < $count - 1) echo(" "); + } + $i++; + } + } + + $styleRules = $this->StyleRules; + if (!$this->Visible) + { + $styleRules[] = new WebStyleSheetRule("display", "none"); + } + if ($this->Width != null) + { + $styleRules[] = new WebStyleSheetRule("width", $this->Width); + } + if ($this->Height != null) + { + $styleRules[] = new WebStyleSheetRule("height", $this->Height); + } + if ($this->MinimumWidth != null) + { + $styleRules[] = new WebStyleSheetRule("min-width", $this->MinimumWidth); + } + if ($this->MinimumHeight != null) + { + $styleRules[] = new WebStyleSheetRule("min-height", $this->MinimumHeight); + } + if ($this->MaximumWidth != null) + { + $styleRules[] = new WebStyleSheetRule("max-width", $this->MaximumWidth); + } + if ($this->MaximumHeight != null) + { + $styleRules[] = new WebStyleSheetRule("max-height", $this->MaximumHeight); + } + + if (count($styleRules) > 0 || $styleAttributeContent != "") + { + echo(" style=\""); + echo($styleAttributeContent); + $count = count($styleRules); + $i = 0; + foreach ($styleRules as $rule) + { + echo($rule->Name); + echo(": "); + echo($rule->Value); + echo(";"); + if ($i < $count - 1) echo(" "); + $i++; + } + echo("\""); + } + if ($this->CssClass != "") + { + if ($classAttributeContent != "") $classAttributeContent .= " "; + $classAttributeContent .= $this->CssClass; + } + if (count($this->ClassList) > 0) + { + if ($classAttributeContent != "") $classAttributeContent .= " "; + $count = count($this->ClassList); + for ($i = 0; $i < $count; $i++) + { + $classAttributeContent .= $this->ClassList[$i]; + if ($i < $count - 1) $classAttributeContent .= " "; + } + } + + if ($classAttributeContent != "") + { + echo(" class=\"" . $classAttributeContent . "\""); + } + + if ($this->ClientID != null) + { + echo(" id=\"" . $this->ClientID . "\""); + } + else if ($this->ID != null) + { + echo(" id=\"" . $this->ID . "\""); + } + + if ($this->ToolTipTitle != null) echo(" data-tooltip-title=\"" . $this->ToolTipTitle . "\""); + if ($this->ToolTipText != null) echo(" data-tooltip-content=\"" . $this->ToolTipText . "\""); + + if (!$this->DoesHaveContent()) echo(" /"); + echo(">"); + } + } + + private static $tagNamesThatMustHaveContent= array("script", "div", "i"); + private function DoesHaveContent() + { + $mustHaveContent = in_array(strtolower($this->TagName), WebControl::$tagNamesThatMustHaveContent); + return $this->HasContent || $mustHaveContent; + } + + /** + * Renders the ending tag of this WebControl. + */ + protected function RenderEndTag() + { + if ($this->TagName != "" && $this->DoesHaveContent()) + { + echo("TagName . ">"); + } + } + + /** + * Renders the beginning tag of this WebControl, followed by any leading content specified by + * the control author. + */ + public function BeginContent() + { + if ($this->EnableRender !== true) return; + + $this->RenderBeginTag(); + $this->BeforeContent(); + } + /** + * Renders any trailing content specified by the control author before the ending tag of this + * WebControl, followed by the ending tag itself. + */ + public function EndContent() + { + if ($this->EnableRender !== true) return; + + $this->AfterContent(); + $this->RenderEndTag(); + } + + /** + * Creates the child controls for this WebControl. + */ + protected function CreateControl() + { + } + + /** + * Renders this WebControl and any child controls. + */ + public function Render() + { + if ($this->EnableRender !== true) return; + + if (!$this->Initialized) $this->Initialize(); + + if ($this->Enabled !== true) + { + $this->Attributes[] = new WebControlAttribute("disabled", "disabled"); + } + $this->CreateControl(); + + $this->BeginContent(); + if (is_callable($this->Content)) + { + call_user_func($this->Content, $this, $this->ExtraData); + } + else if (is_string($this->Content)) + { + echo($this->Content); + } + else + { + if (count($this->Controls) > 0) + { + foreach ($this->Controls as $control) + { + $control->Render(); + } + } + else + { + $this->RenderContent(); + } + } + $this->EndContent(); + } + + + + /** + * Parses an XML representation of a WebControl MarkupElement + * @param MarkupElement $element + * @param PhastParser $parser + * @return WebPage + */ + public static function FromMarkup($element, $parser) + { + $ctl = new WebControl(); + + $attNamespacePath = $element->GetAttribute("NamespacePath"); + $attTagName = $element->GetAttribute("TagName"); + + $virtualTagPath = ""; + if ($attNamespacePath != null) $virtualTagPath .= $attNamespacePath->Value . "\\"; + if ($attTagName != null) $virtualTagPath .= $attTagName->Value; + if ($virtualTagPath != "") + { + $ctl->VirtualTagPath = $virtualTagPath; + } + + $references = array(); + $tagReferences = $element->GetElement("References"); + if ($tagReferences != null) + { + foreach ($tagReferences->Elements as $elem) + { + if (get_class($elem) != "UniversalEditor\\ObjectModels\\Markup\\MarkupTagElement") continue; + + $attTagPrefix = $elem->GetAttribute("TagPrefix"); + if ($attTagPrefix == null) continue; + + $attNamespacePath = $elem->GetAttribute("NamespacePath"); + if ($attNamespacePath == null) continue; + + $attNamespaceURL = $elem->GetAttribute("NamespaceURL"); + $namespaceURL = ""; + if ($attNamespaceURL != null) $namespaceURL = $attNamespaceURL->Value; + + $references[] = new WebNamespaceReference($attTagPrefix->Value, $attNamespacePath->Value, $namespaceURL); + } + } + foreach ($references as $reference) + { + ControlLoader::$Namespaces[$reference->TagPrefix] = $reference->NamespacePath; + } + + $tagContent = $element->GetElement("Content"); + if ($tagContent != null) + { + foreach ($tagContent->Elements as $elem) + { + ControlLoader::LoadControl($elem, $ctl); + } + } + + $attrCssClass = $element->GetAttribute("CssClass"); + if ($attrCssClass != null) + { + $ctl->ClassList[] = $attrCssClass->Value; + } + + /* + $attrCodeBehindClassName = $element->GetAttribute("CodeBehindClassName"); + if ($attrCodeBehindClassName != null) + { + $ctl->CodeBehindClassName = $attrCodeBehindClassName->Value; + + if (class_exists($page->CodeBehindClassName)) + { + $page->ClassReference = new $page->CodeBehindClassName(); + $page->ClassReference->Page = $page; + $page->IsPostback = ($_SERVER["REQUEST_METHOD"] == "POST"); + + if (method_exists($page->ClassReference, "OnClassLoaded")) + { + $page->ClassReference->OnClassLoaded(EventArgs::GetEmptyInstance()); + } + else + { + System::WriteErrorLog("Code-behind for '" . $page->CodeBehindClassName . "' does not define an 'OnClassLoaded' entry point"); + } + } + else + { + System::WriteErrorLog("Code-behind for '" . $page->CodeBehindClassName . "' not found"); + } + } + */ + return $ctl; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControlAttribute.inc.php b/app/lib/phast/WebControlAttribute.inc.php new file mode 100644 index 0000000..bb8682b --- /dev/null +++ b/app/lib/phast/WebControlAttribute.inc.php @@ -0,0 +1,15 @@ +Name = $name; + $this->Value = $value; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControlClientIDMode.inc.php b/app/lib/phast/WebControlClientIDMode.inc.php new file mode 100644 index 0000000..9ca7542 --- /dev/null +++ b/app/lib/phast/WebControlClientIDMode.inc.php @@ -0,0 +1,22 @@ + diff --git a/app/lib/phast/WebControls/ActionList.inc.php b/app/lib/phast/WebControls/ActionList.inc.php new file mode 100644 index 0000000..12ee18a --- /dev/null +++ b/app/lib/phast/WebControls/ActionList.inc.php @@ -0,0 +1,75 @@ +"); + foreach ($this->Items as $item) + { + if (get_class($item) == "Phast\\WebControls\\ActionListCommand") + { + echo("TargetURL != null) + { + echo(" href=\"" . $item->TargetURL . "\""); + } + else + { + echo(" href=\"#\""); + } + if ($item->TargetScript != null) + { + echo(" onclick=\"" . $item->TargetScript . "\""); + } + echo(">"); + if ($item->ImageURL != null) + { + echo("ImageURL) . "\" />"); + } + echo("" . $item->Title . ""); + echo("" . $item->Description . ""); + echo(""); + } + else if (get_class($item) == "Phast\\WebControls\\ActionListSeparator") + { + echo("
"); + } + } + echo(""); + } + } + + class ActionListItem + { + public $ID; + } + + class ActionListCommand extends ActionListItem + { + public $Title; + public $Description; + public $ImageURL; + public $TargetURL; + public $TargetScript; + + public function __construct($id, $title, $description = null, $imageURL = null, $targetURL = null, $targetScript = null) + { + $this->Title = $title; + $this->Description = $description; + $this->ImageURL = $imageURL; + $this->TargetURL = $targetURL; + $this->TargetScript = $targetScript; + } + } + + class ActionListSeparator extends ActionListItem + { + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/AdditionalDetailWidget.inc.php b/app/lib/phast/WebControls/AdditionalDetailWidget.inc.php new file mode 100644 index 0000000..370efc2 --- /dev/null +++ b/app/lib/phast/WebControls/AdditionalDetailWidget.inc.php @@ -0,0 +1,191 @@ +ClassTitle = ""; + $this->DisplayStyle = AdditionalDetailWidgetDisplayStyle::Ellipsis; + $this->MenuItemHeaderText = "Available Actions"; + $this->MenuItems = array(); + $this->ShowText = true; + $this->ShowURL = true; + } + + private function RenderMenuItem($mi) + { + if (get_class($mi) == "Phast\\WebControls\\MenuItemCommand") + { + echo("PostBackUrl == "") + { + echo("#"); + } + else + { + echo(System::ExpandRelativePath($mi->PostBackUrl)); + } + echo("\""); + if ($mi->OnClientClick != "") + { + echo(" onclick=\"" . $mi->OnClientClick . "\""); + } + echo(">"); + echo($mi->Title); + echo(""); + } + else if (get_class($mi) == "Phast\\WebControls\\MenuItemSeparator") + { + echo("
"); + } + } + + protected function OnInitialize() + { + $this->TagName = "div"; + $this->ClassList[] = "AdditionalDetailWidget"; + if ($this->ShowText) + { + $this->ClassList[] = "Text"; + } + switch ($this->DisplayStyle) + { + case AdditionalDetailWidgetDisplayStyle::Magnify: + { + $this->ClassList[] = "Magnify"; + break; + } + case AdditionalDetailWidgetDisplayStyle::Arrow: + { + $this->ClassList[] = "Arrow"; + break; + } + case AdditionalDetailWidgetDisplayStyle::Ellipsis: + { + $this->ClassList[] = "Ellipsis"; + break; + } + } + } + + protected function BeforeContent() + { + if ($this->ShowURL) + { + echo("TargetURL != "") + { + echo(System::ExpandRelativePath($this->TargetURL)); + } + else + { + echo("#"); + } + echo("\""); + + if ($this->TargetFrame != "") + { + echo(" target=\"" . $this->TargetFrame . "\""); + } + + echo(">"); + echo($this->Text); + echo(""); + } + else + { + echo ("" . $this->Text . ""); + } + + echo(" "); + + echo("
"); + + echo("
MenuItems) <= 0) + { + echo(" Empty"); + } + echo("\">"); + echo("
" . $this->MenuItemHeaderText . "
"); + + $divContent = new HTMLControl("div"); + $divContent->ClassList[] = "Content"; + $menu = new Menu(); + foreach ($this->MenuItems as $mi) + { + $menu->Items[] = $mi; + } + $divContent->Controls[] = $menu; + $divContent->Render(); + + echo("
"); + + echo("
"); + echo("
"); + if ($this->ClassTitle != "") echo("" . $this->ClassTitle . ""); + if ($this->Text != "") + { + echo(""); + echo("PostBackURL != "") + { + echo($this->PostBackURL); + } + else + { + echo("#"); + } + echo("\""); + + echo(">"); + echo($this->Text); + echo(""); + echo(""); + } + echo("
"); + echo("
"); + } + protected function AfterContent() + { + echo("
"); + echo("
"); + + echo("
"); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/Alert.inc.php b/app/lib/phast/WebControls/Alert.inc.php new file mode 100644 index 0000000..6ab6faf --- /dev/null +++ b/app/lib/phast/WebControls/Alert.inc.php @@ -0,0 +1,45 @@ +ClassList[] = "pwt-Alert"; + $this->TagName = "div"; + } + + protected function RenderBeginTag() + { + $ctls = $this->Controls; + $this->Controls = array(); + + $divTitle = new HTMLControl("div"); + $divTitle->ClassList[] = "Title"; + $divTitle->Content = $this->Title; + $this->Controls[] = $divTitle; + + $divContent = new HTMLControl("div"); + $divContent->ClassList[] = "Content"; + foreach ($ctls as $ctl) + { + $divContent->Controls[] = $ctl; + } + $this->Controls[] = $divContent; + + parent::RenderBeginTag(); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/Blockquote.phpx b/app/lib/phast/WebControls/Blockquote.phpx new file mode 100644 index 0000000..a519f06 --- /dev/null +++ b/app/lib/phast/WebControls/Blockquote.phpx @@ -0,0 +1,22 @@ + + + + + + + + + +
+

+ + , + +
+
+
+
+
\ No newline at end of file diff --git a/app/lib/phast/WebControls/BreadcrumbContainer.inc.php b/app/lib/phast/WebControls/BreadcrumbContainer.inc.php new file mode 100644 index 0000000..03493b0 --- /dev/null +++ b/app/lib/phast/WebControls/BreadcrumbContainer.inc.php @@ -0,0 +1,55 @@ +NavigateURL = $navigateURL; + $this->Title = $title; + } + } + class BreadcrumbContainer extends \Phast\WebControl + { + public $Items; + + // $bc->Items = array( + // new BreadcrumbItem("http://www.psychatica.com/", "Psychatica") + // new BreadcrumbItem("http://www.psychatica.com/community", "Community"), + // new BreadcrumbItem("http://www.psychatica.com/community/members", "Members", true) + // ); + + protected function RenderContent() + { + echo("
"); + + $i = 0; + $c = count($this->Items); + + foreach ($this->Items as $item) + { + echo(""); + if ($i == $c - 1) + { + echo("" . $item->Title . ""); + } + else + { + echo("NavigateURL) . "\">" . $item->Title . ""); + } + if ($i < $c - 1) + { + echo(">>"); + } + echo(""); + $i++; + } + echo("
"); + } + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/Button.inc.php b/app/lib/phast/WebControls/Button.inc.php new file mode 100644 index 0000000..1f5b51b --- /dev/null +++ b/app/lib/phast/WebControls/Button.inc.php @@ -0,0 +1,131 @@ +TagName = "div"; + $this->ClassList[] = "pwt-Button"; + + $this->DropDownControls = array(); + $this->DropDownRequired = false; + + $this->IconName = null; + $this->UseSubmitBehavior = false; + $this->ParseChildElements = true; + } + + protected function RenderBeginTag() + { + if ($this->UseSubmitBehavior) + { + $tag = new Input(); + + foreach ($this->ClassList as $className) + { + if ($className == "pwt-Button") continue; + $tag->ClassList[] = $className; + } + + $tag->ClassList = $this->ClassList; + $tag->ClassList[] = $this->CssClass; + $tag->Attributes[] = new WebControlAttribute("id", $this->ClientID); + $tag->Type = InputType::Submit; + $tag->Attributes[] = new WebControlAttribute("value", $this->Text); + + $this->Controls[] = $tag; + } + else + { + $tag = new Anchor(); + $tag->ClassList = $this->ClassList; + $tag->ClassList[] = $this->CssClass; + + if ($this->ClientID != null) + { + $tag->Attributes[] = new WebControlAttribute("id", $this->ClientID); + } + else + { + $tag->Attributes[] = new WebControlAttribute("id", $this->ID); + } + + $tag->TargetFrame = $this->TargetFrame; + $tag->TargetURL = $this->TargetURL; + $tag->TargetScript = $this->TargetScript; + + if ($this->IconName != null) + { + $i = new HTMLControl("i"); + $i->ClassList[] = "fa"; + $i->ClassList[] = "fa-" . $this->IconName; + $tag->Controls[] = $i; + } + + $spanText = new HTMLControl("span"); + $spanText->ClassList[] = "Text"; + $spanText->InnerHTML = $this->Text; + + $tag->Controls[] = $spanText; + + $this->Controls[] = $tag; + } + + if (count($this->DropDownControls) > 0) + { + $this->ClassList[] = "pwt-DropDownButton"; + } + if ($this->DropDownRequired) + { + $this->ClassList[] = "pwt-DropDownRequired"; + } + if ($this->DropDownDirection != null) + { + $this->Attributes[] = new WebControlAttribute("data-pwt-dropdown-direction", $this->DropDownDirection); + } + + $aDropDown = new Anchor(); + $aDropDown->ClassList[] = "pwt-Button pwt-DropDownButton"; + $aDropDown->InnerHTML = " "; + $this->Controls[] = $aDropDown; + + $divDropDown = new HTMLControl("div"); + $divDropDown->ClassList[] = "pwt-DropDownContent Popup"; + $divDropDown->Controls = $this->DropDownControls; + $this->Controls[] = $divDropDown; + + parent::RenderBeginTag(); + } + } +?> diff --git a/app/lib/phast/WebControls/ButtonGroup.inc.php b/app/lib/phast/WebControls/ButtonGroup.inc.php new file mode 100644 index 0000000..0f9e0d0 --- /dev/null +++ b/app/lib/phast/WebControls/ButtonGroup.inc.php @@ -0,0 +1,211 @@ +Orientation = ButtonGroupOrientation::Horizontal; + $this->ParseChildElements = true; + } + + protected function RenderContent() + { + ?> +
" style="HorizontalAlignment) + { + case "Left": + case HorizontalAlignment::Left: + { + echo("text-align: left;"); + break; + } + case "Center": + case HorizontalAlignment::Center: + { + echo("text-align: center;"); + break; + } + case "Right": + case HorizontalAlignment::Right: + { + echo("text-align: right;"); + break; + } + }?>"> + ButtonSize)) + { + $buttonWidth = $this->ButtonSize + 32; + $buttonHeight = $this->ButtonSize + 32; + $buttonActualWidth = $this->ButtonSize; + $buttonActualHeight = $this->ButtonSize; + } + else + { + if (is_numeric($this->ButtonWidth)) + { + $buttonWidth = $this->ButtonWidth + 32; + $buttonActualWidth = $this->ButtonWidth; + } + if (is_numeric($this->ButtonHeight)) + { + $buttonHeight = $this->ButtonHeight + 32; + $buttonActualHeight = $this->ButtonHeight; + } + } + + if (is_array($this->Items)) + { + foreach ($this->Items as $item) + { + ?> + NavigationURL != null) { echo (" href=\"" . System::ExpandRelativePath($item->NavigationURL) . "\""); } if ($item->OnClientClick != null) { echo (" onclick=\"" . $item->OnClientClick . "\""); } echo (" style=\"width: " . $buttonWidth . "px; height: " . $buttonHeight . "px; visibility: " . ($item->Visible ? "visible" : "hidden") . ";\""); ?>> + ImagePosition == ButtonGroupImagePosition::AboveText) + { + ?> + " /> + + Title); ?> + ImagePosition == ButtonGroupImagePosition::BelowText) + { + ?> + " /> + + +
Name = $name; + $this->Title = $title; + $this->Description = $description; + $this->ImageURL = $imageURL; + $this->NavigationURL = $navigationURL; + $this->OnClientClick = $onClientClick; + $this->ImagePosition = ButtonGroupImagePosition::AboveText; + $this->AspectRatioPreservationMode = ButtonGroupButtonAspectRatioPreservationMode::FitHeight; + $this->Visible = true; + } + } + class ButtonGroupSeparator extends ButtonGroupItem + { + } + class ButtonGroupLineBreak extends ButtonGroupItem + { + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/CommandBar.inc.php b/app/lib/phast/WebControls/CommandBar.inc.php new file mode 100644 index 0000000..16ec605 --- /dev/null +++ b/app/lib/phast/WebControls/CommandBar.inc.php @@ -0,0 +1,81 @@ +TagName = "div"; + $this->ClassList[] = "CommandBar"; + } + + protected function RenderContent() + { + foreach ($this->Items as $item) + { + $item->Render(); + } + } + } + + class CommandBar + { + public $ID; + public $Title; + } + + class CommandBarItem + { + public $ID; + + public function Render() + { + $this->RenderContent(); + } + protected function RenderContent() + { + } + } + class CommandBarItemButton extends CommandBarItem + { + public $Title; + public $ImageURL; + public $TargetURL; + public $TargetScript; + + public $Items; + + public function __construct() + { + $this->Items = array(); + } + + protected function RenderContent() + { + echo("
"); + echo("" . $this->Title . ""); + echo("
"); + foreach ($this->Items as $item) + { + $item->Render(); + } + echo("
"); + echo("
"); + } + } + + class CommandBarItemSeparator extends CommandBarItem + { + protected function RenderContent() + { + echo("
"); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/Container.inc.php b/app/lib/phast/WebControls/Container.inc.php new file mode 100644 index 0000000..ce277dd --- /dev/null +++ b/app/lib/phast/WebControls/Container.inc.php @@ -0,0 +1,20 @@ +ParseChildElements = false; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/Countdown.inc.php b/app/lib/phast/WebControls/Countdown.inc.php new file mode 100644 index 0000000..df87e3a --- /dev/null +++ b/app/lib/phast/WebControls/Countdown.inc.php @@ -0,0 +1,88 @@ +TagName = "div"; + $this->ClassList[] = "Countdown"; + } + + protected function RenderBeginTag() + { + $year = 2015; + $month = 4; + $day = 25; + $hour = 0; + $minute = 0; + $second = 0; + + $this->Attributes[] = new WebControlAttribute("data-target-year", $year); + $this->Attributes[] = new WebControlAttribute("data-target-month", $month); + $this->Attributes[] = new WebControlAttribute("data-target-day", $day); + $this->Attributes[] = new WebControlAttribute("data-target-hour", $hour); + $this->Attributes[] = new WebControlAttribute("data-target-minute", $minute); + $this->Attributes[] = new WebControlAttribute("data-target-second", $second); + + $this->HasContent = true; + + // quickly generate 6 child controls, one each for Year, Month, Day, Hour, Minute, Second + for ($i = 0; $i < 6; $i++) + { + $div = new HTMLControl("div"); + $div->ClassList[] = "Segment"; + + $divContent = new HTMLControl("div"); + $divContent->ClassList[] = "Content"; + $divContent->InnerHTML = "0"; + $div->Controls[] = $divContent; + + $divTitle = new HTMLControl("div"); + $divTitle->ClassList[] = "Title"; + switch ($i) + { + case 0: + { + $divTitle->InnerHTML = "Years"; + break; + } + case 1: + { + $divTitle->InnerHTML = "Months"; + break; + } + case 2: + { + $divTitle->InnerHTML = "Days"; + break; + } + case 3: + { + $divTitle->InnerHTML = "Hours"; + break; + } + case 4: + { + $divTitle->InnerHTML = "Minutes"; + break; + } + case 5: + { + $divTitle->InnerHTML = "Seconds"; + break; + } + } + $div->Controls[] = $divTitle; + + $this->Controls[] = $div; + } + parent::RenderBeginTag(); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/Disclosure.inc.php b/app/lib/phast/WebControls/Disclosure.inc.php new file mode 100644 index 0000000..ae43c3f --- /dev/null +++ b/app/lib/phast/WebControls/Disclosure.inc.php @@ -0,0 +1,51 @@ +TagName = "div"; + $this->ClassList[] = "Disclosure"; + } + + protected function OnInitialize() + { + $parent = $this->FindParentPage(); + if ($parent != null) $parent->Scripts[] = new WebScript("$(PhastStaticPath)/Scripts/Controls/Disclosure.js"); + } + + protected function RenderBeginTag() + { + if ($this->Expanded) + { + $this->ClassList[] = "Expanded"; + $this->Attributes[] = new WebControlAttribute("data-expanded", "true"); + } + else + { + $this->Attributes[] = new WebControlAttribute("data-expanded", "false"); + } + parent::RenderBeginTag(); + } + + protected function BeforeContent() + { + echo("
" . $this->Title . "
"); + echo("
"); + } + protected function AfterContent() + { + echo("
"); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/FlyoutTabStrip.inc.php b/app/lib/phast/WebControls/FlyoutTabStrip.inc.php new file mode 100644 index 0000000..bd27c68 --- /dev/null +++ b/app/lib/phast/WebControls/FlyoutTabStrip.inc.php @@ -0,0 +1,115 @@ +ID = $id; + $this->Title = $title; + $this->ImageURL = $imageURL; + if ($contentOrFunction != null) + { + if (is_callable($contentOrFunction)) + { + $this->Content = null; + $this->RenderContent = $contentOrFunction; + } + else + { + $this->Content = $contentOrFunction; + $this->RenderContent = null; + } + } + } + } + class FlyoutTabStrip extends WebControl + { + public $Items; + public $Position; + + public function __construct($id) + { + parent::__construct($id); + $this->Position = FlyoutTabStripPosition::Right; + } + + protected function RenderContent() + { + ?> +
"> +
+ Items as $item) + { + ?> +
<?php echo($item->Title); ?>
+ +
+
+ Items as $item) + { + ?>
RenderContent != null && is_callable($item->RenderContent)) + { + call_user_func($item->RenderContent); + } + else if ($item->Content != null) + { + echo($item->Content); + } + ?>
+ +
+
+ + \ No newline at end of file diff --git a/app/lib/phast/WebControls/FormView.inc.php b/app/lib/phast/WebControls/FormView.inc.php new file mode 100644 index 0000000..40d9514 --- /dev/null +++ b/app/lib/phast/WebControls/FormView.inc.php @@ -0,0 +1,446 @@ + element beside the form element. + * @var FormViewLabelStyle + */ + const Label = 1; + /** + * The labels for FormView items are rendered in-place where possible. + * @var FormViewLabelStyle + */ + const Placeholder = 2; + } + + class FormView extends \Phast\WebControl + { + /** + * The style of the labels applied to FormView items. + * @var FormViewLabelStyle + */ + public $LabelStyle; + /** + * Array of FormViewItems contained within this FormView. + * @var FormViewItem[] + */ + public $Items; + + public function GetItemByID($id) + { + foreach ($this->Items as $item) + { + if ($item->ID == $id) return $item; + } + return null; + } + + public function __construct() + { + parent::__construct(); + $this->ParseChildElements = true; + $this->TagName = "div"; + $this->ClassList[] = "FormView"; + } + + protected function RenderContent() + { + foreach ($this->Items as $item) + { + $div = new HTMLControl("div"); + $div->ID = "FormView_" . $this->ID . "_" . $item->ID; + $div->ClassList[] = "Field"; + if ($item->Required) $div->ClassList[] = "Required"; + + if ($item->GenerateLabel) + { + $title = $item->Title; + $i = stripos($title, "_"); + $char = null; + if ($i !== FALSE) + { + $before = substr($title, 0, $i); + $after = substr($title, $i + 1); + $char = substr($after, 0, 1); + $title = $before . "" . $char . "" . substr($after, 1); + } + + $lbl = new HTMLControl("label"); + $lbl->Attributes[] = new WebControlAttribute("for", $item->GetInputElementID()); + if ($char !== null) + { + $lbl->Attributes[] = new WebControlAttribute("accesskey", $char); + echo(" accesskey=\"" . $char . "\""); + } + + if ($item->IconName != "") + { + $i = new HTMLControl("i"); + $i->ClassList[] = "fa"; + $i->ClassList[] = "fa-" . $item->IconName; + $lbl->Controls[] = $i; + } + + $spanText = new HTMLControl("span"); + $spanText->ClassList[] = "Text"; + $spanText->InnerHTML = $title; + + $lbl->Controls[] = $spanText; + + $div->Controls[] = $lbl; + } + + $ctl = $item->CreateControl(); + if ($ctl != null) $div->Controls[] = $ctl; + $div->Render(); + } + } + } + + abstract class FormViewItem + { + public $ID; + public $Name; + public $Title; + public $DefaultValue; + public $Value; + public $Required; + + public $IconName; + + public $GenerateLabel; + public $ReadOnly; + + public function GetInputElementID() + { + return $this->ID; + } + + /** + * The client-side script called when the value of this FormViewItem changed and validated. + * @var string + */ + public $OnClientValueChanged; + + public function __construct($id = null, $name = null, $title = null, $defaultValue = null) + { + $this->ID = $id; + + if ($name == null) $name = $id; + $this->Name = $name; + + if ($title == null) $title = $name; + $this->Title = $title; + + $this->DefaultValue = $defaultValue; + $this->Required = false; + + $this->ParseChildElements = false; + $this->GenerateLabel = true; + $this->ReadOnly = false; + } + + public function CreateControl() + { + return $this->CreateControlInternal(); + } + + protected abstract function CreateControlInternal(); + } + class FormViewItemSeparator extends FormViewItem + { + public function __construct($id = null, $title = null) + { + parent::__construct($id, $id, $title); + $this->GenerateLabel = false; + } + + protected function CreateControlInternal() + { + $divSeparator = new HTMLControl("div"); + $divSeparator->ClassList[] = "Separator"; + + $divTitle = new HTMLControl("div"); + $divTitle->ClassList[] = "Title"; + $divTitle->InnerHTML = $this->Title; + + $divSeparator->Controls[] = $divTitle; + return $divSeparator; + } + } + class FormViewItemLabel extends FormViewItem + { + /** + * Creates a new Label FormViewItem with the given parameters. + * @param string $id The control ID for the FormViewItem. + * @param string $name The name of the form field to associate with the FormViewItem. + * @param string $title The title of the FormViewItem. + * @param string $defaultValue The default value of the FormViewItem. + */ + public function __construct($id = null, $name = null, $title = null, $defaultValue = null) + { + parent::__construct($id, $name, $title, $defaultValue); + } + + protected function CreateControlInternal() + { + $elem = new HTMLControl("div"); + $elem->ID = $this->ID; + $elem->Name = $this->Name; + $elem->InnerHTML = $this->DefaultValue; + if (isset($this->Value)) + { + if (is_string($this->Value)) + { + $elem->InnerHTML = System::ExpandRelativePath($this->Value); + } + } + + return $elem; + } + } + class FormViewItemText extends FormViewItem + { + /** + * Text that is displayed in the textbox of the FormViewItem when the user has not entered a value. + * @var string + */ + public $PlaceholderText; + + /** + * Creates a new Text FormViewItem with the given parameters. + * @param string $id The control ID for the FormViewItem. + * @param string $name The name of the form field to associate with the FormViewItem. + * @param string $title The title of the FormViewItem. + * @param string $defaultValue The default value of the FormViewItem. + */ + public function __construct($id = null, $name = null, $title = null, $defaultValue = null) + { + parent::__construct($id, $name, $title, $defaultValue); + } + + protected function CreateControlInternal() + { + $elem = new Input(); + $elem->ID = $this->ID; + $elem->Type = InputType::Text; + $elem->Name = $this->Name; + $elem->Value = System::ExpandRelativePath($this->DefaultValue); + if (isset($this->Value)) $elem->Value = System::ExpandRelativePath($this->Value); + + if (isset($this->PlaceholderText)) + { + $elem->PlaceholderText = $this->PlaceholderText; + } + if ($this->OnClientValueChanged != null) + { + $elem->Attributes[] = new WebControlAttribute("onchange", $this->OnClientValueChanged); + } + return $elem; + } + } + class FormViewItemPassword extends FormViewItemText + { + public function __construct($id = null, $name = null, $title = null, $defaultValue = null) + { + parent::__construct($id, $name, $title, $defaultValue); + } + + protected function CreateControlInternal() + { + $elem = new Input(); + $elem->ID = $this->ID; + $elem->Type = InputType::Password; + $elem->Name = $this->Name; + $elem->Value = System::ExpandRelativePath($this->DefaultValue); + if (isset($this->Value)) $elem->Value = System::ExpandRelativePath($this->Value); + if (isset($this->PlaceholderText)) + { + $elem->PlaceholderText = $this->PlaceholderText; + } + return $elem; + } + } + class FormViewItemMemo extends FormViewItemText + { + public $Rows; + public $Columns; + public $PlaceholderText; + + public function __construct($id = null, $name = null, $title = null, $defaultValue = null) + { + parent::__construct($id, $name, $title, $defaultValue); + } + + protected function CreateControlInternal() + { + $elem = new HTMLControlTextArea(); + $elem->ID = $this->ID; + $elem->Name = $this->Name; + if (isset($this->Rows)) $elem->Rows = $this->Rows; + if (isset($this->Columns)) $elem->Columns = $this->Columns; + $elem->Value = System::ExpandRelativePath($this->DefaultValue); + if (isset($this->Value)) $elem->Value = System::ExpandRelativePath($this->Value); + if (isset($this->PlaceholderText)) $elem->PlaceholderText = $this->PlaceholderText; + return $elem; + } + } + class FormViewItemNumber extends FormViewItemText + { + public $MinimumValue; + public $MaximumValue; + + /** + * Creates a new Number FormViewItem with the given parameters. + * @param string $id The control ID for the FormViewItem. + * @param string $name The name of the form field to associate with the FormViewItem. + * @param string $title The title of the FormViewItem. + * @param string $defaultValue The default value of the FormViewItem. + */ + public function __construct($id = null, $name = null, $title = null, $defaultValue = null) + { + parent::__construct($id, $name, $title, $defaultValue); + } + + protected function CreateControlInternal() + { + $elem = new Input(); + $elem->ID = $this->ID; + $elem->Type = InputType::Number; + $elem->Name = $this->Name; + if ($this->MaximumValue != null) + { + $elem->Attributes[] = new WebControlAttribute("max", $this->MaximumValue); + } + if ($this->MinimumValue != null) + { + $elem->Attributes[] = new WebControlAttribute("min", $this->MinimumValue); + } + $elem->Value = System::ExpandRelativePath($this->DefaultValue); + if (isset($this->Value)) $elem->Value = System::ExpandRelativePath($this->Value); + + if (isset($this->PlaceholderText)) + { + $elem->PlaceholderText = $this->PlaceholderText; + } + if ($this->OnClientValueChanged != null) + { + $elem->Attributes[] = new WebControlAttribute("onchange", $this->OnClientValueChanged); + } + return $elem; + } + } + class FormViewItemBoolean extends FormViewItem + { + public function __construct($id = null, $name = null, $title = null, $defaultValue = null) + { + parent::__construct($id, $name, $title, $defaultValue); + } + + protected function CreateControlInternal() + { + $elem = new Input(); + $elem->ID = $this->ID; + $elem->Type = InputType::CheckBox; + $elem->Name = $this->Name; + if ($this->DefaultValue) + { + $elem->Attributes[] = new WebControlAttribute("checked", "checked"); + } + return $elem; + } + } + class FormViewItemChoice extends FormViewItem + { + public $EnableMultipleSelection; + public $RequireSelectionFromChoices; + + /** + * The items available for selection. + * @var FormViewItemChoiceValue[] + */ + public $Items; + + public function __construct($id = null, $name = null, $title = null, $defaultValue = null, $items = null) + { + parent::__construct($id, $name, $title, $defaultValue); + if (is_array($items)) + { + $this->Items = $items; + } + else + { + $this->Items = array(); + } + $this->ParseChildElements = true; + } + + public function GetInputElementID() + { + return $this->ID . "_InputElement"; + } + + protected function CreateControlInternal() + { + $elem = new TextBox(); + $elem->ID = $this->ID; + $elem->Name = $this->Name; + $elem->EnableMultipleSelection = $this->EnableMultipleSelection; + $elem->RequireSelectionFromChoices = $this->RequireSelectionFromChoices; + $elem->ClearOnFocus = $elem->RequireSelectionFromChoices; + foreach ($this->Items as $item) + { + $elem->Items[] = new TextBoxItem($item->Title, System::ExpandRelativePath($item->Value), $item->Selected); + } + return $elem; + } + } + class FormViewItemChoiceValue + { + public $Title; + public $Value; + public $Selected; + + public function __construct($title = null, $value = null, $selected = false) + { + $this->Title = $title; + $this->Value = $value; + $this->Selected = $selected; + } + } + class FormViewItemDateTime extends FormViewItem + { + public $Nullable; + public $NullableOptionText; + + public function __construct($id = null, $name = null, $title = null, $defaultValue = null, $nullable = null) + { + parent::__construct($id, $name, $title, $defaultValue); + $this->Nullable = $nullable; + } + + protected function CreateControlInternal() + { + $elem = new Input(); + $elem->ID = $this->ID; + $elem->Type = InputType::Text; + $elem->Name = $this->Name; + return $elem; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/ImageGallery.inc.php b/app/lib/phast/WebControls/ImageGallery.inc.php new file mode 100644 index 0000000..4138054 --- /dev/null +++ b/app/lib/phast/WebControls/ImageGallery.inc.php @@ -0,0 +1,26 @@ + +
+
+   +
 
+   +
+
+ + + + +
+
+ + \ No newline at end of file diff --git a/app/lib/phast/WebControls/ListView.inc.php b/app/lib/phast/WebControls/ListView.inc.php new file mode 100644 index 0000000..87e9fe3 --- /dev/null +++ b/app/lib/phast/WebControls/ListView.inc.php @@ -0,0 +1,406 @@ +Checked = $checked; + } + } + class ListViewColumn + { + public $ID; + public $Title; + public $ImageURL; + /** + * True if this ListViewColumn should be hidden on mobile devices; false if it should be displayed. + * @var boolean + */ + public $MobileHidden; + public $Visible; + public $Width; + + public $Template; + + public function __construct($id = null, $title = null, $imageURL = null, $width = null) + { + $this->ID = $id; + $this->Title = $title; + $this->ImageURL = $imageURL; + $this->MobileHidden = false; + $this->Visible = true; + $this->Width = $width; + + $this->ParseChildElements = false; + } + } + class ListViewItem + { + public $ID; + public $Columns; + public $Checked; + public $Selected; + public $NavigateURL; + public $OnClientClick; + + public $Value; + + public function GetColumnByID($id) + { + foreach ($this->Columns as $column) + { + if ($column->ID == $id) return $column; + } + return null; + } + + public function __construct($columns = null, $selected = false) + { + if ($columns != null) + { + $this->Columns = $columns; + } + $this->Selected = $selected; + $this->ParseChildElements = true; + } + } + class ListViewItemColumn + { + public $ID; + public $Text; + public $Content; + public $UserData; + + public $ParseChildElements; + + public function __construct($id = null, $content = null, $text = null, $userData = null) + { + $this->ID = $id; + $this->Content = $content; + if ($text == null) $text = $content; + $this->Text = $text; + $this->UserData = $userData; + + $this->ParseChildElements = true; + } + } + class ListView extends WebControl + { + public $AllowFiltering; + + public $EnableAddRemoveRows; + public $EnableMultipleSelection; + + /** + * The columns on this ListView. + * @var ListViewColumn[] + */ + public $Columns; + /** + * The items on this ListView. + * @var ListViewItem[] + */ + public $Items; + + public $EnableHotTracking; + public $ShowBorder; + public $ShowGridLines; + public $HighlightAlternateRows; + + public $EnableRowCheckBoxes; + public $PlaceholderText; + + public $Mode; + + public $OnItemActivate; + + public function GetColumnByID($id) + { + foreach ($this->Columns as $column) + { + if ($column->ID == $id) return $column; + } + return null; + } + + public function __construct() + { + parent::__construct(); + $this->Columns = array(); + $this->Items = array(); + $this->AllowFiltering = true; + $this->Mode = ListViewMode::Detail; + + $this->ShowBorder = true; + $this->ShowGridLines = true; + $this->HighlightAlternateRows = true; + $this->EnableAddRemoveRows = false; + $this->EnableHotTracking = true; + $this->EnableMultipleSelection = false; + + $this->ParseChildElements = true; + $this->PlaceholderText = "There are no items"; + } + + protected function OnInitialize() + { + $parent = $this->FindParentPage(); + if ($parent != null) $parent->Scripts[] = new WebScript("$(System.StaticPath)/Scripts/Controls/ListView.js"); + } + + protected function RenderContent() + { + $div = new HTMLControl("div"); + $div->ClassList[] = "ListView"; + if (count($this->Items) <= 0) $div->ClassList[] = "Empty"; + + foreach ($this->ClassList as $str) + { + $div->ClassList[] = $str; + } + foreach ($this->Attributes as $att) + { + $div->Attributes[] = $att; + } + + $div->ID = $this->ID; + + // set up CSS classes for properties + if ($this->ShowBorder) $div->ClassList[] = "HasBorder"; + if ($this->EnableHotTracking) $div->ClassList[] = "HotTracking"; + if ($this->ShowGridLines) $div->ClassList[] = "GridLines"; + if ($this->HighlightAlternateRows) $div->ClassList[] = "AlternateRowHighlight"; + if ($this->AllowFiltering) $div->ClassList[] = "AllowFiltering"; + if ($this->EnableRowCheckBoxes) $div->ClassList[] = "RowCheckBoxes"; + if ($this->EnableMultipleSelection) $div->ClassList[] = "MultiSelect"; + if ($this->EnableAddRemoveRows) $div->ClassList[] = "EnableAddRemoveRows"; + if ($this->Width != null) $div->StyleRules[] = new WebStyleSheetRule("width", $this->Width); + + switch ($this->Mode) + { + case "Detail": + case ListViewMode::Detail: + { + $div->Attributes[] = new WebControlAttribute("data-mode", "Detail"); + break; + } + case "Thumbnail": + case ListViewMode::Thumbnail: + { + $div->Attributes[] = new WebControlAttribute("data-mode", "Thumbnail"); + break; + } + case "Icon": + case ListViewMode::Icon: + { + $div->Attributes[] = new WebControlAttribute("data-mode", "Icon"); + break; + } + case "Tile": + case ListViewMode::Tile: + { + $div->Attributes[] = new WebControlAttribute("data-mode", "Tile"); + break; + } + } + + $divColumnHeaders = new HTMLControl("div"); + $divColumnHeaders->ClassList[] = "ListViewColumnHeaders"; + + $lvcCount = 0; + + $divItemColumn = new HTMLControl("div"); + $divItemColumn->ClassList[] = "ListViewColumnHeader"; + $divItemColumn->ClassList[] = "AddRemoveRowColumnHeader"; + + $aAdd = new HTMLControl("a"); + $aAdd->ClassList[] = "Add"; + $divItemColumn->Controls[] = $aAdd; + + $divColumnHeaders->Controls[] = $divItemColumn; + $lvcCount++; + + $count = count($this->Columns); + for ($i = 0; $i < $count; $i++) + { + $column = $this->Columns[$i]; + $divColumnHeader = new HTMLControl("div"); + $divColumnHeader->Attributes[] = new WebControlAttribute("data-id", $column->ID); + $divColumnHeader->ClassList[] = "ListViewColumnHeader"; + + if ($column->Width != null) + { + $divColumnHeader->StyleRules[] = new WebStyleSheetRule("width", $column->Width); + } + $classList = array(); + if ($column->MobileHidden) $divColumnHeader->ClassList[] = "MobileHidden"; + + if (get_class($column) == "Phast\\WebControls\\ListViewColumnCheckBox") + { + $input = new Input(); + $input->Type = InputType::CheckBox; + $divColumnHeader->Controls[] = $input; + } + else if (get_class($column) == "Phast\\WebControls\\ListViewColumn") + { + $link = new Anchor(); + $link->TargetScript = "lvListView.Sort('" . $column->ID . "'); return false;"; + $link->InnerHTML = $column->Title; + $divColumnHeader->Controls[] = $link; + } + else + { + $literal = new Literal(); + $literal->Value = ""; + $divColumnHeader->Controls[] = $literal; + } + + if (!$column->Visible) + { + $divColumnHeader->ClassList[] = "Hidden"; + } + else + { + $lvcCount++; + } + + if ($lvcCount == 1) + { + $divColumnHeader->ClassList[] = "FirstVisibleChild"; + } + else if ($lvcCount == count($this->Columns)) + { + $divColumnHeader->ClassList[] = "LastVisibleChild"; + } + + $divItemTemplate = new HTMLControl("div"); + $divItemTemplate->ClassList[] = "ItemTemplate"; + $divItemTemplate->Content = $column->Template; + $divColumnHeader->Controls[] = $divItemTemplate; + + $divColumnHeaders->Controls[] = $divColumnHeader; + + if ($i < $count - 1) + { + $divColumnResizer = new HTMLControl("div"); + $divColumnResizer->ClassList[] = "ColumnResizer"; + $divColumnHeaders->Controls[] = $divColumnResizer; + } + } + + $div->Controls[] = $divColumnHeaders; + + $divEmptyMessage = new HTMLControl("div"); + $divEmptyMessage->ClassList[] = "ListViewEmptyMessage"; + $divEmptyMessage->InnerHTML = $this->PlaceholderText; + $div->Controls[] = $divEmptyMessage; + + $divItems = new HTMLControl("div"); + $divItems->ClassList[] = "ListViewItems"; + + foreach ($this->Items as $item) + { + $divItem = new HTMLControl("div"); + $divItem->ClassList[] = "ListViewItem"; + if ($item->Value != null) + { + $divItem->Attributes[] = new WebControlAttribute("data-value", $item->Value); + } + + $count = count($this->Columns); + + $lvcCount = 0; + + $divItemColumn = new HTMLControl("div"); + $divItemColumn->ClassList[] = "ListViewItemColumn"; + $divItemColumn->ClassList[] = "AddRemoveRowItemColumn"; + + $aAdd = new HTMLControl("a"); + $aAdd->ClassList[] = "Add"; + $divItemColumn->Controls[] = $aAdd; + + $aRemove = new HTMLControl("a"); + $aRemove->ClassList[] = "Remove"; + $divItemColumn->Controls[] = $aRemove; + + $divItem->Controls[] = $divItemColumn; + $lvcCount++; + + for ($i = 0; $i < $count; $i++) + { + $column = $this->Columns[$i]; + $divItemColumn = new HTMLControl("div"); + + if (!$column->Visible) + { + $divItemColumn->ClassList[] = "Hidden"; + } + else + { + $lvcCount++; + } + + if ($lvcCount == 1) + { + $divItemColumn->ClassList[] = "FirstVisibleChild"; + } + else if ($lvcCount == $max) + { + $divItemColumn->ClassList[] = "LastVisibleChild"; + } + + $divItemColumn->ClassList[] = "ListViewItemColumn"; + + $col = $item->GetColumnByID($this->Columns[$i]->ID); + if ($col != null) + { + $divItemColumn->ExtraData = $col->UserData; + $divItemColumn->Content = $col->Content; + } + $divItem->Controls[] = $divItemColumn; + + if ($i < $count - 1) + { + $divColumnResizer = new HTMLControl("div"); + $divColumnResizer->ClassList[] = "ColumnResizer"; + $divItem->Controls[] = $divColumnResizer; + } + } + + $divItems->Controls[] = $divItem; + } + $div->Controls[] = $divItems; + $div->Render(); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/Menu.inc.php b/app/lib/phast/WebControls/Menu.inc.php new file mode 100644 index 0000000..4b63127 --- /dev/null +++ b/app/lib/phast/WebControls/Menu.inc.php @@ -0,0 +1,273 @@ +Items = array(); + $this->ParseChildElements = true; + + $this->TagName = "ul"; + $this->ClassList[] = "Menu"; + + if ($this->Top != null) $this->StyleRules[] = new WebStyleSheetRule("top", $this->Top); + if ($this->Left != null) $this->StyleRules[] = new WebStyleSheetRule("left", $this->Left); + if ($this->Width != null) $this->StyleRules[] = new WebStyleSheetRule("width", $this->Width); + if ($this->Height != null) $this->StyleRules[] = new WebStyleSheetRule("height", $this->Height); + if ($this->MaximumWidth != null) $this->StyleRules[] = new WebStyleSheetRule("max-width", $this->MaximumWidth); + if ($this->MaximumHeight != null) $this->StyleRules[] = new WebStyleSheetRule("max-height", $this->MaximumHeight); + } + + public function GetItemByID($id) + { + foreach ($this->Items as $item) + { + if ($item->ID == $id) return $item; + } + return null; + } + + protected function RenderBeginTag() + { + if ($this->Orientation == "Horizontal" || $this->Orientation == MenuOrientation::Horizontal) + { + $this->ClassList[] = "Horizontal"; + } + else if ($this->Orientation == "Vertical" || $this->Orientation == MenuOrientation::Vertical) + { + $this->ClassList[] = "Vertical"; + } + + $this->Controls = array(); + + $liArrow = new HTMLControl("li"); + $liArrow->ClassList[] = "Arrow"; + $this->Controls[] = $liArrow; + + foreach ($this->Items as $menuItem) + { + $this->Controls[] = Menu::CreateMenuItemControl($menuItem); + } + parent::RenderBeginTag(); + } + + public static function CreateMenuItemControl($menuItem) + { + if (get_class($menuItem) == "Phast\\WebControls\\MenuItemCommand") + { + $li = new HTMLControl(); + $li->Attributes = $menuItem->Attributes; + $li->TagName = "li"; + $li->ClassList[] = "Command"; + + if ($menuItem->Visible) + { + $li->ClassList[] = "Visible"; + } + if ($menuItem->Selected) + { + $li->ClassList[] = "Selected"; + } + if (count($menuItem->Items) > 0) + { + $li->ClassList[] = "HasChildren"; + } + + $a = new Anchor(); + $a->TargetURL = $menuItem->TargetURL; + if ($menuItem->OnClientClick != null) + { + $a->Attributes[] = new WebControlAttribute("onclick", $menuItem->OnClientClick); + } + + if ($menuItem->IconName != "") + { + $iIcon = new HTMLControl(); + $iIcon->TagName = "i"; + $iIcon->ClassList[] = "fa"; + $iIcon->ClassList[] = "fa-" . $menuItem->IconName; + $a->Controls[] = $iIcon; + } + + if ($menuItem->Description != null) + { + $spanTitle = new HTMLControl(); + $spanTitle->TagName = "span"; + $spanTitle->ClassList[] = "Title"; + $spanTitle->InnerHTML = $menuItem->Title; + $a->Controls[] = $spanTitle; + + $spanDescription = new HTMLControl(); + $spanDescription->TagName = "span"; + $spanDescription->ClassList[] = "Description"; + $spanDescription->InnerHTML = $menuItem->Description; + $a->Controls[] = $spanDescription; + } + else + { + $spanTitle = new HTMLControl(); + $spanTitle->TagName = "span"; + $spanTitle->ClassList[] = "Title NoDescription"; + $spanTitle->InnerHTML = $menuItem->Title; + $a->Controls[] = $spanTitle; + } + + $li->Controls[] = $a; + + if (count($menuItem->Items) > 0) + { + $menu = new Menu(); + foreach ($menuItem->Items as $item1) + { + $menu->Items[] = $item1; + } + $li->Controls[] = $menu; + } + return $li; + } + else if (get_class($menuItem) == "Phast\\WebControls\\MenuItemHeader") + { + $li = new HTMLControl(); + $li->Attributes = $menuItem->Attributes; + $li->TagName = "li"; + if ($menuItem->Visible) + { + $li->ClassList[] = "Visible"; + } + $li->ClassList[] = "Header"; + + $spanHeader = new HTMLControl(); + $spanHeader->TagName = "span"; + $spanHeader->ClassList[] = "Text"; + $spanHeader->InnerHTML = $menuItem->Title; + $li->Controls[] = $spanHeader; + + return $li; + } + else if (get_class($menuItem) == "Phast\\WebControls\\MenuItemSeparator") + { + $hr = new HTMLControl(); + $hr->Attributes = $menuItem->Attributes; + if ($menuItem->Visible) + { + $hr->ClassList[] = "Visible"; + } + $hr->TagName = "hr"; + return $hr; + } + else + { + System::WriteErrorLog("Unknown MenuItem class: " . get_class($menuItem)); + } + } + } + + class MenuItem extends WebControl + { + public $Visible; + + public function __construct() + { + $this->ParseChildElements = true; + $this->Visible = true; + } + } + class MenuItemHeader extends MenuItem + { + public $Title; + public $Subtitle; + public function __construct($title = null, $subtitle = null) + { + parent::__construct(); + $this->Title = $title; + $this->Subtitle = $subtitle; + } + } + class MenuItemCommand extends MenuItem + { + public $Items; + public $IconName; + public $Title; + public $TargetURL; + public $OnClientClick; + public $Selected; + public $Description; + + public function GetItemByID($id) + { + foreach ($this->Items as $item) + { + if ($item->ID == $id) return $item; + } + return null; + } + + public function __construct($title = null, $targetURL = null, $onClientClick = null, $description = null, $items = null) + { + parent::__construct(); + $this->Title = $title; + $this->TargetURL = $targetURL; + $this->OnClientClick = $onClientClick; + $this->Description = $description; + if ($items == null) $items = array(); + $this->Items = $items; + } + } + class MenuItemSeparator extends MenuItem + { + } + class MenuItemMenu extends MenuItem + { + public $Title; + public $Items; + + public function __construct($title, $menuItems = array()) + { + $this->Title = $title; + $this->Items = $menuItems; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/Meter.inc.php b/app/lib/phast/WebControls/Meter.inc.php new file mode 100644 index 0000000..8d8487f --- /dev/null +++ b/app/lib/phast/WebControls/Meter.inc.php @@ -0,0 +1,176 @@ +ClassList[] = "Meter"; + $this->TagName = "div"; + + $this->MinimumValue = 0; + $this->MaximumValue = 100; + $this->CurrentValue = 0; + + $this->DisplayStyle = MeterDisplayStyle::Percent; + } + + private function GetDisplayStyleValue() + { + $strValue = $this->DisplayStyle; + if (is_string($strValue)) $strValue = strtolower($strValue); + return $strValue; + } + + protected function RenderBeginTag() + { + $this->Controls = array(); + + $this->Attributes[] = new WebControlAttribute("data-minimum-value", $this->MinimumValue); + $this->Attributes[] = new WebControlAttribute("data-maximum-value", $this->MaximumValue); + $this->Attributes[] = new WebControlAttribute("data-current-value", $this->CurrentValue); + + switch ($this->GetDisplayStyleValue()) + { + case "decimal": + case MeterDisplayStyle::Decimal: + { + $this->Attributes[] = new WebControlAttribute("data-display-style", "decimal"); + break; + } + case "none": + case MeterDisplayStyle::None: + { + $this->Attributes[] = new WebControlAttribute("data-display-style", "none"); + break; + } + case "percent": + case MeterDisplayStyle::Percent: + { + $this->Attributes[] = new WebControlAttribute("data-display-style", "percent"); + break; + } + default: + { + } + } + + if ($this->BackgroundColor != null) + { + $this->Attributes[] = new WebControlAttribute("data-background-color", $this->BackgroundColor); + } + if ($this->ForegroundColor != null) + { + $this->Attributes[] = new WebControlAttribute("data-foreground-color", $this->ForegroundColor); + } + + $divContentWrapper = new HTMLControl("div"); + $divContentWrapper->ClassList[] = "ContentWrapper"; + + $divContent = new HTMLControl("div"); + $divContent->ClassList[] = "Content"; + + $decimalValue = (($this->MinimumValue + $this->CurrentValue) / ($this->MaximumValue - $this->MinimumValue)); + if (($this->MaximumValue - $this->MinimumValue) <= 0) + { + $decimalValue = 0; + } + $printedValue = round(($decimalValue * ($this->MaximumValue - $this->MinimumValue)), 0); + $percentValue = round(($decimalValue * 100), 0) . "%"; + + $stringValue = ""; + + switch ($this->GetDisplayStyleValue()) + { + case "decimal": + case MeterDisplayStyle::Decimal: + { + $stringValue = $printedValue; + break; + } + case "none": + case MeterDisplayStyle::None: + { + $stringValue = ""; + break; + } + case "percent": + case MeterDisplayStyle::Percent: + default: + { + $stringValue = $percentValue; + break; + } + } + + $divContent->InnerHTML = $stringValue; + $divContentWrapper->Controls[] = $divContent; + + $canvas = new HTMLControl("canvas"); + $divContentWrapper->Controls[] = $canvas; + + $this->Controls[] = $divContentWrapper; + + $lblTitle = new HTMLControl("label"); + $lblTitle->Attributes[] = new WebControlAttribute("for", $this->ClientID); + $lblTitle->InnerHTML = $this->Title; + $this->Controls[] = $lblTitle; + + parent::RenderBeginTag(); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/Panel.inc.php b/app/lib/phast/WebControls/Panel.inc.php new file mode 100644 index 0000000..2674ab1 --- /dev/null +++ b/app/lib/phast/WebControls/Panel.inc.php @@ -0,0 +1,141 @@ +HeaderControls as $ctl) + { + $ary[] = $ctl; + } + foreach ($this->ContentControls as $ctl) + { + $ary[] = $ctl; + } + foreach ($this->FooterControls as $ctl) + { + $ary[] = $ctl; + } + return $ary; + } + + public function __construct($id = null, $title = "") + { + parent::__construct($id); + $this->Title = $title; + $this->ParseChildElements = true; + + $this->HeaderControls = array(); + $this->ContentControls = array(); + $this->FooterControls = array(); + + $this->TagName = "div"; + $this->ClassList[] = "Panel"; + + $this->Collapsible = false; + $this->Expanded = true; + } + + protected function RenderBeginTag() + { + if ($this->Collapsible === true || $this->Collapsible === "true") + { + $this->ClassList[] = "Collapsible"; + if ($this->Expanded === true || $this->Expanded === "true") + { + $this->ClassList[] = "Expanded"; + } + } + switch ($this->HorizontalAlignment) + { + case "Center": + case HorizontalAlignment::Center: + { + $this->StyleRules[] = new WebStyleSheetRule("margin-left", "auto"); + $this->StyleRules[] = new WebStyleSheetRule("margin-right", "auto"); + break; + } + case "Right": + case HorizontalAlignment::Right: + { + $this->StyleRules[] = new WebStyleSheetRule("margin-left", "auto"); + break; + } + } + + parent::RenderBeginTag(); + } + + protected function BeforeContent() + { + if ($this->Title != "") + { + echo("
" . $this->Title . "
"); + } + echo("
"); + } + + public function BeginFooter() + { + echo("
"); + } + public function EndFooter() + { + echo("
"); + } + + protected function RenderContent() + { + if (count($this->ContentControls) > 0) + { + foreach ($this->ContentControls as $ctl) + { + $ctl->Render(); + } + } + else + { + parent::RenderContent(); + } + } + + protected function AfterContent() + { + echo("
"); + + if (is_callable($this->FooterContent)) + { + $this->BeginFooter(); + call_user_func($this->FooterContent); + $this->EndFooter(); + } + else if (count($this->FooterControls) > 0) + { + $this->BeginFooter(); + foreach ($this->FooterControls as $ctl) + { + $ctl->Render(); + } + $this->EndFooter(); + } + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/PanelContainer.inc.php b/app/lib/phast/WebControls/PanelContainer.inc.php new file mode 100644 index 0000000..3b644c3 --- /dev/null +++ b/app/lib/phast/WebControls/PanelContainer.inc.php @@ -0,0 +1,16 @@ +TagName = "div"; + $this->ClassList[] = "PanelContainer"; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/ProgressBar.inc.php b/app/lib/phast/WebControls/ProgressBar.inc.php new file mode 100644 index 0000000..443c477 --- /dev/null +++ b/app/lib/phast/WebControls/ProgressBar.inc.php @@ -0,0 +1,68 @@ +MinimumValue = 0; + $this->MaximumValue = 100; + $this->CurrentValue = 0; + + $this->Animated = false; + $this->Striped = false; + + $this->TagName = "div"; + + $this->ClassList[] = "ProgressBar"; + } + + protected function RenderBeginTag() + { + if ((is_bool($this->Striped) && $this->Striped) || (is_string($this->Striped) && $this->Striped == "true")) + { + $this->ClassList[] = "Striped"; + } + if ((is_bool($this->Animated) && $this->Animated) || (is_string($this->Animated) && $this->Animated == "true")) + { + $this->ClassList[] = "Animated"; + } + + $divProgressValueFill = new HTMLControl("div"); + $divProgressValueFill->ClassList[] = "ProgressValueFill"; + $divProgressValueFill->StyleRules[] = new WebStyleSheetRule("width", ((($this->MinimumValue + $this->CurrentValue) / ($this->MaximumValue - $this->MinimumValue)) * 100) . "%"); + $divProgressValueFill->Content = " "; + + $divProgressValueLabel = new HTMLControl("div"); + $divProgressValueLabel->ClassList[] = "ProgressValueLabel"; + if ($this->Text == "") + { + $divProgressValueLabel->Content = " "; + } + else + { + $divProgressValueLabel->Content = $this->Text; + } + + $this->Controls = array($divProgressValueFill, $divProgressValueLabel); + + parent::RenderBeginTag(); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/PropertyReference.inc.php b/app/lib/phast/WebControls/PropertyReference.inc.php new file mode 100644 index 0000000..09a0d25 --- /dev/null +++ b/app/lib/phast/WebControls/PropertyReference.inc.php @@ -0,0 +1,16 @@ +ID, $this->DefaultValue)); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/Ribbon.inc.php b/app/lib/phast/WebControls/Ribbon.inc.php new file mode 100644 index 0000000..40b709a --- /dev/null +++ b/app/lib/phast/WebControls/Ribbon.inc.php @@ -0,0 +1,554 @@ +Title = $title; + $this->Collapsed = false; + $this->ApplicationMenu = new RibbonApplicationMenuSettings(); + $this->HelpButton = new RibbonHelpButtonSettings(); + $this->Commands = array(); + $this->Tabs = array(); + + $this->TagName = "div"; + } + + public function GetCommandByID($id) + { + foreach ($this->Commands as $command) + { + if ($command->ID == $id) return $command; + } + return null; + } + public function GetTabByID($id) + { + foreach ($this->Tabs as $tab) + { + if ($tab->ID === $id) return $tab; + } + return null; + } + + protected function OnInitialize() + { + $this->Collapsed = ($this->GetClientProperty("Collapsed", "false") == "true"); + $SelectedTabID = ($this->GetClientProperty("ActiveTabID", null)); + if ($SelectedTabID != null) + { + $this->SelectedTab = $this->GetTabByID($SelectedTabID); + } + } + + private function RenderRibbonTab($tab) + { + echo("SelectedTab->ID === $tab->ID) + { + echo(" Selected"); + } + echo("\" data-tab-id=\"" . $tab->ID . "\" data-tooltip-title=\"" . $tab->ToolTipTitle . "\" data-tooltip-content=\"" . $tab->ToolTipText . "\" href=\"#\""); + if (!$tab->Visible) + { + echo(" style=\"display: none;\""); + } + echo(">"); + if ($tab->ImageURL != null) + { + echo("ImageURL) . "\" />"); + } + echo($tab->Title); + + echo(""); + } + private function RenderRibbonItem($item) + { + if (get_class($item) == "Phast\\WebControls\\RibbonCommandReferenceItem") + { + $this->RenderRibbonCommand($this->GetCommandByID($item->TargetID)); + } + else if (get_class($item) == "Phast\\WebControls\\RibbonSeparatorItem") + { + echo(" "); + } + } + private function RenderRibbonCommand($command) + { + if ($command == null) return; + + echo("
ToolTipTitle . "\" data-tooltip-content=\"" . $command->ToolTipText . "\" class=\"RibbonCommand Ribbon_" . $this->ID . "_Commands_" . $command->ID); + if ($command->Selected) + { + echo(" Selected"); + } + if (!$command->Enabled) + { + echo(" Disabled"); + } + + if (get_class($command) == "Phast\\WebControls\\RibbonDropDownCommand") + { + echo(" RibbonDropDownCommand"); + echo("\">"); + + $titleText = $command->Title; + $accessKey = null; + $iof = stripos($titleText, "&"); + if ($iof !== false) + { + $titleTextBefore = substr($titleText, 0, $iof); + $titleTextAfter = substr($titleText, $iof . 2); + $accessKey = substr($titleText, $iof . 1, 1); + $titleText = $titleTextBefore . "" . $accessKey . "" . $titleTextAfter; + } + + echo(""); + echo("TargetURL != null) + { + echo(" href=\"" . System::ExpandRelativePath($command->TargetURL) . "\""); + } + else + { + echo(" href=\"#\""); + } + if ($command->TargetFrame != null) + { + echo(" target=\"" . $command->TargetFrame . "\""); + } + + $onclickstr = "var ribbon = Ribbon.FromID('" . $this->ID . "'); ribbon.SetApplicationMenuVisible(false);"; + + $onclickstr .= "if (ribbon.IsCollapsed() && ribbon.IsOpened())"; + $onclickstr .= "{ ribbon.SetOpened(false); };"; + $onclickstr .= $command->ID . ".ToggleSelected();"; + /* + if ($command->TargetScript != null) + { + $onclickstr .= $command->TargetScript; + } + */ + echo(" onclick=\"" . $onclickstr . "\""); + echo(">"); + + if ($command->ImageURL != null) + { + echo("ImageURL) . "\" />"); + } + + echo(""); + echo($titleText); + echo(""); + + echo(""); + echo($titleText); + echo(""); + + echo(""); + + echo(""); + + echo("
ID . "_Commands_" . $command->ID . "_DropDownItems\">"); + echo("
 
"); + foreach ($command->Items as $item) + { + $this->RenderRibbonItem($item); + } + echo("
"); + } + else if (get_class($command) == "Phast\\WebControls\\RibbonButtonCommand") + { + echo(" RibbonButtonCommand"); + echo("\">"); + + $titleText = $command->Title; + $accessKey = null; + $iof = stripos($titleText, "&"); + if ($iof !== false) + { + $titleTextBefore = substr($titleText, 0, $iof); + $titleTextAfter = substr($titleText, $iof . 2); + $accessKey = substr($titleText, $iof . 1, 1); + $titleText = $titleTextBefore . "" . $accessKey . "" . $titleTextAfter; + } + + echo(""); + echo("TargetURL != null) + { + echo(" href=\"" . System::ExpandRelativePath($command->TargetURL) . "\""); + } + else + { + echo(" href=\"#\""); + } + if ($command->TargetFrame != null) + { + echo(" target=\"" . $command->TargetFrame . "\""); + } + + $onclickstr = "var ribbon = Ribbon.FromID('" . $this->ID . "'); ribbon.SetApplicationMenuVisible(false);"; + + $onclickstr .= "if (ribbon.IsCollapsed() && ribbon.IsOpened())"; + $onclickstr .= "{ ribbon.SetOpened(false); };"; + if ($command->TargetScript != null) + { + $onclickstr .= $command->TargetScript; + } + echo(" onclick=\"" . $onclickstr . "\""); + echo(">"); + + if ($command->ImageURL != null) + { + echo("ImageURL) . "\" />"); + } + + echo(""); + echo($titleText); + echo(""); + + echo(""); + echo($titleText); + echo(""); + + echo(""); + } + echo("
"); + } + + private function RenderRibbonTabGroup($group) + { + echo("
Visible) echo(" style=\"display: none;\""); + echo(">"); + echo("
 
"); + echo("
"); + foreach ($group->Items as $item) + { + $this->RenderRibbonItem($item); + } + echo("
"); + echo("
" . $group->Title . "
"); + echo("
"); + echo("Visible) echo(" style=\"display: none;\""); + echo("> "); + } + + protected function RenderContent() + { + if ($this->Title != null || $this->ImageURL != null) + { + echo("
"); + if ($this->ImageURL != null) + { + echo("ImageURL) . "\" /> "); + } + if ($this->Title != null) + { + echo("" . $this->Title . ""); + } + echo("
"); + } + echo ("
Collapsed) + { + echo(" Collapsed"); + } + echo("\" data-id=\"" . $this->ID . "\">"); + + echo("ID . "_ApplicationButton\" class=\"ApplicationButton\" data-tooltip-title=\"" . $this->ApplicationMenu->ToolTipTitle . "\" data-tooltip-content=\"" . $this->ApplicationMenu->ToolTipText . "\">"); + echo(""); + echo(""); + + echo("
ID . "_ApplicationMenu\">"); + echo("
 
"); + echo("
"); + foreach ($this->ApplicationMenu->Commands as $item) + { + $this->RenderRibbonCommand($item); + } + echo("
"); + echo("
"); + + echo("
ID . "_TabContainer\">"); + foreach ($this->Tabs as $tab) + { + $this->RenderRibbonTab($tab); + } + /* + foreach (RibbonTabContext ctx in $this->Contexts) + { + echo("
"); + echo("
"); + echo(ctx.Title); + echo("
"); + foreach (RibbonTab tab in ctx.Tabs) + { + $this->RenderRibbonTab(tab, writer); + } + echo("
"); + } + */ + echo("
"); + + echo("
ID . "_TabContentContainer\">"); + foreach ($this->Tabs as $tab) + { + echo("
ID === $this->SelectedTab->ID) + { + echo(" Selected"); + } + echo("\" data-tab-id=\"" . $tab->ID . "\""); + echo(">"); + foreach ($tab->Groups as $tgroup) + { + $this->RenderRibbonTabGroup($tgroup); + } + echo("
"); + } + echo("
"); + + if ($this->UserName != null) + { + echo("" . $this->UserName . ""); + } + + echo("ID . "_HelpButton\" data-tooltip-title=\"" . $this->HelpButton->ToolTipTitle . "\" data-tooltip-content=\"" . $this->HelpButton->ToolTipText . "\""); + if ($this->HelpButton->TargetURL != null) + { + echo(" href=\"" . $this->HelpButton->TargetURL . "\""); + if ($this->HelpButton->TargetFrame != null) + { + echo(" target=\"" . $this->HelpButton->TargetFrame . "\""); + } + } + else + { + echo(" href=\"#\""); + } + if ($this->HelpButton->TargetScript != null) + { + echo(" onclick=\"" . $this->HelpButton->TargetScript . "\""); + } + if ($this->HelpButton->Visible) + { + echo(" style=\"display: inline-block\""); + } + echo(">"); + + echo("\"Help\""); + echo(""); + + echo("
"); + echo(""); + echo("
ID . "_Spacer\" class=\"RibbonSpacer"); + if ($this->Collapsed) echo(" Collapsed"); + echo("\">"); + } + protected function RenderBeginTag() + { + parent::RenderBeginTag(); + echo("
"); + } + protected function RenderEndTag() + { + parent::RenderEndTag(); + echo("
"); + + echo("
 
Press F1 for more help.
"); + } + } + class RibbonTab + { + public $ID; + public $Title; + public $ImageURL; + public $ToolTipTitle; + public $ToolTipText; + + public $Groups; + + public $Visible; + + public function __construct($id, $title = null, $groups = null, $imageURL = null, $tooltipTitle = null, $tooltipText = null, $visible = true) + { + if ($groups == null) $groups = array(); + + $this->ID = $id; + $this->Title = $title; + $this->Groups = $groups; + $this->ImageURL = $imageURL; + $this->ToolTipTitle = $tooltipTitle; + $this->ToolTipText = $tooltipText; + $this->Visible = $visible; + } + + public function GetTabGroupByID($id) + { + foreach ($this->Groups as $group) + { + if ($group->ID == $id) return $group; + } + return null; + } + } + class RibbonTabGroup + { + public $ID; + public $Title; + public $Items; + public $Visible; + + public function __construct($id, $title, $items = null) + { + if ($items == null) $items = array(); + + $this->ID = $id; + $this->Title = $title; + $this->Items = $items; + $this->Visible = true; + } + } + + abstract class RibbonCommand + { + public $ID; + public $Enabled; + + public function __construct($id) + { + $this->ID = $id; + $this->Enabled = true; + } + } + class RibbonButtonCommand extends RibbonCommand + { + public $Title; + public $ImageURL; + + public $TargetURL; + public $TargetScript; + public $TargetFrame; + + public $ToolTipTitle; + public $ToolTipText; + + public $Selected; + + public function __construct($id, $title, $targetURL = null, $targetScript = null, $imageURL = null, $toolTipTitle = null, $toolTipText = null) + { + parent::__construct($id); + $this->Title = $title; + + $this->TargetURL = $targetURL; + $this->TargetScript = $targetScript; + + $this->ImageURL = $imageURL; + $this->ToolTipTitle = $toolTipTitle; + $this->ToolTipText = $toolTipText; + } + } + class RibbonDropDownCommand extends RibbonButtonCommand + { + public $Items; + + public function __construct($id, $title, $targetURL = null, $targetScript = null, $imageURL = null, $toolTipTitle = null, $toolTipText = null, $commands = null) + { + parent::__construct($id, $title, $targetURL, $targetScript, $imageURL, $toolTipTitle, $toolTipText); + + if ($commands == null) $commands = array(); + $this->Items = $commands; + } + } + + abstract class RibbonItem + { + } + class RibbonCommandReferenceItem extends RibbonItem + { + public $TargetID; + public function __construct($targetID) + { + $this->TargetID = $targetID; + } + } + class RibbonSeparatorItem extends RibbonItem + { + } + + class RibbonApplicationMenuSettings + { + public $Title; + public $Commands; + + public $ToolTipTitle; + public $ToolTipText; + + public function __construct() + { + $this->Commands = array(); + } + } + class RibbonHelpButtonSettings + { + public $ToolTipTitle; + public $ToolTipText; + + public $TargetURL; + public $TargetFrame; + public $TargetScript; + + public $Visible; + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/Section.inc.php b/app/lib/phast/WebControls/Section.inc.php new file mode 100644 index 0000000..0180dfb --- /dev/null +++ b/app/lib/phast/WebControls/Section.inc.php @@ -0,0 +1,19 @@ + diff --git a/app/lib/phast/WebControls/Sidebar.inc.php b/app/lib/phast/WebControls/Sidebar.inc.php new file mode 100644 index 0000000..ee6ff46 --- /dev/null +++ b/app/lib/phast/WebControls/Sidebar.inc.php @@ -0,0 +1,16 @@ +TagName = "nav"; + $this->ClassList[] = "Sidebar"; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/SplitContainer.inc.php b/app/lib/phast/WebControls/SplitContainer.inc.php new file mode 100644 index 0000000..37f1a4b --- /dev/null +++ b/app/lib/phast/WebControls/SplitContainer.inc.php @@ -0,0 +1,91 @@ +Orientation = Orientation::Vertical; + $this->PrimaryPanel = new SplitContainerPanel($this, "Primary"); + $this->SecondaryPanel = new SplitContainerPanel($this, "Secondary"); + $this->SplitterWidth = "4px"; + $this->TagName = "div"; + } + + protected function RenderBeginTag() + { + $this->ClassList[] = "SplitContainer"; + switch ($this->Orientation) + { + case Orientation::Horizontal: + { + $this->ClassList[] = "Horizontal"; + break; + } + case Orientation::Vertical: + { + $this->ClassList[] = "Vertical"; + break; + } + } + parent::RenderBeginTag(); + } + protected function RenderEndTag() + { + parent::RenderEndTag(); + echo(""); + } + } + class SplitContainerPanel extends \Phast\WebControl + { + public $ID; + public $ParentContainer; + public $Visible; + + public function __construct($parent, $id) + { + $this->ParentContainer = $parent; + $this->ID = $id; + $this->TagName = "div"; + $this->Visible = true; + } + + protected function RenderBeginTag() + { + $this->ClassList[] = "SplitContainerPanel"; + $this->ClassList[] = $this->ID; + + $this->ClientID = "SplitContainer_" . $this->ParentContainer->ID . "_" . $this->ID . "\""; + if ($this->ID == "Primary" && $this->ParentContainer->SplitterPosition != null) + { + $this->StyleRules[] = new WebStyleSheetRule("width", $this->ParentContainer->SplitterPosition); + } + + parent::RenderBeginTag(); + } + protected function RenderEndTag() + { + parent::RenderEndTag(); + + if ($this->ID == "Primary") + { + echo("
ParentContainer->ID . "_Splitter\""); + if ($this->ParentContainer->SplitterWidth != null) + { + echo (" style=\"width: " . $this->ParentContainer->SplitterWidth . "\""); + } + echo("> 
"); + } + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/TabContainer.inc.php b/app/lib/phast/WebControls/TabContainer.inc.php new file mode 100644 index 0000000..049bbeb --- /dev/null +++ b/app/lib/phast/WebControls/TabContainer.inc.php @@ -0,0 +1,230 @@ +Controls as $ctl) + { + if ($ctl->ID == $id) return $ctl; + + $ctll = $ctl->GetControlByID($id); + if ($ctll != null) return $ctll; + } + return null; + } + + public function __construct($id = null, $title = null, $imageURL = null, $targetURL = null, $targetScript = null, $visible = true) + { + $this->ID = $id; + $this->Title = $title; + $this->ImageURL = $imageURL; + $this->TargetURL = $targetURL; + $this->TargetScript = $targetScript; + $this->Visible = $visible; + } + } + + class TabContainer extends WebControl + { + public $SelectedTab; + public $SelectedTabID; + + public $TabPages; + /** + * Determines the position of the tabs relative to the tab pages. + * @var TabContainerTabPosition + */ + public $TabPosition; + + public $OnClientTabChanged; + + public function __construct() + { + parent::__construct(); + $this->TagName = "div"; + $this->ParseChildElements = true; + $this->TabPosition = TabContainerTabPosition::Top; + } + + public function GetTabByID($id) + { + foreach ($this->TabPages as $tabPage) + { + if ($tabPage->ID == $id) return $tabPage; + } + return null; + } + + protected function OnInitialize() + { + $oldtab = $this->SelectedTab; + $this->SelectedTab = $this->GetTabByID($this->GetClientProperty("SelectedTabID")); + if ($this->SelectedTab == null) $this->SelectedTab = $oldtab; + } + + protected function RenderBeginTag() + { + if ($this->OnClientTabChanged != null) + { + $this->Attributes[] = new WebControlAttribute("data-onclienttabchanged", $this->OnClientTabChanged); + } + $this->ClassList[] = "TabContainer"; + + if (is_string($this->TabPosition)) + { + switch (strtolower($this->TabPosition)) + { + case "left": + { + $this->ClassList[] = "TabPositionLeft"; + break; + } + case "top": + { + $this->ClassList[] = "TabPositionTop"; + break; + } + case "right": + { + $this->ClassList[] = "TabPositionRight"; + break; + } + case "bottom": + { + $this->ClassList[] = "TabPositionBottom"; + break; + } + } + } + else + { + switch ($this->TabPosition) + { + case TabContainerTabPosition::Left: + { + $this->ClassList[] = "TabPositionLeft"; + break; + } + case TabContainerTabPosition::Top: + { + $this->ClassList[] = "TabPositionTop"; + break; + } + case TabContainerTabPosition::Right: + { + $this->ClassList[] = "TabPositionRight"; + break; + } + case TabContainerTabPosition::Bottom: + { + $this->ClassList[] = "TabPositionBottom"; + break; + } + } + } + + $this->Controls = array(); + + $ulTabs = new HTMLControl("ul"); + $ulTabs->ClassList[] = "Tabs"; + + $j = 0; + foreach ($this->TabPages as $tabPage) + { + $liTab = new HTMLControl("li"); + if ((is_bool($tabPage->Visible) && $tabPage->Visible) || (is_string($tabPage->Visible) && $tabPage->Visible != "false")) + { + $liTab->ClassList[] = "Visible"; + } + if (($this->SelectedTabID != null && $tabPage->ID == $this->SelectedTabID) || ($this->SelectedTab != null && ($tabPage->ID == $this->SelectedTab->ID))) + { + $liTab->ClassList[] = "Selected"; + } + + $aTab = new Anchor(); + $aTab->Attributes[] = new WebControlAttribute("data-id", $tabPage->ID); + $aTab->TargetURL = $tabPage->TargetURL; + $aTab->InnerHTML = $tabPage->Title; + $liTab->Controls[] = $aTab; + + $ulTabs->Controls[] = $liTab; + $j++; + } + $this->Controls[] = $ulTabs; + + $divTabPages = new HTMLControl("div"); + $divTabPages->ClassList[] = "TabPages"; + foreach ($this->TabPages as $tabPage) + { + $divTabPage = new HTMLControl("div"); + $divTabPage->ClassList[] = "TabPage"; + if (($this->SelectedTabID != null && $tabPage->ID == $this->SelectedTabID) || ($this->SelectedTab != null && ($tabPage->ID == $this->SelectedTab->ID))) + { + $divTabPage->ClassList[] = "Selected"; + } + + if (isset($tabPage->Content)) + { + $divTabPage->Content = $tabPage->Content; + } + foreach ($tabPage->Controls as $ctl) + { + $divTabPage->Controls[] = $ctl; + } + $divTabPages->Controls[] = $divTabPage; + } + $this->Controls[] = $divTabPages; + + parent::RenderBeginTag(); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/TabStrip.inc.php b/app/lib/phast/WebControls/TabStrip.inc.php new file mode 100644 index 0000000..2841cbe --- /dev/null +++ b/app/lib/phast/WebControls/TabStrip.inc.php @@ -0,0 +1,87 @@ +ID = $id; + $this->Title = $title; + $this->NavigateURL = $navigateURL; + $this->OnClientClick = $onClientClick; + $this->Selected = $selected; + } + } + class TabStrip extends WebControl + { + public $TabPosition; + + protected function RenderContent() + { +?> + + \ No newline at end of file diff --git a/app/lib/phast/WebControls/TextBox.inc.php b/app/lib/phast/WebControls/TextBox.inc.php new file mode 100644 index 0000000..64f4b77 --- /dev/null +++ b/app/lib/phast/WebControls/TextBox.inc.php @@ -0,0 +1,206 @@ +Title = $title; + $this->Value = $value; + $this->Selected = $selected; + } + } + + class TextBox extends WebControl + { + public $Name; + public $PlaceholderText; + + /** + * The items available for selection from this TextBox. + * @var TextBoxItem[] + */ + public $Items; + + public $InnerStyle; + public $MultiSelect; + + public $RequireSelectionFromChoices; + public $EnableMultipleSelection; + public $ClearOnFocus; + public $OpenWhenFocused; + + public $ShowColumnHeaders; + + public $SuggestionURL; + + public $Text; + + public function __construct() + { + parent::__construct(); + + $this->ShowColumnHeaders = true; + $this->Items = array(); + + $this->TagName = "div"; + $this->ClassList[] = "TextBox"; + + $this->OpenWhenFocused = true; + $this->MultiSelect = false; + } + + protected function OnInitialize() + { + $parent = $this->FindParentPage(); + if ($parent != null) $parent->Scripts[] = new WebScript("$(PhastStaticPath)/Scripts/Controls/TextBox.js"); + } + + protected function RenderBeginTag() + { + $this->Controls = array(); + + if ($this->RequireSelectionFromChoices) + { + $this->ClassList[] = "RequireSelection"; + } + if ($this->ClearOnFocus) + { + $this->ClassList[] = "ClearOnFocus"; + } + if ($this->MultiSelect) + { + $this->ClassList[] = "MultiSelect"; + } + if ($this->SuggestionURL != null) + { + $this->Attributes[] = new WebControlAttribute("data-suggestion-url", System::ExpandRelativePath($this->SuggestionURL)); + } + if ($this->OpenWhenFocused) + { + $this->Attributes[] = new WebControlAttribute("data-auto-open", "true"); + } + + $divTextboxContent = new HTMLControl("div"); + $divTextboxContent->ClassList[] = "TextboxContent"; + + $spanTextboxSelectedItems = new HTMLControl("span"); + $spanTextboxSelectedItems->ClassList[] = "TextboxSelectedItems"; + + $i = 0; + foreach ($this->Items as $item) + { + if (!$item->Selected) continue; + + $spanSelectedItem = new HTMLControl("span"); + $spanSelectedItem->ClassList[] = "SelectedItem"; + + $spanText = new HTMLControl("span"); + $spanText->ClassList[] = "Text"; + $spanText->InnerHTML = $item->Title; + $spanSelectedItem->Controls[] = $spanText; + + $aCloseButton = new Anchor(); + $aCloseButton->ClassList[] = "CloseButton"; + $spanSelectedItem->Controls[] = $aCloseButton; + + $spanTextboxSelectedItems->Controls[] = $spanSelectedItem; + + $i++; + } + $divTextboxContent->Controls[] = $spanTextboxSelectedItems; + + $inputText = new Input(); + $inputText->ID = $this->ID . "_InputElement"; + $inputText->Type = InputType::Text; + $inputText->Attributes[] = new WebControlAttribute("autocomplete", "off"); + $inputText->Name = $this->Name; + $inputText->PlaceholderText = $this->PlaceholderText; + $inputText->Value = $this->Text; + $inputText->Width = $this->Width; + + if ($this->InnerStyle != null) + { + $inputText->Attributes[] = new WebControlAttribute("style", $this->InnerStyle); + } + $divTextboxContent->Controls[] = $inputText; + + $this->Controls[] = $divTextboxContent; + + $divSuggestionList = new HTMLControl("div"); + $divSuggestionList->ClassList[] = "SuggestionList"; + + $ulSuggestionList = new HTMLControl("ul"); + $ulSuggestionList->ClassList[] = "Menu"; + $ulSuggestionList->ClassList[] = "Popup"; + foreach ($this->Items as $item) + { + $li = new HTMLControl("li"); + $li->ClassList[] = "MenuItem"; + $li->ClassList[] = "Command"; + $li->ClassList[] = "Visible"; + $aSuggestionListItem = new Anchor(); + + $iCheckmark = new HTMLControl("i"); + $iCheckmark->ClassList[] = "fa"; + $iCheckmark->ClassList[] = "fa-check"; + $iCheckmark->ClassList[] = "Checkmark"; + $aSuggestionListItem->Controls[] = $iCheckmark; + + $spanText = new HTMLControl("span"); + $spanText->Content = $item->Title; + $aSuggestionListItem->Controls[] = $spanText; + + $aSuggestionListItem->Attributes[] = new WebControlAttribute("data-value", $item->Value); + $li->Controls[] = $aSuggestionListItem; + $ulSuggestionList->Controls[] = $li; + } + $divSuggestionList->Controls[] = $ulSuggestionList; + + $divSuggestionListThrobber = new HTMLControl("div"); + $divSuggestionListThrobber->ClassList[] = "Throbber"; + $divSuggestionList->Controls[] = $divSuggestionListThrobber; + + $this->Controls[] = $divSuggestionList; + + $selectSuggestionList = new HTMLControl("select"); + $selectSuggestionList->ClassList[] = "SuggestionList"; + $selectSuggestionList->Attributes[] = new WebControlAttribute("multiple", "multiple"); + + foreach ($this->Items as $item) + { + $option = new HTMLControl("option"); + $option->Attributes[] = new WebControlAttribute("value", $item->Value); + $option->Content = $item->Title; + if ($item->Selected) + { + $option->Attributes[] = new WebControlAttribute("selected", "selected"); + } + $selectSuggestionList->Controls[] = $option; + } + $this->Controls[] = $selectSuggestionList; + + parent::RenderBeginTag(); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/Timeline.inc.php b/app/lib/phast/WebControls/Timeline.inc.php new file mode 100644 index 0000000..1234623 --- /dev/null +++ b/app/lib/phast/WebControls/Timeline.inc.php @@ -0,0 +1,56 @@ +ParseChildElements = false; + $this->ClassList[] = "Timeline"; + $this->TagName = "div"; + } + + protected function RenderBeginTag() + { + $this->Controls = array(); + foreach ($this->Posts as $post) + { + $divPost = new HTMLControl("div"); + $divPost->ClassList[] = "TimelinePost"; + + $divPostHeader = new HTMLControl("div"); + $divPostHeader->ClassList[] = "Header"; + $divPost->Controls[] = $divPostHeader; + + $divPostContent = new HTMLControl("div"); + $divPostContent->ClassList[] = "Content"; + $divPost->Controls[] = $divPostContent; + + $divPostFooter = new HTMLControl("div"); + $divPostFooter->ClassList[] = "Footer"; + $divPost->Controls[] = $divPostFooter; + + $this->Controls[] = $divPost; + } + parent::RenderBeginTag(); + } + } + class TimelinePost + { + public $ID; + public $CreationUserName; + public $CreationUserImageURL; + public $Controls; + public $ViewCount; + + public function __construct() + { + $this->ParseChildElements = true; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/ToggleSwitch.inc.php b/app/lib/phast/WebControls/ToggleSwitch.inc.php new file mode 100644 index 0000000..333c5e3 --- /dev/null +++ b/app/lib/phast/WebControls/ToggleSwitch.inc.php @@ -0,0 +1,75 @@ +TagName = "div"; + $this->ClassList[] = "ToggleSwitch"; + } + + protected function RenderBeginTag() + { + switch (mvarOrientation) + { + case ToggleSwitchOrientation::Horizontal: + { + $this->ClassList[] = "Horizontal"; + break; + } + case ToggleSwitchOrientation::Vertical: + { + $this->ClassList[] = "Vertical"; + break; + } + } + + $divToggleSwitchInner = new HTMLControl("div"); + $divToggleSwitchInner->ClassList[] = "ToggleSwitchInner"; + + $divToggleOn = new HTMLControl("div"); + $divToggleOn->ClassList[] = "ToggleOn"; + $divToggleOn->InnerHTML = "On"; + $divToggleSwitchInner->Controls[] = $divToggleOn; + + $divToggleThumb = new HTMLControl("div"); + $divToggleThumb->ClassList[] = "ToggleThumb"; + $divToggleSwitchInner->Controls[] = $divToggleThumb; + + $divToggleOff = new HTMLControl("div"); + $divToggleOff->ClassList[] = "ToggleOff"; + $divToggleOff->InnerHTML = "Off"; + $divToggleSwitchInner->Controls[] = $divToggleOff; + + $this->Controls[] = $divToggleSwitchInner; + + parent::RenderBeginTag(); + } + } +?> diff --git a/app/lib/phast/WebControls/ToolTip.inc.php b/app/lib/phast/WebControls/ToolTip.inc.php new file mode 100644 index 0000000..a200389 --- /dev/null +++ b/app/lib/phast/WebControls/ToolTip.inc.php @@ -0,0 +1,37 @@ +Title = $title; + $this->Content = $content; + } + + protected function BeforeContent() + { +?> +
Width != null) { echo (" style=\"width: " . (is_numeric($this->Width) ? ($this->Width . "px") : $this->Width) . ";\""); } ?>> +
Title); ?>
+
Content); ?>
+
+
+ +
+ + \ No newline at end of file diff --git a/app/lib/phast/WebControls/TrackBar.inc.php b/app/lib/phast/WebControls/TrackBar.inc.php new file mode 100644 index 0000000..5dd51f4 --- /dev/null +++ b/app/lib/phast/WebControls/TrackBar.inc.php @@ -0,0 +1,181 @@ +TagName = "div"; + $this->ClassList[] = "TrackBar"; + $this->ShowValue = false; + $this->Size = null; + $this->Orientation = TrackBarOrientation::Horizontal; + } + protected function OnInitialize() + { + $this->Controls = array(); + + $this->Attributes[] = new WebControlAttribute("data-maximum-value", $this->MaximumValue); + $this->Attributes[] = new WebControlAttribute("data-minimum-value", $this->MinimumValue); + $this->Attributes[] = new WebControlAttribute("data-current-value", $this->CurrentValue); + + if ($this->ShowValue) + { + $this->ClassName[] = "ShowValue"; + } + + if (is_string($this->Orientation)) + { + switch (strtolower($this->Orientation)) + { + case "horizontal": + { + $this->Orientation = TrackBarOrientation::Horizontal; + break; + } + case "vertical": + { + $this->Orientation = TrackBarOrientation::Vertical; + break; + } + } + } + + switch ($this->Orientation) + { + case TrackBarOrientation::Horizontal: + { + $this->ClassList[] = "Horizontal"; + break; + } + case TrackBarOrientation::Vertical: + { + $this->ClassList[] = "Vertical"; + break; + } + } + + if ($this->Size != null) + { + switch ($this->Orientation) + { + case TrackBarOrientation::Horizontal: + { + $this->StyleRules[] = new WebStyleSheetRule("width", $this->Size); + break; + } + case TrackBarOrientation::Vertical: + { + $this->StyleRules[] = new WebStyleSheetRule("height", $this->Size); + break; + } + } + } + + $leftPos = (($this->CurrentValue - $this->MinimumValue) / ($this->MaximumValue - $this->MinimumValue)) * 100; + if ($this->Orientation == TrackBarOrientation::Vertical) + { + $leftPos = 100 - $leftPos; + } + + $divTrack = new HTMLControl("div"); + $divTrack->ClassList[] = "Track"; + + $divQuantity = new HTMLControl("div"); + $divQuantity->ClassList[] = "Quantity"; + + switch ($this->Orientation) + { + case TrackBarOrientation::Horizontal: + { + $divQuantity->StyleRules[] = new WebStyleSheetRule("width", $leftPos . "%"); + break; + } + case TrackBarOrientation::Vertical: + { + $divQuantity->StyleRules[] = new WebStyleSheetRule("top", $leftPos . "%"); + break; + } + } + $divTrack->Controls[] = $divQuantity; + + $divThumb = new HTMLControl("div"); + $divThumb->ClassList[] = "Thumb"; + switch ($this->Orientation) + { + case TrackBarOrientation::Horizontal: + { + $divThumb->StyleRules[] = new WebStyleSheetRule("left", $leftPos . "%"); + break; + } + case TrackBarOrientation::Vertical: + { + $divThumb->StyleRules[] = new WebStyleSheetRule("top", $leftPos . "%"); + break; + } + } + + $spanThumbText = new HTMLControl("span"); + $spanThumbText->ClassList[] = "Text"; + $spanThumbText->InnerHTML = $this->CurrentValue; + $divThumb->Controls[] = $spanThumbText; + + $divTrack->Controls[] = $divThumb; + + $this->Controls[] = $divTrack; + + parent::OnInitialize(); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/WebActionListControl.inc.php b/app/lib/phast/WebControls/WebActionListControl.inc.php new file mode 100644 index 0000000..efa1fcf --- /dev/null +++ b/app/lib/phast/WebControls/WebActionListControl.inc.php @@ -0,0 +1,91 @@ +"); + echo("
" . $item->Title . "
"); + echo("
"); + foreach ($item->Items as $item1) + { + $this->RenderItemRecursive($item1); + } + if ($item->MoreCommand != null) + { + echo("MoreCommand->NavigateUrl) . "\">" . $item->MoreCommand->Title . ""); + } + echo("
"); + echo("
"); + } + else if (get_class($item) == "WebActionListCommand") + { + echo("
"); + + if (count($item->MenuItems) > 0) + { + echo(" "); + } + echo("NavigateUrl) . "\""); + if ($item->OnClientClick != null) + { + echo(" onclick=\"" . $item->OnClientClick . "\""); + } + echo(">" . $item->Title . ""); + + echo("
"); + } + else if (get_class($item) == "WebActionListSeparator") + { + echo("
"); + } + } + protected function RenderContent() + { + echo("
"); + foreach ($this->Items as $item) + { + $this->RenderItemRecursive($item); + } + echo("
"); + } + } + + abstract class WebActionListItem + { + } + + class WebActionListGroup extends WebActionListItem + { + public $Title; + public $Items; + public $MoreCommand; + + public function __construct($title) + { + $this->Title = $title; + } + } + class WebActionListCommand extends WebActionListItem + { + public $Title; + public $NavigateUrl; + public $OnClientClick; + public $MenuItems; + + public function __construct($title, $navigateUrl = "#", $onClientClick = null, $menuItems = array()) + { + $this->Title = $title; + $this->NavigateUrl = $navigateUrl; + $this->OnClientClick = $onClientClick; + $this->MenuItems = $menuItems; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/WebDropDownControl.inc.php b/app/lib/phast/WebControls/WebDropDownControl.inc.php new file mode 100644 index 0000000..a66b241 --- /dev/null +++ b/app/lib/phast/WebControls/WebDropDownControl.inc.php @@ -0,0 +1,51 @@ + + + + \ No newline at end of file diff --git a/app/lib/phast/WebControls/WebMenuControl.inc.php b/app/lib/phast/WebControls/WebMenuControl.inc.php new file mode 100644 index 0000000..db889b9 --- /dev/null +++ b/app/lib/phast/WebControls/WebMenuControl.inc.php @@ -0,0 +1,71 @@ +ID . "_menu\">"); + foreach ($this->MenuItems as $menuItem) + { + if (get_class($menuItem) == "WebMenuItemCommand") + { + echo("NavigateUrl) . "\" onclick=\""); + if ($menuItem->OnClientClick != null) + { + echo($menuItem->OnClientClick . "; "); + } + echo($this->ID . ".PopupMenu.Hide(); return false;\">" . $menuItem->Title . ""); + } + else if (get_class($menuItem) == "WebMenuItemHeader") + { + echo("" . $menuItem->Title . " " . $menuItem->Subtitle . ""); + } + } + echo(""); + echo(""); + } + } + + class WebMenuItem + { + } + class WebMenuItemHeader extends WebMenuItem + { + public $Title; + public $Subtitle; + public function __construct($title, $subtitle = null) + { + $this->Title = $title; + $this->Subtitle = $subtitle; + } + } + class WebMenuItemCommand extends WebMenuItem + { + public $Title; + public $NavigateUrl; + public $OnClientClick; + + public function __construct($title, $navigateUrl = "#", $onClientClick = null) + { + $this->Title = $title; + $this->NavigateUrl = $navigateUrl; + $this->OnClientClick = $onClientClick; + } + } + class WebMenuItemMenu extends WebMenuItem + { + public $Title; + public $MenuItems; + + public function __construct($title, $menuItems = array()) + { + $this->Title = $title; + $this->MenuItems = $menuItems; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/WebMenuLinkControl.inc.php b/app/lib/phast/WebControls/WebMenuLinkControl.inc.php new file mode 100644 index 0000000..36d1d1f --- /dev/null +++ b/app/lib/phast/WebControls/WebMenuLinkControl.inc.php @@ -0,0 +1,60 @@ +ID = $id; + $this->Title = $title; + $this->NavigateUrl = $navigateUrl; + $this->OnClientClick = $onClientClick; + $this->MenuItems = $menuItems; + } + + protected function RenderContent() + { + echo("
ID . "_menu\">"); + + foreach ($this->MenuItems as $menuItem) + { + if (get_class($menuItem) == "Phast\\WebControls\\WebMenuItemCommand") + { + echo("NavigateUrl) . "\" onclick=\""); + if ($menuItem->OnClientClick != null) + { + echo($menuItem->OnClientClick . "; "); + } + echo($this->ID . ".PopupMenu.Hide(); return false;\">" . $menuItem->Title . ""); + } + else if (get_class($menuItem) == "Phast\\WebControls\\WebMenuItemHeader") + { + echo("" . $menuItem->Title . " " . $menuItem->Subtitle . ""); + } + else + { + echo(""); + } + } + echo("
"); + + echo("ID . "\" class=\"MenuLink\">"); + if (count($this->MenuItems) > 0) + { + echo("ID . ".PopupMenu.Show(); event.preventDefault(); event.stopPropagation(); return false;\"> "); + } + echo("NavigateUrl) . "\"" . ($this->OnClientClick != null ? (" onclick=\"" . $this->OnClientClick . "\"") : "") . ">" . $this->Title . ""); + + echo(""); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/WebPanelControl.inc.php b/app/lib/phast/WebControls/WebPanelControl.inc.php new file mode 100644 index 0000000..eedfe2a --- /dev/null +++ b/app/lib/phast/WebControls/WebPanelControl.inc.php @@ -0,0 +1,43 @@ +Title = $title; + } + + protected function BeforeContent() + { + echo("
Width != null) + { + echo("width: " . $this->Width . "; "); + } + switch ($this->HorizontalAlignment) + { + case "center": + { + echo("margin-left: auto; "); + echo("margin-right: auto; "); + break; + } + } + echo("\">"); + echo("
" . $this->Title . "
"); + echo("
"); + } + protected function AfterContent() + { + echo("
"); + echo("
"); + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/Window.inc.php b/app/lib/phast/WebControls/Window.inc.php new file mode 100644 index 0000000..d4c1f42 --- /dev/null +++ b/app/lib/phast/WebControls/Window.inc.php @@ -0,0 +1,138 @@ +HasButtons = false; + $this->TagName = "div"; + $this->ClassList[] = "Window"; + $this->ParseChildElements = true; + } + + protected function OnInitialize() + { + $parent = $this->FindParentPage(); + if ($parent != null) $parent->Scripts[] = new WebScript("$(PhastStaticPath)/Scripts/Controls/Window.js"); + } + + protected function RenderBeginTag() + { + switch($this->HorizontalAlignment) + { + case HorizontalAlignment::Left: + { + $this->Attributes[] = new WebControlAttribute("data-horizontal-alignment", "left"); + break; + } + case HorizontalAlignment::Center: + { + $this->Attributes[] = new WebControlAttribute("data-horizontal-alignment", "center"); + break; + } + case HorizontalAlignment::Right: + { + $this->Attributes[] = new WebControlAttribute("data-horizontal-alignment", "right"); + break; + } + } + switch($this->VerticalAlignment) + { + case VerticalAlignment::Top: + { + $this->Attributes[] = new WebControlAttribute("data-vertical-alignment", "top"); + break; + } + case VerticalAlignment::Middle: + { + $this->Attributes[] = new WebControlAttribute("data-vertical-alignment", "middle"); + break; + } + case VerticalAlignment::Bottom: + { + $this->Attributes[] = new WebControlAttribute("data-vertical-alignment", "bottom"); + break; + } + } + + $divHeader = new HTMLControl("div"); + $divHeader->ClassList[] = "Header"; + $spanTitle = new HTMLControl("span"); + $spanTitle->ClassList[] = "Title"; + $spanTitle->InnerHTML = $this->Title; + $divHeader->Controls[] = $spanTitle; + + $divContent = new HTMLControl("div"); + $divContent->ClassList[] = "Content"; + + $divInnerContent = new HTMLControl("div"); + $divInnerContent->ClassList[] = "Content"; + foreach ($this->ContentControls as $ctl) + { + $divInnerContent->Controls[] = $ctl; + } + $divContent->Controls[] = $divInnerContent; + + $divLoading = new HTMLControl("div"); + $divLoading->ClassList[] = "Loading"; + + $divLoadingStatus = new HTMLControl("div"); + $divLoadingStatus->ClassList[] = "LoadingStatus"; + $divThrobber = new HTMLControl("div"); + $divThrobber->ClassList[] = "Throbber"; + $divLoadingStatus->Controls[] = $divThrobber; + + $pLoadingStatus = new HTMLControl("p"); + $pLoadingStatus->ClassList[] = "LoadingStatusText"; + $divLoadingStatus->Controls[] = $pLoadingStatus; + + $divLoading->Controls[] = $divLoadingStatus; + $divContent->Controls[] = $divLoading; + + $divFooter = new HTMLControl("div"); + $divFooter->ClassList[] = "Footer"; + foreach ($this->FooterControls as $ctl) + { + $divFooter->Controls[] = $ctl; + } + + if ($this->Width != null) + { + if (is_numeric($this->Width)) + { + $this->StyleRules[] = new WebStyleSheetRule("width", $this->Width . "px"); + } + else + { + $this->StyleRules[] = new WebStyleSheetRule("width", $this->Width); + } + } + + $this->Controls = array($divHeader, $divContent, $divFooter); + parent::RenderBeginTag(); + } + + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/Wizard.inc.php b/app/lib/phast/WebControls/Wizard.inc.php new file mode 100644 index 0000000..2eee454 --- /dev/null +++ b/app/lib/phast/WebControls/Wizard.inc.php @@ -0,0 +1,141 @@ +Controls as $ctl) + { + if ($ctl->ID == $id) return $ctl; + } + return null; + } + } + class Wizard extends WebControl + { + /** + * The pages contained in this Wizard. + * @var WizardPage[] + */ + public $Pages; + + /** + * The ID of the currently-selected page. + * @var string + */ + public $SelectedPageID; + + public function __construct() + { + parent::__construct(); + + $this->TagName = "div"; + $this->ClassList[] = "Wizard"; + $this->ParseChildElements = true; + } + + public function GetPageByID($id) + { + foreach ($this->Pages as $page) + { + if ($page->ID == $id) return $page; + } + return null; + } + + protected function OnInitialize() + { + $this->Controls = array(); + + $olWizardSteps = new HTMLControl("ol"); + $i = 1; + foreach ($this->Pages as $page) + { + $li = new HTMLControl("li"); + if ($this->SelectedPageID != "" && ($page->ID == $this->SelectedPageID)) + { + $li->ClassList[] = "Selected"; + } + $li->Attributes[] = new WebControlAttribute("role", "tab"); + $li->Attributes[] = new WebControlAttribute("aria-selected", "false"); + + $span = new HTMLControl("span"); + $span->ClassList[] = "Number"; + $span->InnerHTML = $i; + $li->Controls[] = $span; + + $span = new HTMLControl("span"); + $span->ClassList[] = "Title"; + $span->InnerHTML = $page->Title; + $li->Controls[] = $span; + + $span = new HTMLControl("span"); + $span->ClassList[] = "Description"; + $span->InnerHTML = $page->Description; + $li->Controls[] = $span; + + if ($page->IconName != "") + { + $span = new HTMLControl("i"); + $span->ClassList[] = "fa"; + $span->ClassList[] = "fa-" . $page->IconName; + $li->Controls[] = $span; + } + + $olWizardSteps->Controls[] = $li; + $i++; + } + $this->Controls[] = $olWizardSteps; + + $divWizardPages = new HTMLControl("div"); + $divWizardPages->ClassList[] = "WizardPages"; + + foreach ($this->Pages as $page) + { + $divWizardPage = new HTMLControl("div"); + $divWizardPage->Attributes[] = new WebControlAttribute("aria-role", "tabpanel"); + $divWizardPage->ClassList[] = "WizardPage"; + if ($this->SelectedPageID != "" && ($page->ID == $this->SelectedPageID)) + { + $divWizardPage->ClassList[] = "Selected"; + } + + foreach ($page->Controls as $ctl) + { + $divWizardPage->Controls[] = $ctl; + } + + $divWizardPages->Controls[] = $divWizardPage; + } + $this->Controls[] = $divWizardPages; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebControls/Wunderbar.inc.php b/app/lib/phast/WebControls/Wunderbar.inc.php new file mode 100644 index 0000000..b7ae160 --- /dev/null +++ b/app/lib/phast/WebControls/Wunderbar.inc.php @@ -0,0 +1,118 @@ +ID = $id; + $this->Items = array(); + } + + public function GetItemByID($id) + { + foreach ($this->Items as $item) + { + if ($item->ID == $id) return $item; + } + return null; + } + + protected function RenderContent() + { + echo("
"); + echo("
"); + foreach ($this->Items as $pane) + { + echo("
"); + if (is_callable($pane->Content)) + { + call_user_func($pane->Content); + } + else + { + echo($pane->Content); + } + echo("
"); + } + echo("
"); + echo("
"); + foreach ($this->Items as $pane) + { + echo("SelectedItem) + { + echo(" class=\"Selected\""); + } + echo(" href=\""); + if ($pane->ButtonTargetURL != null) + { + echo(System::ExpandRelativePath($pane->ButtonTargetURL)); + } + else + { + echo("#"); + } + echo("\""); + if ($pane->ButtonTargetFrame != null) + { + echo(" target=\"" . $pane->ButtonTargetFrame . "\""); + } + if (!$pane->InhibitDefaultBehavior) + { + echo(" onclick=\"" . $this->ID . ".SetSelectedPane('" . $pane->ID . "');"); + if ($pane->ButtonOnClientClick != null) + { + echo(" onclick=\"" . $pane->ButtonOnClientClick . "\""); + } + echo("\""); + } + else + { + if ($pane->ButtonOnClientClick != null) + { + echo(" onclick=\"" . $pane->ButtonOnClientClick . "\""); + } + } + echo(">"); + + if ($pane->ImageURL != null) echo("ImageURL) . "\" />"); + echo("" . $pane->Title . ""); + echo(""); + } + echo("
"); + echo("
"); + } + } + class WunderbarPanel + { + public $ID; + public $Title; + public $Content; + public $ImageURL; + + public $ButtonTargetURL; + public $ButtonTargetFrame; + public $ButtonOnClientClick; + + public $InhibitDefaultBehavior; + + public function __construct($id, $title, $contentOrContentFunction, $imageURL = null, $buttonTargetURL = null, $buttonOnClientClick = null, $inhibitDefaultBehavior = false) + { + $this->ID = $id; + $this->Title = $title; + $this->Content = $contentOrContentFunction; + $this->ImageURL = $imageURL; + + $this->ButtonTargetURL = $buttonTargetURL; + $this->ButtonOnClientClick = $buttonOnClientClick; + + $this->InhibitDefaultBehavior = $inhibitDefaultBehavior; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebNamespaceReference.inc.php b/app/lib/phast/WebNamespaceReference.inc.php new file mode 100644 index 0000000..c49a746 --- /dev/null +++ b/app/lib/phast/WebNamespaceReference.inc.php @@ -0,0 +1,17 @@ +TagPrefix = $tagPrefix; + $this->NamespacePath = $namespacePath; + $this->NamespaceURL = $namespaceURL; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebOpenGraphSettings.inc.php b/app/lib/phast/WebOpenGraphSettings.inc.php new file mode 100644 index 0000000..d560e1a --- /dev/null +++ b/app/lib/phast/WebOpenGraphSettings.inc.php @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/app/lib/phast/WebPage.inc.php b/app/lib/phast/WebPage.inc.php new file mode 100644 index 0000000..b99d2bd --- /dev/null +++ b/app/lib/phast/WebPage.inc.php @@ -0,0 +1,1228 @@ +Controls; + if ($this->MasterPage != null) + { + $controls = $this->MergeMasterPageControls($this->MasterPage->Controls); + } + return $controls; + } + + /** + * The WebPage from which this WebPage inherits. + * @var WebPage + */ + public $MasterPage; + + /** + * The metadata associated with this WebPage. + * @var WebPageMetadata[] + */ + public $Metadata; + + /** + * The references to XML namespaces and Phast elements associated with this WebPage. + * @var WebNamespaceReference[] + */ + public $References; + + public $ResourceLinks; + public $Scripts; + /** + * The cascading style sheets associated with this WebPage. + * @var WebStyleSheet[] + */ + public $StyleSheets; + public $Styles; + /** + * Variables that are defined on the client, manifested as HTML INPUT elements and transmitted via POST. + * @var WebVariable[] + */ + public $ClientVariables; + /** + * Variables that are defined on the server, invisible to the client. + * @var WebVariable[] + */ + public $ServerVariables; + /** + * Variables that are defined in the virtual path (for example /path/to/$(variable) ) + * @var WebVariable[] + */ + public $PathVariables; + public $OpenGraph; + + /** + * When true, omits the DOCTYPE HTML declaration at the beginning of the document to be compatible with + * older Web browsers. + * @var boolean + */ + public $UseCompatibleRenderingMode; + + /** + * Specifies whether the request for this page is for partial content only. + * @var boolean + */ + public $IsPartial; + + /** + * Retrieves the control with the specified ID in this control's child control collection. + * @param string $id The ID of the control to search for. + * @return WebControl|NULL The control with the specified ID, or null if no control with the specified ID was found. + */ + public function GetControlByID($id, $recurse = true) + { + $ctls = $this->GetAllControls(); + foreach ($ctls as $ctl) + { + if ($ctl->ID == $id) return $ctl; + if ($recurse) + { + $ctl1 = $ctl->GetControlByID($id, true); + if ($ctl1 != null) return $ctl1; + } + } + + trigger_error("Control with ID '" . $id . "' not found"); + return null; + } + + /** + * + * @param MarkupElement $element + * @param PhastParser $parser + * @return WebPage + */ + public static function FromMarkup($element, $parser) + { + $page = new WebPage(); + + $attEnableTenantedHosting = $element->GetAttribute("EnableTenantedHosting"); + if ($attEnableTenantedHosting != null) + { + $page->EnableTenantedHosting = ($attEnableTenantedHosting->Value == "true"); + } + $attFileName = $element->GetAttribute("FileName"); + if ($attFileName != null) + { + $page->FileName = $attFileName->Value; + } + $attrMasterPageFileName = $element->GetAttribute("MasterPageFileName"); + if ($attrMasterPageFileName != null) + { + $page->MasterPage = $parser->GetMasterPageByFileName($attrMasterPageFileName->Value); + } + $attTitle = $element->GetAttribute("Title"); + if ($attTitle != null) + { + $page->Title = $attTitle->Value; + } + $attUseCompatibleRenderingMode = $element->GetAttribute("UseCompatibleRenderingMode"); + if ($attUseCompatibleRenderingMode != null) + { + $page->UseCompatibleRenderingMode = ($attUseCompatibleRenderingMode->Value == "true"); + } + + $tagScripts = $element->GetElement("Scripts"); + if ($tagScripts != null) + { + foreach ($tagScripts->Elements as $elem) + { + if (get_class($elem) != "UniversalEditor\\ObjectModels\\Markup\\MarkupTagElement") continue; + + $attContentType = $elem->GetAttribute("ContentType"); + $contentType = "text/javascript"; + if ($attContentType != null) $contentType = $attContentType->Value; + + $page->Scripts[] = new WebScript($elem->GetAttribute("FileName")->Value, $contentType); + } + } + $tagStyleSheets = $element->GetElement("StyleSheets"); + if ($tagStyleSheets != null) + { + foreach ($tagStyleSheets->Elements as $elem) + { + if (get_class($elem) != "UniversalEditor\\ObjectModels\\Markup\\MarkupTagElement") continue; + + $attFileName = $elem->GetAttribute("FileName"); + if ($attFileName == null) continue; + + $page->StyleSheets[] = new WebStyleSheet($attFileName->Value); + } + } + $tagVariables = $element->GetElement("ClientVariables"); + if ($tagVariables != null) + { + foreach ($tagVariables->Elements as $elem) + { + if (get_class($elem) != "UniversalEditor\\ObjectModels\\Markup\\MarkupTagElement") continue; + + $attName = $elem->GetAttribute("Name"); + if ($attName == null) continue; + + $value = ""; + $attValue = $elem->GetAttribute("Value"); + if ($attValue != null) $value = $attValue->Value; + + $page->ClientVariables[] = new WebVariable($attName->Value, $value); + } + } + $tagVariables = $element->GetElement("ServerVariables"); + if ($tagVariables != null) + { + foreach ($tagVariables->Elements as $elem) + { + if (get_class($elem) != "UniversalEditor\\ObjectModels\\Markup\\MarkupTagElement") continue; + + $attName = $elem->GetAttribute("Name"); + if ($attName == null) continue; + + $value = ""; + $attValue = $elem->GetAttribute("Value"); + if ($attValue != null) $value = $attValue->Value; + + $page->ServerVariables[] = new WebVariable($attName->Value, $value); + } + } + + $tagReferences = $element->GetElement("References"); + if ($tagReferences != null) + { + foreach ($tagReferences->Elements as $elem) + { + if (get_class($elem) != "UniversalEditor\\ObjectModels\\Markup\\MarkupTagElement") continue; + + $attTagPrefix = $elem->GetAttribute("TagPrefix"); + if ($attTagPrefix == null) continue; + + $attNamespacePath = $elem->GetAttribute("NamespacePath"); + if ($attNamespacePath == null) continue; + + $attNamespaceURL = $elem->GetAttribute("NamespaceURL"); + $namespaceURL = ""; + if ($attNamespaceURL != null) $namespaceURL = $attNamespaceURL->Value; + + $page->References[] = new WebNamespaceReference($attTagPrefix->Value, $attNamespacePath->Value, $namespaceURL); + } + } + + $references = $page->References; + if ($page->MasterPage != null) + { + $references = $page->MasterPage->References; + foreach ($page->References as $reference) + { + $references[] = $reference; + } + } + foreach ($references as $reference) + { + ControlLoader::$Namespaces[$reference->TagPrefix] = $reference->NamespacePath; + } + + $tagContent = $element->GetElement("Content"); + if ($tagContent != null) + { + foreach ($tagContent->Elements as $elem) + { + ControlLoader::LoadControl($elem, $page); + } + } + + $attrCssClass = $element->GetAttribute("CssClass"); + if ($attrCssClass != null) + { + $page->ClassList[] = $attrCssClass->Value; + } + + $attrCodeBehindClassName = $element->GetAttribute("CodeBehindClassName"); + if ($attrCodeBehindClassName != null) + { + $page->CodeBehindClassName = $attrCodeBehindClassName->Value; + + if (class_exists($page->CodeBehindClassName)) + { + $page->ClassReference = new $page->CodeBehindClassName(); + $page->ClassReference->Page = $page; + $page->IsPostback = ($_SERVER["REQUEST_METHOD"] == "POST"); + + if (method_exists($page->ClassReference, "OnClassLoaded")) + { + $page->ClassReference->OnClassLoaded(EventArgs::GetEmptyInstance()); + } + else + { + System::WriteErrorLog("Code-behind for '" . $page->CodeBehindClassName . "' does not define an 'OnClassLoaded' entry point"); + } + } + else + { + System::WriteErrorLog("Code-behind for '" . $page->CodeBehindClassName . "' not found"); + } + } + return $page; + } + + /** + * Creates the metadata tag for the given metadata. + * @param WebPageMetadata $metadata + * @return HTMLControl The HTMLControl that represents the META HTML tag referencing the given metadata. + */ + public static function CreateMetaTag(WebPageMetadata $metadata) + { + $tag = new HTMLControl(); + $tag->TagName = "meta"; + $tag->HasContent = false; + + switch ($metadata->Type) + { + case WebPageMetadataType::Name: + { + $tag->Attributes[] = new WebControlAttribute("name", $metadata->Name); + break; + } + case WebPageMetadataType::HTTPEquivalent: + { + $tag->Attributes[] = new WebControlAttribute("http-equiv", $metadata->Name); + break; + } + case WebPageMetadataType::Property: + { + $tag->Attributes[] = new WebControlAttribute("property", $metadata->Name); + break; + } + } + + $tag->Attributes[] = new WebControlAttribute("content", $metadata->Content); + return $tag; + } + /** + * Creates the resource link tag for the given resource link. + * @param WebResourceLink $link + * @return HTMLControl The HTMLControl that represents the LINK HTML tag referencing the given resource link. + */ + public static function CreateResourceLinkTag(WebResourceLink $link) + { + $tag = new HTMLControl(); + $tag->TagName = "link"; + $tag->HasContent = false; + $tag->Attributes[] = new WebControlAttribute("rel", $link->Relationship); + if ($link->ContentType != null) + { + $tag->Attributes[] = new WebControlAttribute("type", $link->ContentType); + } + $tag->Attributes[] = new WebControlAttribute("href", System::ExpandRelativePath($link->URL)); + return $tag; + } + public static function CreateScriptTag(WebScript $script) + { + $tag= new HTMLControl(); + $tag->TagName = "script"; + if ($script->ContentType != "") + { + $tag->Attributes[] = new WebControlAttribute("type", $script->ContentType); + } + if ($script->Content != "") + { + $tag->InnerHTML = $script->Content; + } + if ($script->FileName != "") + { + $tag->Attributes[] = new WebControlAttribute("src", System::ExpandRelativePath($script->FileName)); + } + return $tag; + } + /** + * Creates the style sheet tag for the given style sheet. + * @param WebStyleSheet $stylesheet + * @return HTMLControl The HTMLControl that represents the LINK HTML tag referencing the given style sheet. + */ + public static function CreateStyleSheetTag(WebStyleSheet $stylesheet) + { + return WebPage::CreateResourceLinkTag(new WebResourceLink($stylesheet->FileName, "stylesheet", (($stylesheet->ContentType == "") ? "text/css" : $stylesheet->ContentType))); + } + + public function __construct() + { + $this->BreadcrumbItems = array(); + $this->EnableTenantedHosting = true; + $this->Metadata = array(); + $this->OpenGraph = new WebOpenGraphSettings(); + $this->ResourceLinks = array(); + $this->ClassList = array(); + $this->Enabled = true; + $this->MasterPage = null; + $this->References = array(); + $this->Scripts = array(); + $this->StyleSheets = array(); + $this->Styles = array(); + + $this->ClientVariables = array(); + $this->PathVariables = array(); + $this->ServerVariables = array(); + + $this->UseCompatibleRenderingMode = false; + + $this->IsPartial = isset($_GET["partial"]); + + $ce = new CancelEventArgs(); + $this->OnCreating($ce); + if ($ce->Cancel) return; + + foreach ($this->ClientVariables as $variable) + { + if (isset($_POST["WebPageVariable_" . $variable->Name . "_Value"])) + { + $variable->Value = $_POST["WebPageVariable_" . $variable->Name . "_Value"]; + } + if (isset($_POST["WebPageVariable_" . $variable->Name . "_IsSet"])) + { + $variable->IsSet = $_POST["WebPageVariable_" . $variable->Name . "_IsSet"]; + } + } + + $this->OnCreated(EventArgs::GetEmptyInstance()); + } + + private $isInitialized; + + public function Initialize($renderingPage = null) + { + if ($this->isInitialized) return true; + if ($renderingPage == null) $renderingPage = $this; + + if ($this->MasterPage != null) + { + if (!$this->MasterPage->Initialize($this)) return false; + } + + $initializingEventArgs = new CancelEventArgs(); + $initializingEventArgs->RenderingPage = $renderingPage; + + $initializedEventArgs = new EventArgs(); + $initializedEventArgs->RenderingPage = $renderingPage; + + if (method_exists($this, "OnInitializing")) + { + $this->OnInitializing($initializingEventArgs); + if ($initializingEventArgs->Cancel) return false; + } + $initializingEventArgs->Cancel = false; + + if ($this->ClassReference != null) + { + if (method_exists($this->ClassReference, "OnInitializing")) + { + $this->ClassReference->OnInitializing($initializingEventArgs); + if ($initializingEventArgs->Cancel) return false; + } + if (method_exists($this->ClassReference, "OnInitialized")) + { + $this->ClassReference->OnInitialized($initializedEventArgs); + } + } + + if (method_exists($this, "OnInitialized")) + { + $this->OnInitialized($initializedEventArgs); + } + $this->isInitialized = true; + return true; + } + + protected function OnInitializing(CancelEventArgs $e) + { + + } + protected function OnInitialized(EventArgs $e) + { + + } + + /** + * The function called before the page constructor is called. + * @param CancelEventArgs $e The arguments for this event handler. + */ + protected function OnCreating(CancelEventArgs $e) + { + + } + /** + * The function called after the page constructor has completed. + * @param EventArgs $e The arguments for this event handler. + */ + protected function OnCreated(EventArgs $e) + { + + } + + /** + * The function called before the page has started rendering. + * @param RenderedEventArgs $e The arguments for this event handler. + */ + protected function OnRendering(RenderingEventArgs $e) + { + + } + /** + * The function called after the page has completely rendered. + * @param RenderedEventArgs $e The arguments for this event handler. + */ + protected function OnRendered(RenderedEventArgs $e) + { + + } + + /** + * Performs any necessary processing before the main content of the Web page. Designed for use by page developers. + */ + protected function BeforeContent() + { + + } + /** + * Renders the main content of the Web page. Designed for use by page developers. + */ + protected function RenderContent() + { + + } + /** + * Performs any necessary processing after the main content of the Web page. Designed for use by page developers. + */ + protected function AfterContent() + { + + } + + /** + * This function is called before the content for a full page is generated. To generate a partial page, pass + * "partial" in the query string. + */ + protected function BeforeFullContent() + { + + } + /** + * This function is called after the content for a full page is generated. To generate a partial page, pass + * "partial" in the query string. + */ + protected function AfterFullContent() + { + + } + + /** + * Determines if the WebPageVariable with the given name exists on this WebPage. + * @param string $name The name of the WebPageVariable to search for. + * @return boolean True if the WebPageVariable with the given name exists on this WebPage; false otherwise. + * @see WebPageVariable + */ + public function HasPathVariable($name) + { + return ($this->GetPathVariable($name) != null); + } + + /** + * Retrieves the WebPageVariable with the given name associated with this WebPage. + * @param string $name The name of the WebPageVariable to return. + * @return WebPageVariable|NULL The WebPageVariable with the given name, or NULL if no WebPageVariable with the given name is defined for this WebPage. + */ + public function GetPathVariable($name) + { + foreach ($this->PathVariables as $variable) + { + if ($variable->Name == $name) return $variable; + } + return null; + } + /** + * Retrieves the string value for the WebPageVariable with the given name associated with this WebPage. + * @param string $name The name of the WebPageVariable whose value is to be returned. + * @return string The value of the WebPageVariable with the given name, or $defaultValue (default: null) if no WebPageVariable with the given name is defined for this WebPage. + */ + public function GetPathVariableValue($name, $defaultValue = null) + { + $variable = $this->GetPathVariable($name); + if ($variable == null) return $defaultValue; + return $variable->Value; + } + + /** + * Retrieves the WebPageVariable with the given name associated with this WebPage. + * @param string $name The name of the WebPageVariable to return. + * @return WebPageVariable|NULL The WebPageVariable with the given name, or NULL if no WebPageVariable with the given name is defined for this WebPage. + */ + public function GetClientVariable($name) + { + foreach ($this->ClientVariables as $variable) + { + if ($variable->Name == $name) return $variable; + } + return null; + } + /** + * Retrieves the string value for the WebPageVariable with the given name associated with this WebPage. + * @param string $name The name of the WebPageVariable whose value is to be returned. + * @return string The value of the WebPageVariable with the given name, or the empty string ("") if no WebPageVariable with the given name is defined for this WebPage. + */ + public function GetClientVariableValue($name) + { + $variable = $this->GetClientVariable($name); + if ($variable == null) return null; + return $variable->Value; + } + + /** + * Retrieves the WebPageVariable with the given name associated with this WebPage. + * @param string $name The name of the WebPageVariable to return. + * @return WebPageVariable|NULL The WebPageVariable with the given name, or NULL if no WebPageVariable with the given name is defined for this WebPage. + */ + public function GetServerVariable($name) + { + foreach ($this->ServerVariables as $variable) + { + if ($variable->Name == $name) return $variable; + } + return null; + } + /** + * Retrieves the string value for the WebPageVariable with the given name associated with this WebPage. + * @param string $name The name of the WebPageVariable whose value is to be returned. + * @return string The value of the WebPageVariable with the given name, or the empty string ("") if no WebPageVariable with the given name is defined for this WebPage. + */ + public function GetServerVariableValue($name) + { + $variable = $this->GetServerVariable($name); + if ($variable == null) return null; + return $variable->Value; + } + + /** + * Updates the WebPageVariable with the given name associated with this WebPage. + * @param string $name The name of the WebPageVariable to update. + * @param string $value The value to set for the specified WebPageVariable. + * @param boolean $autoDeclare True if the variable should be created if it doesn't exist; false if the function should fail. + * @return boolean True if the variable was updated successfully; false otherwise. + */ + public function SetVariableValue($name, $value, $autoDeclare = false) + { + $variable = $this->GetVariable($name); + if ($variable == null) + { + if (!$autoDeclare) return false; + + $variable = new WebPageVariable($name, $value, true); + $this->Variables[] = $variable; + return true; + } + $variable->Value = $value; + return true; + } + /** + * Determines if a WebPageVariable with the given name is defined on this WebPage. + * @param string $name The name of the WebPageVariable to search for. + * @return boolean True if a WebPageVariable with the given name is defined on this WebPage; false if not. + */ + public function IsVariableDefined($name) + { + $variable = $this->GetVariable($name); + if ($variable == null) return false; + return true; + } + /** + * Determines if a WebPageVariable with the given name has a value (is not null) on this WebPage. + * @param string $name The name of the WebPageVariable to search for. + * @return boolean True if a WebPageVariable with the given name is defined and not null on this WebPage; false if either the variable is not defined or the variable is defined but does not have a value. + */ + public function IsClientVariableSet($name) + { + $variable = $this->GetClientVariable($name); + if ($variable == null) return false; + return ($variable->IsSet == "true"); + } + + /** + * Determines if a WebPageVariable with the given name has a value (is not null) on this WebPage. + * @param string $name The name of the WebPageVariable to search for. + * @return boolean True if a WebPageVariable with the given name is defined and not null on this WebPage; false if either the variable is not defined or the variable is defined but does not have a value. + */ + public function IsServerVariableSet($name) + { + $variable = $this->GetServerVariable($name); + if ($variable == null) return false; + return ($variable->IsSet == "true"); + } + + /** + * Renders the specified WebControl as a JSON control. + * @param WebControl $ctl + */ + private function RenderJSONControl($ctl) + { + $ary = array + ( + "ClassName" => get_class($ctl) + ); + + $count = count($ctl->Attributes); + if ($count > 0) + { + $attrs = array(); + for ($i = 0; $i < $count; $i++) + { + $attr = $ctl->Attributes[$i]; + $attrs[] = array + ( + "Name" => $attr->Name, + "Value" => $attr->Value + ); + } + $ary["Attributes"] = $attrs; + } + + $reflect = new \ReflectionClass($ctl); + $props = $reflect->getProperties(\ReflectionProperty::IS_PUBLIC); + $count = count($props); + + if ($count > 0) + { + $attrs = array(); + $props2 = array(); + foreach($props as $prop) + { + $key = $prop->getName(); + $value = $prop->getValue($ctl); + if ($value == null) continue; + + if (is_array($value)) + { + foreach ($value as $val) + { + if (is_object($val)) + { + $val->ClassName = get_class($val); + } + } + } + + $props2[$key] = $value; + } + $count = count($props2); + + if ($count > 0) + { + foreach ($props2 as $key => $value) + { + if ($key == "ParentObject") + { + $key = "ParentObjectID"; + $value = $value->ID; + } + $attrs[] = array + ( + "Name" => $key, + "Value" => $value + ); + } + $ary["Properties"] = $attrs; + } + } + return $ary; + } + + public function Render() + { + if (!$this->Initialize()) + { + trigger_error("Could not initialize the WebPage"); + return; + } + + switch (System::$WebPageFormat) + { + case WebPageFormat::JavaScript: + { + $filenames = array(); + + $filename = System::$CurrentPage->PhysicalFileName . ".js"; + if (file_exists($filename)) $filenames[] = $filename; + + $mp = System::$CurrentPage->MasterPage; + while ($mp != null) + { + if (file_exists($mp->PhysicalFileName . ".js")) + { + array_unshift($filenames, $mp->PhysicalFileName . ".js"); + } + $mp = $mp->MasterPage; + } + + if (count($filenames) > 0) + { + header("HTTP/1.1 200 OK"); + header("Content-Type: text/javascript"); + + foreach ($filenames as $filen) + { + readfile($filen); + echo("\r\n\r\n"); + } + } + else + { + header("HTTP/1.1 404 Not Found"); + } + return; + } + case WebPageFormat::JSON: + { + header("Content-Type: application/json; charset=utf-8"); + + $ary = array + ( + "FileName" => $this->FileName + ); + + $count = count($this->Controls); + if ($count > 0) + { + $ctls = array(); + for($i = 0; $i < $count; $i++) + { + $ctl = $this->Controls[$i]; + $ctls[] = $this->RenderJSONControl($ctl); + } + $ary["Controls"] = $ctls; + } + + echo(json_encode($ary)); + return; + } + case WebPageFormat::XML: + { + header("Content-Type: application/xml; charset=utf-8"); + echo (""); + + $tagPage = new HTMLControl("Page"); + $tagPage->Attributes[] = new WebControlAttribute("FileName", $this->FileName); + + if (count($this->Controls) > 0) + { + $tagControls = new HTMLControl("Controls"); + foreach ($this->Controls as $ctl) + { + $tagControl = new HTMLControl("Control"); + $tagControl->Attributes[] = new WebControlAttribute("ID", $ctl->ID); + $tagControl->Attributes[] = new WebControlAttribute("ClassName", get_class($ctl)); + + if (count($ctl->Attributes) > 0) + { + $tagAttributes = new HTMLControl("Attributes"); + foreach ($ctl->Attributes as $attr) + { + $tagAttribute = new HTMLControl("Attribute"); + $tagAttribute->Attributes[] = new WebControlAttribute("Name", $attr->Name); + $tagAttribute->Attributes[] = new WebControlAttribute("Value", $attr->Value); + $tagAttributes[] = $tagAttribute; + } + $tagControl->Controls[] = $tagAttributes; + } + + $tagControls->Controls[] = $tagControl; + } + $tagPage->Controls[] = $tagControls; + } + + $tagPage->Render(); + return; + } + } + + header("Content-Type: text/html; charset=utf-8"); + if (!$this->IsPartial) + { + + if (!$this->UseCompatibleRenderingMode) + { + echo("\r\n"); + } + + $tagHTML = new HTMLControl(); + $tagHTML->TagName = "html"; + + $tagHEAD = new HTMLControl(); + $tagHEAD->TagName = "head"; + + if ($this->Title != "") + { + $tagTITLE = new HTMLControl(); + $tagTITLE->TagName = "title"; + $tagTITLE->InnerHTML = $this->Title; + $tagHEAD->Controls[] = $tagTITLE; + } + + // ========== BEGIN: Metadata ========== + $items = array(); + $items[] = new WebPageMetadata("Content-Type", "text/html; charset=utf-8", true); + $items[] = new WebPageMetadata("viewport", "width=device-width,minimum-scale=1.0"); + $items[] = new WebPageMetadata("X-UA-Compatible", "IE=edge", WebPageMetadataType::HTTPEquivalent); + + if ($this->MasterPage != null) + { + foreach ($this->MasterPage->Metadata as $item) + { + $items[] = $item; + } + } + foreach ($this->Metadata as $item) + { + $items[] = $item; + } + foreach ($items as $item) + { + $tagHEAD->Controls[] = WebPage::CreateMetaTag($item); + } + // ========== END: Metadata ========== + + // ========== BEGIN: Resource Links ========== + $items = array(); + $items[] = new WebResourceLink($this->GetCanonicalFileName(), "canonical"); + + if ($this->MasterPage != null) + { + foreach ($this->MasterPage->ResourceLinks as $item) + { + $items[] = $item; + } + } + foreach ($this->ResourceLinks as $item) + { + $items[] = $item; + } + foreach ($items as $item) + { + $tagHEAD->Controls[] = WebPage::CreateResourceLinkTag($item); + } + // ========== END: Resource Links ========== + + // ========== BEGIN: StyleSheets ========== + $items = array(); + $items[] = new WebStyleSheet("$(Configuration:System.StaticPath)/StyleSheets/Main.css"); + + if ($this->MasterPage != null) + { + foreach ($this->MasterPage->StyleSheets as $item) + { + $items[] = $item; + } + } + foreach ($this->StyleSheets as $item) + { + $items[] = $item; + } + foreach ($items as $item) + { + $tagHEAD->Controls[] = WebPage::CreateStyleSheetTag($item); + } + // ========== END: StyleSheets ========== + + // ========== BEGIN: Scripts ========== + $items = array(); + + // Bring in Phast first + $items[] = WebScript::FromFile("$(Configuration:System.StaticPath)/Scripts/System.js.php", "text/javascript"); + $items[] = WebScript::FromContent("System.IsTenantedHostingEnabled = function() { return " . (System::$EnableTenantedHosting ? "true" : "false") . "; }; System.GetTenantName = function() { return \"" . System::GetTenantName() . "\"; };", "text/javascript"); + + $filename = System::$CurrentPage->PhysicalFileName . ".js"; + if (file_exists($filename)) + { + $items[] = new WebScript(System::GetRequestURL() . ".js"); + } + else + { + // echo(""); + } + + // Update the Phast application base path + $item = new WebScript(); + $item->ContentType = "text/javascript"; + $item->Content = "System.BasePath = \"" . System::GetConfigurationValue("Application.BasePath") . "\";"; + $items[] = $item; + + if ($this->MasterPage != null) + { + foreach ($this->MasterPage->Scripts as $item) + { + $items[] = $item; + } + } + foreach ($this->Scripts as $item) + { + $items[] = $item; + } + foreach ($items as $item) + { + $tagHEAD->Controls[] = WebPage::CreateScriptTag($item); + } + // ========== END: Scripts ========== + + // ========== BEGIN: OpenGraph Support ========== + if ($this->OpenGraph->Enabled) + { + $og_title = $this->OpenGraph->Title; + $og_type = "other"; + $og_url = $this->OpenGraph->URL; + $og_site_name = $this->OpenGraph->Title; + $og_image = $this->OpenGraph->ImageURL; + $og_description = $this->OpenGraph->Description; + + if ($og_title != null) $og_title = $this->Title; + + $tagHEAD->Controls[] = WebPage::CreateMetaTag(new WebPageMetadata("og:title", $og_title, WebPageMetadataType::Property)); + $tagHEAD->Controls[] = WebPage::CreateMetaTag(new WebPageMetadata("og:type", $og_type, WebPageMetadataType::Property)); + $tagHEAD->Controls[] = WebPage::CreateMetaTag(new WebPageMetadata("og:url", $og_url, WebPageMetadataType::Property)); + $tagHEAD->Controls[] = WebPage::CreateMetaTag(new WebPageMetadata("og:site_name", $og_site_name, WebPageMetadataType::Property)); + $tagHEAD->Controls[] = WebPage::CreateMetaTag(new WebPageMetadata("og:image", $og_image, WebPageMetadataType::Property)); + $tagHEAD->Controls[] = WebPage::CreateMetaTag(new WebPageMetadata("og:description", $og_description, WebPageMetadataType::Property)); + } + // ========== END: OpenGraph Support ========== + + $tagHTML->Controls[] = $tagHEAD; + + $tagBODY = new HTMLControl(); + $tagBODY->TagName = "body"; + + $divThrobber = new HTMLControl("div"); + $divThrobber->CssClass = "Throbber"; + $tagBODY->Controls[] = $divThrobber; + + $classList = array(); + if (is_array($this->ClassList)) + { + foreach ($this->ClassList as $item) + { + $classList[] = $item; + } + } + + $tagBODY->ClassList = $classList; + $tagBODY->StyleRules = $this->Styles; + + $this->BeforeClientVariablesInitialize(); + if (count($this->ClientVariables) > 0) + { + $form = new Form(); + $form->ClientID = "WebPageForm"; + $form->Method = FormMethod::Post; + foreach ($this->ClientVariables as $variable) + { + $input = new HTMLControlInput(); + $input->Type = HTMLControlInputType::Hidden; + $input->ClientID = "WebPageVariable_" . $variable->Name . "_Value"; + $input->Name = "WebPageVariable_" . $variable->Name . "_Value"; + if (isset($_POST["WebPageVariable_" . $variable->Name . "_Value"])) + { + $variable->Value = $_POST["WebPageVariable_" . $variable->Name . "_Value"]; + } + $input->Value = $variable->Value; + $form->Controls[] = $input; + + $input = new HTMLControlInput(); + $input->Type = HTMLControlInputType::Hidden; + $input->ClientID = "WebPageVariable_" . $variable->Name . "_IsSet"; + $input->Name = "WebPageVariable_" . $variable->Name . "_IsSet"; + if (isset($_POST["WebPageVariable_" . $variable->Name . "_IsSet"])) + { + $variable->IsSet = $_POST["WebPageVariable_" . $variable->Name . "_IsSet"]; + } + $input->Value = (($variable->IsSet == "true") ? "true" : "false"); + $form->Controls[] = $input; + } + $tagBODY->Controls[] = $form; + } + $this->AfterClientVariablesInitialize(); + + $re = new RenderingEventArgs(RenderMode::Complete); + $this->OnRendering($re); + if ($re->Cancel) return; + } + else + { + $re = new RenderingEventArgs(RenderMode::Partial); + $this->OnRendering($re); + if ($re->Cancel) return; + } + $re = new RenderingEventArgs(RenderMode::Any); + $this->OnRendering($re); + if ($re->Cancel) return; + + if (is_callable($this->Content) && count($this->Controls) == 0) + { + $tagBODY->Content = $this->Content; + } + else if (!is_callable($this->Content) && count($this->Controls) > 0) + { + $controls = $this->Controls; + if ($this->MasterPage != null) + { + $controls = $this->MergeMasterPageControls($this->MasterPage->Controls); + } + + foreach ($controls as $ctl) + { + $tagBODY->Controls[] = $ctl; + } + } + $tagHTML->Controls[] = $tagBODY; + $tagHTML->Render(); + + $this->OnRendered(new RenderedEventArgs(RenderMode::Any)); + $renderedEventArgs = null; + if ($this->IsPartial) + { + $renderedEventArgs = new RenderedEventArgs(RenderMode::Partial); + } + else + { + $renderedEventArgs = new RenderedEventArgs(RenderMode::Complete); + } + $this->OnRendered($renderedEventArgs); + + if ($this->ClassReference != null) + { + if (method_exists($this->ClassReference, "OnRendered")) + { + $this->ClassReference->OnRendered($renderedEventArgs); + } + } + } + + private function MergeMasterPageControls($controls = null) + { + if ($controls == null) $controls = array(); + $newControls = array(); + if ($this->MasterPage != null) + { + foreach ($controls as $control) + { + $ctl = clone $control; + + if (get_class($ctl) == "Phast\\WebControls\\SectionPlaceholder") + { + $pageControls = $this->Controls; + foreach ($pageControls as $pageControl) + { + $pageCtl = clone $pageControl; + if (get_class($pageCtl) != "Phast\\WebControls\\Section") continue; + + if ($pageCtl->PlaceholderID != $ctl->ID) continue; + $newControls[] = $pageCtl; + } + } + else + { + $ctl->Controls = $this->MergeMasterPageControls($ctl->Controls); + $newControls[] = $ctl; + } + } + } + return $newControls; + } + + protected function BeforeClientVariablesInitialize() + { + } + protected function AfterClientVariablesInitialize() + { + } + } +?> diff --git a/app/lib/phast/WebPageCommand.inc.php b/app/lib/phast/WebPageCommand.inc.php new file mode 100644 index 0000000..50b21de --- /dev/null +++ b/app/lib/phast/WebPageCommand.inc.php @@ -0,0 +1,17 @@ +Title = $title; + $this->NavigateURL = $navigateURL; + $this->OnClientClick = $onClientClick; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebPageFormat.inc.php b/app/lib/phast/WebPageFormat.inc.php new file mode 100644 index 0000000..b3a9adb --- /dev/null +++ b/app/lib/phast/WebPageFormat.inc.php @@ -0,0 +1,31 @@ + \ No newline at end of file diff --git a/app/lib/phast/WebPageMessage.inc.php b/app/lib/phast/WebPageMessage.inc.php new file mode 100644 index 0000000..38865aa --- /dev/null +++ b/app/lib/phast/WebPageMessage.inc.php @@ -0,0 +1,23 @@ +Message = $message; + $this->Severity = $severity; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebPageMetadata.inc.php b/app/lib/phast/WebPageMetadata.inc.php new file mode 100644 index 0000000..cd247dd --- /dev/null +++ b/app/lib/phast/WebPageMetadata.inc.php @@ -0,0 +1,40 @@ +Name = $name; + $this->Content = $content; + $this->Type = $type; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebPageVariable.inc.php b/app/lib/phast/WebPageVariable.inc.php new file mode 100644 index 0000000..d624ced --- /dev/null +++ b/app/lib/phast/WebPageVariable.inc.php @@ -0,0 +1,17 @@ +Name = $name; + $this->Value = $value; + $this->IsSet = $isSet; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebResourceLink.inc.php b/app/lib/phast/WebResourceLink.inc.php new file mode 100644 index 0000000..59897dd --- /dev/null +++ b/app/lib/phast/WebResourceLink.inc.php @@ -0,0 +1,17 @@ +URL = $url; + $this->Relationship = $relationship; + $this->ContentType = $contentType; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebScript.inc.php b/app/lib/phast/WebScript.inc.php new file mode 100644 index 0000000..d57e110 --- /dev/null +++ b/app/lib/phast/WebScript.inc.php @@ -0,0 +1,32 @@ +Content = $Content; + return $script; + } + + public function __construct($fileName = null, $contentType = null) + { + $this->FileName = $fileName; + if ($contentType == null) + { + $contentType = "text/javascript"; + } + $this->ContentType = $contentType; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebStyleSheet.inc.php b/app/lib/phast/WebStyleSheet.inc.php new file mode 100644 index 0000000..d1084f8 --- /dev/null +++ b/app/lib/phast/WebStyleSheet.inc.php @@ -0,0 +1,26 @@ +FileName = $FileName; + $this->ContentType = $ContentType; + } + } + class WebStyleSheetRule + { + public $Name; + public $Value; + + public function __construct($name, $value) + { + $this->Name = $name; + $this->Value = $value; + } + } +?> \ No newline at end of file diff --git a/app/lib/phast/WebVariable.inc.php b/app/lib/phast/WebVariable.inc.php new file mode 100644 index 0000000..013e813 --- /dev/null +++ b/app/lib/phast/WebVariable.inc.php @@ -0,0 +1,15 @@ +Name = $name; + $this->Value = $value; + } + } +?> diff --git a/app/ui/pages/000-Default.phpx b/app/ui/pages/000-Default.phpx new file mode 100644 index 0000000..2ac13ed --- /dev/null +++ b/app/ui/pages/000-Default.phpx @@ -0,0 +1,11 @@ + + + + +

It works!

+

This is the default page for your Phast application.

+

See how Phast that was? :-)

+
+
+
+
\ No newline at end of file