From e99281261767710f96b9967a0ab03a3ea24a05e0 Mon Sep 17 00:00:00 2001 From: Kevin Chabowski Date: Wed, 28 Dec 2011 14:38:20 +0100 Subject: Initial Commit. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Many files copied from Ratatöskr (maybe there should be some kind of libratatoeskr ?) * Included the Stupid Template Engine * Wrote first version of models.php --- .htaccess | 14 + CONTRIBUTORS | 6 + COPYING | 43 ++ config.php | 10 + db.php | 106 ++++ index.php | 23 + models.php | 388 +++++++++++++ packagelist | 0 pluginpackage.php | 288 ++++++++++ pwhash.php | 74 +++ repometa | 0 stupid_template_engine.php | 1297 ++++++++++++++++++++++++++++++++++++++++++++ templates/.htaccess | 4 + urlprocess.php | 179 ++++++ utils.php | 229 ++++++++ 15 files changed, 2661 insertions(+) create mode 100644 .htaccess create mode 100644 CONTRIBUTORS create mode 100644 COPYING create mode 100644 config.php create mode 100644 db.php create mode 100644 index.php create mode 100644 models.php create mode 100644 packagelist create mode 100644 pluginpackage.php create mode 100644 pwhash.php create mode 100644 repometa create mode 100644 stupid_template_engine.php create mode 100755 templates/.htaccess create mode 100644 urlprocess.php create mode 100644 utils.php diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..7db1e17 --- /dev/null +++ b/.htaccess @@ -0,0 +1,14 @@ +RewriteEngine On +Options FollowSymLinks + +RewriteCond %{REQUEST_FILENAME} -f [OR] +RewriteCond %{REQUEST_FILENAME} -d +RewriteRule ^(.+) - [PT,L] + + +RewriteRule (.*) index.php?action=$1 [QSA,L] + + + Order Allow,Deny + Deny from all + diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 0000000..eb521ac --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,6 @@ +People who have worked on r7r_repo +================================== + +If you modified something, feel free to append your name to this list. + +* Kevin Chabowski diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..abf8589 --- /dev/null +++ b/COPYING @@ -0,0 +1,43 @@ +r7r_repo is licensed under the X11/MIT license for free software: + +Copyright (c) 2011 The Ratatöskr Team + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: The above copyright notice and this +permission notice shall be included in all copies or substantial portions of the +Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +r7r_repo uses many files (db.php, pwhash.php, urlprocess, utils.php, parts of +models.php) of Ratatöskr by the Ratatöskr Team, which ist also licensed under the X11/MIT license for +free software. + +r7r_repo uses the Stupid Template Engine by Kevin Chabowsi which is also licensed unter the +X11/MIT license for free software. + +r7r_repo uses the pluginpackage.php file by the Ratatöskr Team, which is licensed under the WTFPL: + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + +Copyright (C) 2004 Sam Hocevar + +Everyone is permitted to copy and distribute verbatim or modified +copies of this license document, and changing it is allowed as long +as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/config.php b/config.php new file mode 100644 index 0000000..83efe05 --- /dev/null +++ b/config.php @@ -0,0 +1,10 @@ + diff --git a/db.php b/db.php new file mode 100644 index 0000000..6add093 --- /dev/null +++ b/db.php @@ -0,0 +1,106 @@ +, but needs arguments as single array. + * + * Parameters: + * $args - The arguments as an array. + * + * Returns: + * The formatted string. + */ +function qdb_vfmt($args) +{ + global $config; + + if(count($args) < 1) + throw new InvalidArgumentException('Need at least one parameter'); + + $query = $args[0]; + + $data = array_map(function($x) { return is_string($x) ? sqlesc($x) : $x; }, array_slice($args, 1)); + $query = str_replace("PREFIX_", $config["mysql"]["prefix"], $query); + + return vsprintf($query, $data); +} + +/* + * Function: qdb_fmt + * Formats a string like , that means it replaces "PREFIX_" and 's everything before sends everything to vsprintf. + * + * Returns: + * The formatted string. + */ +function qdb_fmt() +{ + return qdb_vfmt(func_get_args()); +} + + +/* + * Function: qdb + * Query Database. + * + * This function replaces mysql_query and should eliminate SQL-Injections. + * Use it like this: + * + * $result = qdb("SELECT `foo` FROM `bar` WHERE `id` = %d AND `baz` = '%s'", 100, "lol"); + * + * It will also replace "PREFIX_" with the prefix defined in 'config.php'. + */ +function qdb() +{ + $query = qdb_vfmt(func_get_args()); + $rv = mysql_query($query); + if($rv === false) + throw new MySQLException(mysql_errno() . ': ' . mysql_error() . (__DEBUG__ ? ("[[FULL QUERY: " . $query . "]]") : "" )); + return $rv; +} + +/* + * Class: MySQLException + * Will be thrown by qdb*, if the query induced an MySQL error. + */ +class MySQLException extends Exception { } + +?> diff --git a/index.php b/index.php new file mode 100644 index 0000000..3ce0daa --- /dev/null +++ b/index.php @@ -0,0 +1,23 @@ + diff --git a/models.php b/models.php new file mode 100644 index 0000000..62e541d --- /dev/null +++ b/models.php @@ -0,0 +1,388 @@ +populate_by_sqlrow($sqlrow); + return $obj; + } +} + +/* SettingsIterator ans Settings copied from Ratatöskr */ + +class SettingsIterator implements Iterator +{ + private $index; + private $keys; + private $settings_obj; + + public function __construct($settings_obj, $keys) + { + $this->index = 0; + $this->settings_obj = $settings_obj; + $this->keys = $keys; + } + + /* Iterator implementation */ + public function current() { return $this->settings_obj[$this->keys[$this->index]]; } + public function key() { return $this->keys[$this->index]; } + public function next() { ++$this->index; } + public function rewind() { $this->index = 0; } + public function valid() { return $this->index < count($this->keys); } +} + +/* + * Class: Settings + * A class that holds the Settings of Ratatöskr. + * You can access settings like an array. + */ +class Settings implements ArrayAccess, IteratorAggregate, Countable +{ + /* Singleton implementation */ + private function __copy() {} + private static $instance = NULL; + /* + * Constructor: get_instance + * Get an instance of this class. + * All instances are equal (ie. this is a singleton), so you can also use + * the global <$ratatoeskr_settings> instance. + */ + public static function get_instance() + { + if(self::$instance === NULL) + self::$instance = new self; + return self::$instance; + } + + private $buffer; + private $to_be_deleted; + private $to_be_created; + private $to_be_updated; + + private function __construct() + { + $this->buffer = array(); + $result = qdb("SELECT `key`, `value` FROM `PREFIX_settings_kvstorage` WHERE 1"); + while($sqlrow = mysql_fetch_assoc($result)) + $this->buffer[$sqlrow["key"]] = unserialize(base64_decode($sqlrow["value"])); + + $this->to_be_created = array(); + $this->to_be_deleted = array(); + $this->to_be_updated = array(); + } + + public function save() + { + foreach($this->to_be_deleted as $k) + qdb("DELETE FROM `PREFIX_settings_kvstorage` WHERE `key` = '%s'", $k); + foreach($this->to_be_updated as $k) + qdb("UPDATE `PREFIX_settings_kvstorage` SET `value` = '%s' WHERE `key` = '%s'", base64_encode(serialize($this->buffer[$k])), $k); + foreach($this->to_be_created as $k) + qdb("INSERT INTO `PREFIX_settings_kvstorage` (`key`, `value`) VALUES ('%s', '%s')", $k, base64_encode(serialize($this->buffer[$k]))); + $this->to_be_created = array(); + $this->to_be_deleted = array(); + $this->to_be_updated = array(); + } + + /* ArrayAccess implementation */ + public function offsetExists($offset) + { + return isset($this->buffer[$offset]); + } + public function offsetGet($offset) + { + return $this->buffer[$offset]; + } + public function offsetSet ($offset, $value) + { + if(!$this->offsetExists($offset)) + { + if(in_array($offset, $this->to_be_deleted)) + { + $this->to_be_updated[] = $offset; + unset($this->to_be_deleted[array_search($offset, $this->to_be_deleted)]); + } + else + $this->to_be_created[] = $offset; + } + elseif((!in_array($offset, $this->to_be_created)) and (!in_array($offset, $this->to_be_updated))) + $this->to_be_updated[] = $offset; + $this->buffer[$offset] = $value; + } + public function offsetUnset($offset) + { + if(in_array($offset, $this->to_be_created)) + unset($this->to_be_created[array_search($offset, $this->to_be_created)]); + else + $this->to_be_deleted[] = $offset; + unset($this->buffer[$offset]); + } + + /* IteratorAggregate implementation */ + public function getIterator() { return new SettingsIterator($this, array_keys($this->buffer)); } + + /* Countable implementation */ + public function count() { return count($this->buffer); } +} + +$settings = Settings::get_instance(); + +/* users */ +class User extends BySQLRowEnabled +{ + private $name; + private $id; + + public $pwhash; + public $isadmin; + + protected function __construct() {} + + protected function populate_by_sqlrow($sqlrow) + { + $this->id = $sqlrow["id"]; + $this->name = $sqlrow["name"]; + $this->pwhash = $sqlrow["pwhash"]; + $this->isadmin = $sqlrow["isadmin"] == 1; + } + + function get_id() { return $this->id; } + function get_name() { return $this->name; } + + public static function create($name) + { + try + { + self::by_name($name); + throw new AlreadyExistsError(); + } + catch(DoesNotExistError $e) + { + $obj = new self; + $obj->name = $name; + $obj->pwhash = ""; + $obj->isadmin = False; + qdb("INSERT INTO `PREFIX_users` (`name`, `pwhash`, isadmin`) VALUES ('%s', '', 0)", $name); + $this->id = mysql_insert_id(); + return $obj; + } + } + + public static function by_id($id) + { + $result = qdb("SELECT `id`, `name`, `pwhash`, `isadmin` FROM `PREFIX_users` WHERE `id` = %d", $id); + $sqlrow = mysql_fetch_assoc($result); + if($sqlrow === False) + throw new DoesNotExistError(); + return self::by_sqlrow($sqlrow); + } + + public static function by_name($name) + { + $result = qdb("SELECT `id`, `name`, `pwhash`, `isadmin` FROM `PREFIX_users` WHERE `name` = '%s'", $name); + $sqlrow = mysql_fetch_assoc($result); + if($sqlrow === False) + throw new DoesNotExistError(); + return self::by_sqlrow($sqlrow); + } + + public static function all() + { + $rv = array(); + $result = qdb("SELECT `id`, `name`, `pwhash`, `isadmin` FROM `PREFIX_users` WHERE 1"); + while($sqlrow = mysql_fetch_assoc($result)) + $rv[] = self::by_sqlrow($sqlrow); + return $rv; + } + + public function get_packages() + { + $rv = array(); + $result = qdb("SELECT `id`, `name`, `user`, `lastversion`, `description`, `lastupdate`, `txtversion` FROM `PREFIX_packages` WHERE `user` = %d", $this->id); + while($sqlrow = mysql_fetch_assoc($result)) + $rv[] = Package::by_sqlrow($result); + return $rv; + } + + public function save() + { + qdb("UPDATE `PREFIX_users` SET `isadmin` = %d, `pwhash` = '%s' WHERE `id` = %d", ($this->isadmin ? 1 : 0), $this->pwhash, $this->id); + } + + public function delete() + { + qdb("DELETE FROM `PREFIX_users` WHERE `id` = %d", $this->id); + } +} + +class Package extends BySQLRowEnabled +{ + private $id; + private $name; + private $user; + + public $lastversion; + public $description; + public $lastupdate; + public $txtversion; + + public function get_id() { return $id; } + public function get_name() { return $name; } + public function get_user() { return $user; } + + protected function __construct() {} + + protected function populate_by_sqlrow($sqlrow) + { + $this->id = $sqlrow["id"]; + $this->name = $sqlrow["name"]; + $this->user = User::by_id($sqlrow["user"]); + $this->lastversion = $sqlrow["lastversion"]; + $this->description = $sqlrow["description"]; + $this->lastupdate = $sqlrow["lastupdate"]; + $this->txtversion = $sqlrow["txtversion"]; + } + + public static function create($name, $user) + { + if(preg_match("/^[0-9a-zA-Z_\\-]+$/", $name) != 1) + throw new InvalidArgumentException("Invalid package name (must be min 1 char, 0-9A-Za-z_-"); + try + { + self::by_name($name); + throw new AlreadyExistsError(); + } + catch(DoesNotExistError $e) + { + $obj = new self; + $obj->name = $name; + $obj->user = $user; + $obj->lastupdate = time(); + $obj->lastversion = 0; + $obj->txtversion = ""; + $obj->description = ""; + + qdb("INSERT INTO `PREFIX_packages` (`name`, `user`, `lastupdate`, `lastversion`, `txtversion`, description`, `description`) VALUES ('%s', %d, UNIX_TIMESTAMP(), 0, '', '')", $name, $user->get_id()); + $obj->id = mysql_insert_id(); + + mkdir(dirname(__FILE__) . "/packages/" . $this->name); + + return $obj; + } + } + + public static function by_id($id) + { + $result = qdb("SELECT `id`, `name`, `user`, `lastversion`, `description`, `lastupdate`, `txtversion` FROM `PREFIX_packages` WHERE `id` = %d", $id); + $sqlrow = mysql_fetch_assoc($result); + if($sqlrow === False) + throw new DoesNotExistError(); + return self::by_sqlrow($sqlrow); + } + + public static function by_name($name) + { + $result = qdb("SELECT `id`, `name`, `user`, `lastversion`, `description`, `lastupdate`, `txtversion` FROM `PREFIX_packages` WHERE `name` = '%s'", $name); + $sqlrow = mysql_fetch_assoc($result); + if($sqlrow === False) + throw new DoesNotExistError(); + return self::by_sqlrow($sqlrow); + } + + public static function update_lists() + { + $packagelist = array(); + $result = qdb("SELECT `id`, `name`, `user`, `lastversion`, `description`, `lastupdate`, `txtversion` FROM `PREFIX_packages` WHERE 1"); + while($sqlrow = mysql_fetch_assoc($result)) + $packagelist[] = array($sqlrow["name"], $sqlrow["lastversion"], $sqlrow["description"]); + file_put_contents(dirname(__FILE__) . "/packagelist", serialize($packagelist)); + } + + public static function all() + { + $rv = array(); + $result = qdb("SELECT `id`, `name`, `user`, `lastversion`, `description`, `lastupdate`, `txtversion` FROM `PREFIX_packages` WHERE 1"); + while($sqlrow = mysql_fetch_assoc($result)) + $rv[] = self::by_sqlrow($sqlrow); + return $rv; + } + + public static function latest() + { + $rv = array(); + $result = qdb("SELECT `id`, `name`, `user`, `lastversion`, `description`, `lastupdate`, `txtversion` FROM `PREFIX_packages` WHERE 1 ORDER BY `lastupdate` DESC LIMIT 0,15"); + while($sqlrow = mysql_fetch_assoc($result)) + $rv[] = self::by_sqlrow($sqlrow); + return $rv; + } + + public static function search($search) + { + $rv = array(); + $result = qdb("SELECT `id`, `name`, `user`, `lastversion`, `description`, `lastupdate`, `txtversion` FROM `PREFIX_packages` WHERE `name` LIKE '%%%s%%' OR `description` LIKE '%%%s%%'", $search, $search); + while($sqlrow = mysql_fetch_assoc($result)) + $rv[] = self::by_sqlrow($sqlrow); + return $rv; + } + + public function newversion($pgk) + { + global $settings; + if($pkg->name != $this->name) + throw new NotAllowedError("Package name not equal."); + if($pkg->versioncount <= $this->lastversion) + throw new NotAllowedError("Older or same version."); + $pkg->updatepath = $settings["root_url"] . "/packages/" . urlencode($this->name) . "/update"; + + $pkg_ser = $pkg->save(); + file_put_contents(dirname(__FILE__) . "/packages/" . urlencode($this>name) . "/versions/" . $pkg->versioncount, $pkg_ser); + file_put_contents(dirname(__FILE__) . "/packages/" . urlencode($this>name) . "/versions/current", $pkg_ser); + $meta = $pkg->extract_meta(); + file_put_contents(dirname(__FILE__) . "/packages/" . urlencode($this>name) . "/meta", serialize($meta)); + + $this->lastversion = $pkg->versioncount; + $this->txtversion = $pkg->versiontext; + $this->description = $pkg->short_description; + $this->lastupdate = time(); + $this->save(); + + $update_info = array( + "current-version" => $this->lastversion, + "dl-path" => $settings["root_url"] . "/packages/" . urlencode($this->name) . "/versions/" . $this->lastversion; + ); + + file_put_contents(dirname(__FILE__) . "/packages/" . urlencode($this>name) . "/update", serialize($update_info)); + + self::update_lists(); + } + + public function save() + { + qdb("UPDATE `PREFIX_packages` SET `lastversion` = %d, `lastupdate` = %d, `txtversion` = '%s', `description` = '%s' WHERE `id` = %d", $this->lastversion, $this->lastupdate, $this->txtversion, $this->description, $this->id); + } + + public function delete() + { + qdb("DELETE FROM `PREFIX_packages` WHERE `id` = %d", $this->id); + delete_directory(dirname(__FILE__) . "/packages/" . $this->name); + self::update_lists(); + } +} + +?> diff --git a/packagelist b/packagelist new file mode 100644 index 0000000..e69de29 diff --git a/pluginpackage.php b/pluginpackage.php new file mode 100644 index 0000000..d8ce170 --- /dev/null +++ b/pluginpackage.php @@ -0,0 +1,288 @@ + $v) + { + $k = "$dir/$k"; + if(is_array($v)) + array2dir($v, $k); + else + file_put_contents($k, $v); + } +} + +/* + * Class: InvalidPackage + * An Exception that 's function can throw, if the package is invalid. + */ +class InvalidPackage extends Exception {} + +/* + * Class: PluginPackage + * A plugin package representation. + */ +class PluginPackage +{ + public static $magic = "R7RPLGPACKV001"; + + /* + * Variables: Mandatory values + * + * $code - The plugin code + * $classname - The name of the plugins main class + * $name - Name of the plugin (must be at least one character, allowed chars: a-z A-Z 0-9 - _) + * $author - The author of the plugin (preferably in the format: Name) + * $versiontext - A text to describe the current version, something like "1.1 Beta" + * $versioncount - A number for this version, should be increased with every release + * $api - The used API version + * $short_description - A short description. + */ + public $code = NULL; + public $classname = NULL; + public $name = NULL; + public $author = NULL; + public $versiontext = NULL; + public $versioncount = NULL; + public $api = NULL; + public $short_description = NULL; + + /* + * Variables: Optional values + * + * $updatepath - A URL that points to a update information resource (serialize'd array("current-version" => VERSIONCOUNT, "dl-path" => DOWNLOAD PATH); will get overwritten/set by the default repository software. + * $web - A URL to the webpage for the plugin. If left empty, the default repository software will set this to the description page of your plugin. + * $license - The license text of your plugin. + * $help - A help / manual (formatted in HTML) for your plugin. + * $custompub - 'd directory that contains custom public(i.e. can later be accessed from the web) data. + * $custompriv - 'd directory that contains custom private data. + * $tpls - 'd directory containing custom STE templates. + */ + public $updatepath = NULL; + public $web = NULL; + public $license = NULL; + public $help = NULL; + public $custompub = NULL; + public $custompriv = NULL; + public $tpls = NULL; + + /* + * Function: validate + * Validate, if the variables are set correctly. + * Will throw an exception if invalid. + */ + public function validate() + { + function validate_url ($u) { return preg_match("/^http[s]{0,1}:\\/\\/.*$/", $u) != 0; } + function validate_arraydir($a) + { + if(!is_array($a)) + return False; + foreach($a as $k=>$v) + { + if(!is_string($k)) + return False; + if(is_array($v) and (!validate_arraydir($v))) + return False; + elseif(!is_string($v)) + return False; + } + return True; + } + + if(!is_string($this->code)) + throw new InvalidPackage("Invalid code value."); + if(!is_string($this->classname)) + throw new InvalidPackage("Invalid classname value."); + if(preg_match("/^[a-zA-Z0-9_\\-]+$/", $this->name) == 0) + throw new InvalidPackage("Invalid name value (must be at least 1 character, accepted chars: a-z A-Z 0-9 - _)."); + if(!is_string($this->author)) + throw new InvalidPackage("Invalid author value."); + if(!is_string($this->versiontext)) + throw new InvalidPackage("Invalid versiontext value."); + if(!is_numeric($this->versioncount)) + throw new InvalidPackage("Invalid versioncount value. Must be a number."); + if(!is_numeric($this->api)) + throw new InvalidPackage("Invalid api value. Must be a number."); + if(!is_string($this->short_description)) + throw new InvalidPackage("Invalid short_description value."); + + if(($this->updatepath !== NULL) and (!validate_url($this->updatepath))) + throw new InvalidPackage("Invalid updatepath value. Must be an URL."); + if(($this->web !== NULL) and (!validate_url($this->web))) + throw new InvalidPackage("Invalid web value. Must be an URL."); + if(($this->license !== NULL) and (!is_string($this->license))) + throw new InvalidPackage("Invalid license value."); + if(($this->help !== NULL) and (!is_string($this->help))) + throw new InvalidPackage("Invalid help value."); + if(($this->custompub !== NULL) and (!validate_arraydir($this->custompub))) + throw new InvalidPackage("Invalid custompub value."); + if(($this->custompriv !== NULL) and (!validate_arraydir($this->custompriv))) + throw new InvalidPackage("Invalid custompriv value."); + if(($this->tpls !== NULL) and (!validate_arraydir($this->tpls))) + throw new InvalidPackage("Invalid tpls value."); + return True; + } + + /* + * Function: load + * Load a plugin package from binary data. + * + * Parameters: + * $plugin_raw - The raw package to load. + * + * Returns: + * The object. + * + * Throws: + * if package is invalid. + */ + public static function load($plugin_raw) + { + /* Read and compare magic number */ + $magic = substr($plugin_raw, 0, strlen(self::$magic)); + if($magic != self::$magic) + throw new InvalidPackage("Wrong magic number"); + + /* Read sha1sum and uncompress serialized plugin, then compare the hash */ + $sha1sum = substr($plugin_raw, strlen(self::$magic), 20); + $pluginser = gzuncompress(substr($plugin_raw, strlen(self::$magic) + 20)); + if(sha1($pluginser, True) != $sha1sum) + throw new InvalidPackage("Wrong SHA1 hash"); + + $plugin = @unserialize($pluginser); + if(!($plugin instanceof self)) + throw new InvalidPackage("Not the correct class or not unserializeable."); + + $plugin->validate(); + + return $plugin; + } + + /* + * Function: save + * Save the plugin. + * + * Returns: + * A binary plugin package. + * + * Throws: + * if package is invalid. + */ + public function save() + { + $this->validate(); + $ser = serialize($this); + return self::$magic . sha1($ser, True) . gzcompress($ser, 9); + } + + /* + * Function: extract_meta + * Get just the metadata of this package. + * + * Returns: + * A object. + */ + public function extract_meta() + { + $meta = new PluginPackageMeta(); + + $meta->name = $this>name; + $meta->author = $this>author; + $meta->versiontext = $this>versiontext; + $meta->versioncount = $this>versioncount; + $meta->api = $this>api; + $meta->short_description = $this>short_description; + $meta->updatepath = $this>updatepath; + $meta->web = $this>web; + $meta->license = $this>license; + + return $meta; + } +} + +/* + * Class: PluginPackageMeta + * Only the metadata of a . + */ +class PluginPackageMeta +{ + /* + * Variables: Mandatory values + * + * $name - Name of the plugin (must be at least one character, allowed chars: a-z A-Z 0-9 - _) + * $author - The author of the plugin (preferably in the format: Name) + * $versiontext - A text to describe the current version, something like "1.1 Beta" + * $versioncount - A number for this version, should be increased with every release + * $api - The used API version + * $short_description - A short description. + */ + public $name = NULL; + public $author = NULL; + public $versiontext = NULL; + public $versioncount = NULL; + public $api = NULL; + public $short_description = NULL; + + /* + * Variables: Optional values + * + * $updatepath - A URL that points to a update information resource (serialize'd array("current-version" => VERSIONCOUNT, "dl-path" => DOWNLOAD PATH); will get overwritten/set by the default repository software. + * $web - A URL to the webpage for the plugin. If left empty, the default repository software will set this to the description page of your plugin. + * $license - The license text of your plugin. + */ + public $updatepath = NULL; + public $web = NULL; + public $license = NULL; +} + +?> diff --git a/pwhash.php b/pwhash.php new file mode 100644 index 0000000..8ec4762 --- /dev/null +++ b/pwhash.php @@ -0,0 +1,74 @@ + diff --git a/repometa b/repometa new file mode 100644 index 0000000..e69de29 diff --git a/stupid_template_engine.php b/stupid_template_engine.php new file mode 100644 index 0000000..0edb880 --- /dev/null +++ b/stupid_template_engine.php @@ -0,0 +1,1297 @@ +tpl = $tpl; + $this->offset = $off; + } +} + +class TextNode extends ASTNode +{ + public $text; +} + +class TagNode extends ASTNode +{ + public $name; + public $params; + public $sub; +} + +class VariableNode extends ASTNode +{ + public $name; + public $arrayfields; + public function transcompile() + { + $varaccess = '@$ste->vars[' . (is_numeric($this->name) ? $this->name : '\'' . escape_text($this->name) . '\''). ']'; + foreach($this->arrayfields as $af) + { + if((count($af) == 1) and ($af[0] instanceof TextNode) and is_numeric($af->text)) + $varaccess .= '[' . $af->text . ']'; + else + $varaccess .= '[' . implode(".", + array_map( + function($node) + { + if($node instanceof TextNode) + return "\"" . escape_text($node->text) . "\""; + else if($node instanceof VariableNode) + return $node->transcompile(); + }, $af + ) + ). ']'; + } + return $varaccess; + } +} + +class ParseCompileError extends \Exception +{ + public $msg; + public $tpl; + public $off; + + public function __construct($msg, $tpl, $offset, $code = 0, $previous = NULL) + { + $this->msg = $msg; + $this->tpl = $tpl; + $this->off = $offset; + $this->message = "$msg (Template $tpl, Offset $offset)"; + } + + public function rewrite($code) + { + $line = substr_count(str_replace("\r\n", "\n", $code), "\n", 0, $this->off + 1) + 1; + $this->message = "{$this->msg} (Template $tpl, Line $line)"; + $this->is_rewritten = True; + } +} + +/* $text must start after the first opening bracket */ +function find_closing_bracket($text, $opening, $closing) +{ + $counter = 1; + $len = strlen($text); + for($i = 0; $i < $len; ++$i) + { + switch($text[$i]) + { + case $opening: + ++$counter; + break; + case $closing: + --$counter; + break; + } + if($counter == 0) + break; + } + + if($counter > 0) + throw new \Exception("Missing closing \"$closing\". Stop."); + + return $i; +} + +function instance_in_array($classname, $a) +{ + foreach($a as $v) + { + if($v instanceof $classname) + return True; + } + return False; +} + +function unescape_text($text) +{ + return stripcslashes($text); +} + +function tokenize_text($text, $tpl, $off) +{ + $tokens = array(); + /* Find next non-escaped $-char */ + if(preg_match("/(?:(?text = preg_replace("/^\\n\\s*/s", "", unescape_text($text)); + return (strlen($node->text) == 0) ? array() : array($node); + } + + if($match[0][1] > 0) + { + $node = new TextNode($tpl, $off); + $node->text = unescape_text(substr($text, 0, $match[0][1])); + $tokens[] = $node; + } + + if($text[$match[0][1] + 1] == "{") + { + try + { + $varend = find_closing_bracket(substr($text, $match[0][1] + 2), "{", "}") + $match[0][1] + 2; + } + catch(\Exception $e) + { + throw new ParseCompileError("Parse Error: Missing closing '}'", $tpl, $off + $match[0][1] + 1); + } + return array_merge( + $tokens, + tokenize_text("\$" . substr($text, $match[0][1] + 2, ($varend - 1) - ($match[0][1] + 1)), $tpl, $off + $match[0][1] + 2), + tokenize_text(substr($text, $varend + 1), $tpl, $off + $varend + 1) + ); + } + + $text = substr($text, $match[0][1] + 1); + $off += $match[0][1] + 1; + if(preg_match("/^[a-zA-Z0-9_]+/s", $text, $match, PREG_OFFSET_CAPTURE) == 0) + { + $nexttokens = tokenize_text($text, $tpl, $off); + if($nexttokens[0] instanceof TextNode) + $nexttokens[0]->text = "\$" . $nexttokens[0]->text; + else + { + $node = new TextNode($tpl, $off); + $node->text = "\$"; + $tokens[] = $node; + } + return array_merge($tokens, $nexttokens); + } + + $node = new VariableNode($tpl, $off + $match[0][1]); + $node->name = $match[0][0]; + $node->arrayfields = array(); + + $text = substr($text, $match[0][1] + strlen($match[0][0])); + $off += $match[0][1] + strlen($match[0][0]); + while(@$text[0] == "[") + { + $text = substr($text, 1); + $off += 1; + try + { + $fieldend = find_closing_bracket($text, "[", "]"); + } + catch(\Exception $e) + { + throw new ParseCompileError("Parse Error: Missing closing ']'", $tpl, $off - 1); + } + $node->arrayfields[] = tokenize_text(substr($text, 0, $fieldend), $tpl, $off); + $text = substr($text, $fieldend + 1); + $off += $fieldend + 1; + } + + $tokens[] = $node; + + return strlen($text) > 0 ? array_merge($tokens, tokenize_text($text, $tpl, $off)) : $tokens; +} + +function mk_ast($code, $tpl, $err_off) +{ + $ast = array(); + + if(preg_match("/\\<\\s*ste:([a-zA-Z0-9_]*)/s", $code, $matches, PREG_OFFSET_CAPTURE) == 0) + return tokenize_text($code, $tpl, $err_off); + + $ast = tokenize_text(substr($code, 0, $matches[0][1]), $tpl, $err_off); + $tag = new TagNode($tpl, $err_off + $matches[0][1]); + $tag->name = $matches[1][0]; + + $code = substr($code, $matches[0][1] + strlen($matches[0][0])); + $err_off += $matches[0][1] + strlen($matches[0][0]); + + $tag->params = array(); + + while(preg_match("/^\\s+([a-zA-Z0-9_]+)=((?:\"(?:.*?)(? 0) + { + $paramval = substr($code, $matches[2][1] + 1, strlen($matches[2][0]) - 2); + $paramval = str_replace("\\\"", "\"", $paramval); + $paramval = str_replace("\\'", "'", $paramval); + $tag->params[$matches[1][0]] = tokenize_text($paramval, $tpl, $err_off + $matches[2][1] + 1); + $code = substr($code, strlen($matches[0][0])); + $err_off += strlen($matches[0][0]); + } + + if(preg_match("/^\\s*([\\/]?)\\s*\\>/s", $code, $matches) == 0) + throw new ParseCompileError("Parse Error: Missing closing '>' in \"" . $tag->name . "\"-Tag.", $tpl, $tag->offset); + + $code = substr($code, strlen($matches[0])); + $err_off += strlen($matches[0]); + + $tag->sub = array(); + + if($matches[1][0] != "/") + { + $off = 0; + $last_tag_start = 0; + $tagstack = array(array($tag->name, $tag->offset)); + while(preg_match("/\\<((?:\\s*)|(?:\\s*\\/\\s*))ste:([a-zA-Z0-9_]*)(?:\\s+(?:[a-zA-Z0-9_]+)=(?:(?:\"(?:.*?)(?/s", $code, $matches, PREG_OFFSET_CAPTURE, $off) > 0) /* RegEx from hell! Matches all Tags. Opening, closing and self-closing ones. */ + { + if(trim($matches[3][0]) != "/") + { + $closingtag = trim($matches[1][0]); + if($closingtag[0] == "/") + { + list($matching_opentag, $mo_off) = array_pop($tagstack); + if($matching_opentag != $matches[2][0]) + throw new ParseCompileError("Parse Error: Missing closing \"ste:$matching_opentag\"-Tag.", $tpl, $mo_off + $err_off); + } + else + $tagstack[] = array($matches[2][0], $matches[0][1]); + } + $last_tag_start = $matches[0][1]; + $off = $last_tag_start + strlen($matches[0][0]); + if(empty($tagstack)) + break; + } + + if((!empty($tagstack)) or ($tag->name != $matches[2][0])) + throw new ParseCompileError("Parse Error: Missing closing \"ste:" . $tag->name . "\"-Tag.", $tpl, $tag->offset); + + if($tag->name == "rawtext") + { + $tag = new TextNode($tpl, $err_off); + $tag->text = substr($code, 0, $last_tag_start); + } + else if($tag->name == "comment") + $tag = NULL; /* All this work to remove a comment ... */ + else + $tag->sub = mk_ast(substr($code, 0, $last_tag_start), $tpl, $err_off); + $code = substr($code, $off); + $err_off += $off; + } + + if($tag !== NULL) + $ast[] = $tag; + return array_merge($ast, strlen($code) > 0 ? mk_ast($code, $tpl, $err_off) : array()); +} + +/* + * Function: precompile + * Precompiling STE T/PL templates. + * You only need this function, if you want to manually transcompile a template. + * + * Parameters: + * $code - The input code + * + * Returns: + * The precompiled code. + */ +function precompile($code) +{ + $code = preg_replace( /* Transform short form of comparison (~{a|op|b}) to long form */ + "/(?:(?", + $code + ); + $code = preg_replace( /* Transform short form of if-clause (?{cond|then|else}) to long form */ + "/(?:(?\$1\$2\$3", + $code + ); + /* Unescape \? \~ \{ \} \| */ + $code = preg_replace("/(?:(?. + */ +function parse($code, $tpl) +{ + return mk_ast($code, $tpl, 0); +} + +function indent_code($code) +{ + return implode( + "\n", + array_map( + function($line) { return "\t$line"; }, + explode("\n", $code) + ) + ); +} + +/* We could also just eval() the $infix_math code, but this is much cooler :-D (Parser inception) */ +function shunting_yard($infix_math) +{ + $operators = array( + "+" => array("l", 2), + "-" => array("l", 2), + "*" => array("l", 3), + "/" => array("l", 3), + "^" => array("r", 4), + "_" => array("r", 5), + "(" => array("", 0), + ")" => array("", 0) + ); + + preg_match_all("/\s*(?:(?:[+\\-\\*\\/\\^\\(\\)])|(\\d*[\\.]?\\d*))\\s*/s", $infix_math, $tokens, PREG_PATTERN_ORDER); + $tokens_raw = array_filter(array_map('trim', $tokens[0]), function($x) { return ($x === "0") or (!empty($x)); }); + $output_queue = array(); + $op_stack = array(); + + $lastpriority = NULL; + /* Make - unary, if neccessary */ + $tokens = array(); + foreach($tokens_raw as $token) + { + $priority = isset($operators[$token]) ? $operators[$token][1] : -1; + if(($token == "-") and (($lastpriority === NULL) or ($lastpriority >= 0))) + { + $priority = $operators["_"][1]; + $tokens[] = "_"; + } + else + $tokens[] = $token; + $lastpriority = $priority; + } + + while(!empty($tokens)) + { + $token = array_shift($tokens); + if(is_numeric($token)) + $output_queue[] = $token; + else if($token == "(") + $op_stack[] = $token; + else if($token == ")") + { + $lbr_found = False; + while(!empty($op_stack)) + { + $op = array_pop($op_stack); + if($op == "(") + { + $lbr_found = True; + break; + } + $output_queue[] = $op; + } + if(!$lbr_found) + throw new \Exception("Bracket mismatch."); + } + else if(!isset($operators[$token])) + throw new \Exception("Invalid token ($token): Not a number, bracket or operator. Stop."); + else + { + $priority = $operators[$token][1]; + if($operators[$token][0] == "l") + while((!empty($op_stack)) and ($priority <= $operators[$op_stack[count($op_stack)-1]][1])) + $output_queue[] = array_pop($op_stack); + else + while((!empty($op_stack)) and ($priority < $operators[$op_stack[count($op_stack)-1]][1])) + $output_queue[] = array_pop($op_stack); + $op_stack[] = $token; + } + } + + while(!empty($op_stack)) + { + $op = array_pop($op_stack); + if($op == "(") + throw new \Exception("Bracket mismatch..."); + $output_queue[] = $op; + } + + return $output_queue; +} + +function pop2(&$array) +{ + $rv = array(array_pop($array), array_pop($array)); + if(array_search(NULL, $rv, True) !== False) + throw new \Exception("Not enough numbers on stack. Invalid formula."); + return $rv; +} + +function calc_rpn($rpn) +{ + $stack = array(); + foreach($rpn as $token) + { + switch($token) + { + case "+": + list($b, $a) = pop2($stack); + $stack[] = $a + $b; + break; + case "-": + list($b, $a) = pop2($stack); + $stack[] = $a - $b; + break; + case "*": + list($b, $a) = pop2($stack); + $stack[] = $a * $b; + break; + case "/": + list($b, $a) = pop2($stack); + $stack[] = $a / $b; + break; + case "^": + list($b, $a) = pop2($stack); + $stack[] = pow($a, $b); + break; + case "_": + $a = array_pop($stack); + if($a === NULL) + throw new \Exception("Not enough numbers on stack. Invalid formula."); + $stack[] = -$a; + break; + default: + $stack[] = $token; + break; + } + } + return array_pop($stack); +} + +function loopbody($code) +{ + return "try\n{\n" . indent_code($code) . "\n}\ncatch(\ste\BreakException \$e) { break; }\ncatch(\ste\ContinueException \$e) { continue; }\n"; +} + +$ste_builtins = array( + "if" => function($ast) + { + $output = ""; + $condition = array(); + $then = NULL; + $else = NULL; + + foreach($ast->sub as $node) + { + if(($node instanceof TagNode) and ($node->name == "then")) + $then = $node->sub; + else if(($node instanceof TagNode) and ($node->name == "else")) + $else = $node->sub; + else + $condition[] = $node; + } + + if($then === NULL) + throw new ParseCompileError("Transcompile error: Missing in .", $ast->tpl, $ast->offset); + + $output .= "\$outputstack[] = \"\";\n\$outputstack_i++;\n"; + $output .= _transcompile($condition); + $output .= "\$outputstack_i--;\nif(\$ste->evalbool(array_pop(\$outputstack)))\n{\n"; + $output .= indent_code(_transcompile($then)); + $output .= "\n}\n"; + if($else !== NULL) + { + $output .= "else\n{\n"; + $output .= indent_code(_transcompile($else)); + $output .= "\n}\n"; + } + return $output; + }, + "cmp" => function($ast) + { + $operators = array( + array('eq', '=='), + array('neq', '!='), + array('lt', '<'), + array('lte', '<='), + array('gt', '>'), + array('gte', '>=') + ); + + $code = ""; + + if(isset($ast->params["var_b"])) + $b = '$ste->get_var_by_name(' . _transcompile($ast->params["var_b"], True) . ')'; + else if(isset($ast->params["text_b"])) + $b = _transcompile($ast->params["text_b"], True); + else + throw new ParseCompileError("Transcompile error: neiter var_b nor text_b set in .", $ast->tpl, $ast->offset); + + if(isset($ast->params["var_a"])) + $a = '$ste->get_var_by_name(' . _transcompile($ast->params["var_a"], True) . ')'; + else if(isset($ast->params["text_a"])) + $a = _transcompile($ast->params["text_a"], True); + else + throw new ParseCompileError("Transcompile error: neiter var_a nor text_a set in .", $ast->tpl, $ast->offset); + + if(!isset($ast->params["op"])) + throw new ParseCompileError("Transcompile error: op not given in .", $ast->tpl, $ast->offset); + if((count($ast->params["op"]) == 1) and ($ast->params["op"][0] instanceof TextNode)) + { + /* Operator is known at compile time, this saves *a lot* of output code! */ + $op = trim($ast->params["op"][0]->text); + $op_php = NULL; + foreach($operators as $v) + { + if($v[0] == $op) + { + $op_php = $v[1]; + break; + } + } + if($op_php === NULL) + throw new ParseCompileError("Transcompile Error: Unknown operator in ", $ast->tpl, $ast->offset); + $code .= "\$outputstack[\$outputstack_i] .= (($a) $op_php ($b)) ? 'yes' : '';\n"; + } + else + { + $code .= "switch(trim(" . _transcompile($ast->params["op"], True) . "))\n{\n\t"; + $code .= implode("", array_map( + function($op) use ($a,$b) + { + list($op_stetpl, $op_php) = $op; + return "case '$op_stetpl':\n\t\$outputstack[\$outputstack_i] .= (($a) $op_php ($b)) ? 'yes' : '';\n\tbreak;\n\t"; + }, $operators + )); + $code .= "default: throw new \Exception('Runtime Error: Unknown operator in .');\n}\n"; + } + return $code; + }, + "not" => function($ast) + { + $code = "\$outputstack[] = '';\n\$outputstack_i++;\n"; + $code .= _transcompile($ast->sub); + $code .= "\$outputstack_i--;\n\$outputstack[\$outputstack_i] .= (!\$ste->evalbool(array_pop(\$outputstack))) ? 'yes' : '';\n"; + return $code; + }, + "even" => function($ast) + { + $code = "\$outputstack[] = '';\n\$outputstack_i++;\n"; + $code .= _transcompile($ast->sub); + $code .= "\$outputstack_i--;\n\$tmp_even = array_pop(\$outputstack);\n\$outputstack[\$outputstack_i] .= (is_numeric(\$tmp_even) and (\$tmp_even % 2 == 0)) ? 'yes' : '';\n"; + return $code; + }, + "for" => function($ast) + { + $code = ""; + $loopname = "forloop_" . str_replace(".", "_", uniqid("",True)); + if(empty($ast->params["start"])) + throw new ParseCompileError("Transcompile error: Missing 'start' parameter in .", $ast->tpl, $ast->offset); + $code .= "\$${loopname}_start = " . _transcompile($ast->params["start"], True) . ";\n"; + + if(empty($ast->params["stop"])) + throw new ParseCompileError("Transcompile error: Missing 'end' parameter in .", $ast->tpl, $ast->offset); + $code .= "\$${loopname}_stop = " . _transcompile($ast->params["stop"], True) . ";\n"; + + $step = NULL; /* i.e. not known at compilation time */ + if(empty($ast->params["step"])) + $step = 1; + else if((count($ast->params["step"]) == 1) and ($ast->params["step"][0] instanceof TextNode)) + $step = $ast->params["step"][0]->text + 0; + else + $code .= "\$${loopname}_step = " . _transcompile($ast->params["step"], True) . ";\n"; + + if(!empty($ast->params["counter"])) + $code .= "\$${loopname}_countername = " . _transcompile($ast->params["counter"], True) . ";\n"; + + $loopbody = empty($ast->params["counter"]) ? "" : "\$ste->set_var_by_name(\$${loopname}_countername, \$${loopname}_counter);\n"; + $loopbody .= _transcompile($ast->sub); + $loopbody = indent_code("{\n" . loopbody(indent_code($loopbody)) . "\n}\n"); + + if($step === NULL) + { + $code .= "if(\$${loopname}_step == 0)\n\tthrow new \Exception('Runtime Error: step can not be 0 in .');\n"; + $code .= "if(\$${loopname}_step > 0)\n{\n"; + $code .= "\tfor(\$${loopname}_counter = \$${loopname}_start; \$${loopname}_counter <= \$${loopname}_stop; \$${loopname}_counter += \$${loopname}_step)\n"; + $code .= $loopbody; + $code .= "\n}\nelse\n{\n"; + $code .= "\tfor(\$${loopname}_counter = \$${loopname}_start; \$${loopname}_counter >= \$${loopname}_stop; \$${loopname}_counter += \$${loopname}_step)\n"; + $code .= $loopbody; + $code .= "\n}\n"; + } + else if($step == 0) + throw new ParseCompileError("Transcompile Error: step can not be 0 in .", $ast->tpl, $ast->offset); + else if($step > 0) + $code .= "for(\$${loopname}_counter = \$${loopname}_start; \$${loopname}_counter <= \$${loopname}_stop; \$${loopname}_counter += $step)\n$loopbody\n"; + else + $code .= "for(\$${loopname}_counter = \$${loopname}_start; \$${loopname}_counter >= \$${loopname}_stop; \$${loopname}_counter += $step)\n$loopbody\n"; + + return $code; + }, + "foreach" => function($ast) + { + $loopname = "foreachloop_" . str_replace(".", "_", uniqid("",True)); + $code = ""; + + if(empty($ast->params["array"])) + throw new ParseCompileError("Transcompile Error: array not given in .", $ast->tpl, $ast->offset); + $code .= "\$${loopname}_arrayvar = " . _transcompile($ast->params["array"], True) . ";\n"; + + if(empty($ast->params["value"])) + throw new ParseCompileError("Transcompile Error: value not given in .", $ast->tpl, $ast->offset); + $code .= "\$${loopname}_valuevar = " . _transcompile($ast->params["value"], True) . ";\n"; + + if(!empty($ast->params["key"])) + $code .= "\$${loopname}_keyvar = " . _transcompile($ast->params["key"], True) . ";\n"; + + if(!empty($ast->params["counter"])) + $code .= "\$${loopname}_countervar = " . _transcompile($ast->params["counter"], True) . ";\n"; + + $loopbody = ""; + $code .= "\$${loopname}_array = \$ste->get_var_by_name(\$${loopname}_arrayvar);\n"; + $code .= "if(!is_array(\$${loopname}_array))\n\t\$${loopname}_array = array();\n"; + if(!empty($ast->params["counter"])) + { + $code .= "\$${loopname}_counter = -1;\n"; + $loopbody .= "\$${loopname}_counter++;\n\$ste->set_var_by_name(\$${loopname}_countervar, \$${loopname}_counter);\n"; + } + + $loopbody .= "\$ste->set_var_by_name(\$${loopname}_valuevar, \$${loopname}_value);\n"; + if(!empty($ast->params["key"])) + $loopbody .= "\$ste->set_var_by_name(\$${loopname}_keyvar, \$${loopname}_key);\n"; + $loopbody .= "\n"; + $loopbody .= _transcompile($ast->sub); + $loopbody = "{\n" . loopbody(indent_code($loopbody)) . "\n}\n"; + + $code .= "foreach(\$${loopname}_array as \$${loopname}_key => \$${loopname}_value)\n$loopbody\n"; + + return $code; + }, + "infloop" => function($ast) + { + return "while(True)\n{\n" . loopbody(indent_code(_transcompile($ast->sub)) . "\n}") . "\n"; + }, + "break" => function($ast) + { + return "throw new \\ste\\BreakException();\n"; + }, + "continue" => function($ast) + { + return "throw new \\ste\\ContinueException();\n"; + }, + "block" => function($ast) + { + if(empty($ast->params["name"])) + throw new ParseCompileError("Transcompile Error: name missing in .", $ast->tpl, $ast->offset); + + $blknamevar = "blockname_" . str_replace(".", "_", uniqid("", True)); + + $code = "\$${blknamevar} = " . _transcompile($ast->params["name"], True) . ";\n"; + + $tmpblk = uniqid("", True); + $code .= "\$ste->blocks['$tmpblk'] = array_pop(\$outputstack);\n\$ste->blockorder[] = '$tmpblk';\n\$outputstack = array('');\n\$outputstack_i = 0;\n"; + + $code .= _transcompile($ast->sub); + + $code .= "\$ste->blocks[\$${blknamevar}] = array_pop(\$outputstack);\n"; + $code .= "if(array_search(\$${blknamevar}, \$ste->blockorder) === FALSE)\n\t\$ste->blockorder[] = \$${blknamevar};\n\$outputstack = array('');\n\$outputstack_i = 0;\n"; + + return $code; + }, + "load" => function($ast) + { + if(empty($ast->params["name"])) + throw new ParseCompileError("Transcompile Error: name missing in .", $ast->tpl, $ast->offset); + + return "\$outputstack[\$outputstack_i] .= \$ste->load(" . _transcompile($ast->params["name"], True) . ");\n"; + }, + "mktag" => function($ast) + { + if(empty($ast->params["name"])) + throw new ParseCompileError("Transcompile Error: name missing in .", $ast->tpl, $ast->offset); + + $tagname = _transcompile($ast->params["name"], True); + + $fxbody = "\$outputstack = array(); \$outputstack_i = 0;\$ste->vars['_tag_parameters'] = \$params;\n"; + + if(!empty($ast->params["mandatory"])) + { + $code .= "\$outputstack[] = '';\n\$outputstack_i++;\n"; + $code .= _transcompile($ast->params["mandatory"]); + $code .= "\$outputstack_i--;\n\$mandatory_params = explode('|', array_pop(\$outputstack));\n"; + + $fxbody .= "foreach(\$mandatory_params as \$mp)\n{\n\tif(!isset(\$params[\$mp]))\n\t\tthrow new \Exception(\"Runtime Error: \$mp missing in . Stop.\");\n}"; + } + + $fxbody .= _transcompile($ast->sub); + $fxbody .= "return array_pop(\$outputstack);"; + + $code .= "\$tag_fx = function(\$ste, \$params, \$sub) use (\$mandatory_params)\n{\n" . indent_code($fxbody) . "\n};\n"; + $code .= "\$ste->register_tag($tagname, \$tag_fx);\n"; + + return $code; + }, + "tagcontent" => function($ast) + { + return "\$outputstack[\$outputstack_i] .= \$sub(\$ste);"; + }, + "set" => function($ast) + { + if(empty($ast->params["var"])) + throw new ParseCompileError("Transcompile Error: var missing in .", $ast->tpl, $ast->offset); + + $code = "\$outputstack[] = '';\n\$outputstack_i++;\n"; + $code .= _transcompile($ast->sub); + $code .= "\$outputstack_i--;\n"; + + $code .= "\$ste->set_var_by_name(" . _transcompile($ast->params["var"], True) . ", array_pop(\$outputstack));\n"; + + return $code; + }, + "calc" => function($ast) + { + $code = "\$outputstack[] = '';\n\$outputstack_i++;\n"; + $code .= _transcompile($ast->sub); + $code .= "\$outputstack_i--;\n\$outputstack[\$outputstack_i] .= \$ste->calc(array_pop(\$outputstack));\n"; + + return $code; + } +); + +function escape_text($text) +{ + return addcslashes($text, "\r\n\t\$\0..\x1f\\'\"\x7f..\xff"); +} + +function _transcompile($ast, $no_outputstack = False) /* The real transcompile function, does not add boilerplate code. */ +{ + $code = ""; + global $ste_builtins; + + $text_and_var_buffer = array(); + + foreach($ast as $node) + { + if($node instanceof TextNode) + $text_and_var_buffer[] = '"' . escape_text($node->text) . '"'; + else if($node instanceof VariableNode) + $text_and_var_buffer[] = $node->transcompile(); + else if($node instanceof TagNode) + { + if(!empty($text_and_var_buffer)) + { + $code .= "\$outputstack[\$outputstack_i] .= " . implode (" . ", $text_and_var_buffer) . ";\n"; + $text_and_var_buffer = array(); + } + if(isset($ste_builtins[$node->name])) + $code .= $ste_builtins[$node->name]($node); + else + { + $paramarray = "parameters_" . str_replace(".", "_", uniqid("", True)); + $code .= "\$$paramarray = array();\n"; + + foreach($node->params as $pname => $pcontent) + $code .= "\$${paramarray}['" . escape_text($pname) . "'] = " . _transcompile($pcontent, True) . ";\n"; + + $code .= "\$outputstack[\$outputstack_i] .= \$ste->call_tag('" . escape_text($node->name) . "', \$${paramarray}, "; + $code .= empty($node->sub) ? "function(\$ste) { return ''; }" : transcompile($node->sub); + $code .= ");\n"; + } + } + } + + if(!empty($text_and_var_buffer)) + { + if(!$no_outputstack) + $code .= "\$outputstack[\$outputstack_i] .= "; + $code .= implode (" . ", $text_and_var_buffer); + if(!$no_outputstack) + $code .= ";\n"; + $text_and_var_buffer = array(); + } + else if($no_outputstack) + { + $code = "\"\""; + } + + return $code; +} + +$ste_transc_boilerplate = "\$outputstack = array('');\n\$outputstack_i = 0;\n"; + +/* + * Function: transcompile + * Transcompiles an abstract syntax tree to PHP. + * You only need this function, if you want to manually transcompile a template. + * + * Parameters: + * $ast - The abstract syntax tree to transcompile. + * + * Returns: + * PHP code. The PHP code is an anonymous function expecting a instance as its parameter and returns a string (everything that was not pached into a section). + */ +function transcompile($ast) /* Transcompile and add some boilerplate code. */ +{ + global $ste_transc_boilerplate; + return "function(\$ste)\n{\n" . indent_code($ste_transc_boilerplate . _transcompile($ast) . "return array_pop(\$outputstack);") . "\n}"; +} + +/* + * Constants: Template modes + * + * MODE_SOURCE - The Templates source + * MODE_TRANSCOMPILED - The transcompiled template + */ +const MODE_SOURCE = 0; +const MODE_TRANSCOMPILED = 1; + +/* + * Class: StorageAccess + * An interface. + * A StorageAccess implementation is used to access the templates from any storage. + * This means, that you are not limited to store the Templates inside directories, you can also use a database or something else. + */ +interface StorageAccess +{ + /* + * Function: load + * Loading a template. + * + * Parameters: + * $tpl - The name of the template. + * &$mode - Which mode is preferred? One of the