From a7c962462d15afe06875d359e4648794cfbc4fc8 Mon Sep 17 00:00:00 2001 From: Nathan Adams Date: Sat, 19 Apr 2014 15:11:53 -0500 Subject: [PATCH] Adding template engine Adding error handling Adding basic SMTP class core routing is working --- .htaccess | 5 +- application/.htaccess | 1 + application/config.php | 5 + application/controllers/.htaccess | 1 + application/tmp/.htaccess | 1 + application/views/.htaccess | 1 + index.php | 3 +- system/.htaccess | 1 + system/engine/.htaccess | 1 + system/engine/SMTP.php | 80 +++++ system/engine/config-default.php | 14 + system/engine/controller.php | 15 + system/engine/core.php | 258 +++++++++++++--- system/engine/exceptions.php | 18 ++ system/engine/status.php | 15 + system/vendor/.htaccess | 1 + system/vendor/README.md | 453 ++++++++++++++++++++++++++++ system/vendor/StackTracePrint.php | 66 ++++ system/vendor/h2o.php | 266 ++++++++++++++++ system/vendor/h2o/.htaccess | 1 + system/vendor/h2o/context.php | 266 ++++++++++++++++ system/vendor/h2o/datatype.php | 173 +++++++++++ system/vendor/h2o/errors.php | 10 + system/vendor/h2o/filters.php | 369 +++++++++++++++++++++++ system/vendor/h2o/loaders.php | 290 ++++++++++++++++++ system/vendor/h2o/nodes.php | 84 ++++++ system/vendor/h2o/parser.php | 290 ++++++++++++++++++ system/vendor/h2o/tags.php | 483 ++++++++++++++++++++++++++++++ 28 files changed, 3126 insertions(+), 45 deletions(-) create mode 100644 application/.htaccess create mode 100644 application/config.php create mode 100644 application/controllers/.htaccess create mode 100644 application/tmp/.htaccess create mode 100644 application/views/.htaccess create mode 100644 system/.htaccess create mode 100644 system/engine/.htaccess create mode 100644 system/engine/SMTP.php create mode 100644 system/engine/config-default.php create mode 100644 system/engine/controller.php create mode 100644 system/engine/exceptions.php create mode 100644 system/engine/status.php create mode 100644 system/vendor/.htaccess create mode 100644 system/vendor/README.md create mode 100644 system/vendor/StackTracePrint.php create mode 100644 system/vendor/h2o.php create mode 100644 system/vendor/h2o/.htaccess create mode 100644 system/vendor/h2o/context.php create mode 100644 system/vendor/h2o/datatype.php create mode 100644 system/vendor/h2o/errors.php create mode 100644 system/vendor/h2o/filters.php create mode 100644 system/vendor/h2o/loaders.php create mode 100644 system/vendor/h2o/nodes.php create mode 100644 system/vendor/h2o/parser.php create mode 100644 system/vendor/h2o/tags.php diff --git a/.htaccess b/.htaccess index 6571402..8252d42 100644 --- a/.htaccess +++ b/.htaccess @@ -1,2 +1,5 @@ RewriteEngine on -RewriteRule ^(.*)$ /index.php/$1 [L] \ No newline at end of file +RewriteBase /test +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^(.*)$ index.php/$1 [L] \ No newline at end of file diff --git a/application/.htaccess b/application/.htaccess new file mode 100644 index 0000000..14249c5 --- /dev/null +++ b/application/.htaccess @@ -0,0 +1 @@ +Deny from all \ No newline at end of file diff --git a/application/config.php b/application/config.php new file mode 100644 index 0000000..a7569ee --- /dev/null +++ b/application/config.php @@ -0,0 +1,5 @@ +run(); \ No newline at end of file diff --git a/system/.htaccess b/system/.htaccess new file mode 100644 index 0000000..14249c5 --- /dev/null +++ b/system/.htaccess @@ -0,0 +1 @@ +Deny from all \ No newline at end of file diff --git a/system/engine/.htaccess b/system/engine/.htaccess new file mode 100644 index 0000000..14249c5 --- /dev/null +++ b/system/engine/.htaccess @@ -0,0 +1 @@ +Deny from all \ No newline at end of file diff --git a/system/engine/SMTP.php b/system/engine/SMTP.php new file mode 100644 index 0000000..7a7a5df --- /dev/null +++ b/system/engine/SMTP.php @@ -0,0 +1,80 @@ +from = $from; + $this->to = $to; + $this->subject = $subject; + $this->msg = $msg; + $this->user = $user; + $this->password = $password; + $this->port = $port; + $this->server = $server; + } + + public function send($html=false) + { + $err = null; + $errstr = ""; + $this->smtpserverconn = fsockopen($this->server, $this->port, $err, $errstr, 100); + $response = fgets($this->smtpserverconn, 515); + if ($response === false) + { + throw new Exception("Could not connect to SMTP server!"); + } + + if ($this->user != null && $this->password != null) + { + $this->sendCommand("AUTH LOGIN"); + $this->sendCommand(base64_encode($this->user)); + $this->sendCommand(base64_encode($this->password)); + } + + $this->sendCommand("HELO " . $_SERVER["SERVER_NAME"]); + $this->sendCommand("MAIL FROM: " . $this->from); + $this->sendCommand("RCPT TO: " . $this->to); + $this->sendCommand("DATA"); + + if ($html) + { + $this->sendCommand("MIME-Version: 1.0", false); + $this->sendCommand("Content-type: text/html; charset=iso-8859-1", false); + } + + $this->sendCommand("From: " . $this->from, false); + $this->sendCommand("To: " . $this->to, false); + $this->sendCommand("Subject: " . $this->subject, false); + + + $this->sendCommand($this->msg, false); + + $this->sendCommand("", false); + $this->sendCommand(".", false); + $this->sendCommand("QUIT"); + + } + + private function sendCommand($command, $return = true) + { + fputs($this->smtpserverconn, $command . "\r\n"); + if ($return) + return fgets($this->smtpserverconn, 515); + else + return null; + } +} \ No newline at end of file diff --git a/system/engine/config-default.php b/system/engine/config-default.php new file mode 100644 index 0000000..d2bd78e --- /dev/null +++ b/system/engine/config-default.php @@ -0,0 +1,14 @@ +config = $config; + $this->tpl = $tpl; + $this->core = $core; + } + +} \ No newline at end of file diff --git a/system/engine/core.php b/system/engine/core.php index 23a1a66..7171cf0 100644 --- a/system/engine/core.php +++ b/system/engine/core.php @@ -1,63 +1,235 @@ findController(); + $config = include("system/engine/config-default.php"); + if (is_file("application/config.php")) + { + $newconfig = include("application/config.php"); + } + $this->config = array_merge($config, $newconfig); + if ($this->config["USE_H20_TPL"]) + $this->tpl = new H2o(null, array( + "searchpath" => getcwd() . "/application/views/", "cache_dir" => "application/tmp/", + )); + set_error_handler("HF_Core::error_handler"); + $this->findController(); } private function findController() { - $request = $_SERVER["REQUEST_URI"]; - if ($request == "" || $request == "/") - { - require("../../application/controllers/main.php"); - $this->$class = new main(); - $this->$method = "index"; - $this->$classname = "main"; - return; - } - $arr = explode("/", $request); - $path = "../../application/controllers/"; - for($i = 0; $i < count($arr); $i++) - { - if ($is_file($path . $arr[$i] . ".php")) // found the controller - { - include($path . $arr[$i] . ".php"); - $this->$class = new $arr[$i]; - - if ($i + 1 != count($arr)) // if there is a define after the controller name - this would be the method name - { - $this->$method = $arr[$i+1]; - $this->$args = array_slice ($arr, 2); - $this->$classname = $arr[$i]; - } else { // call index - $this->$method = "index"; - $this->$classname = $arr[$i]; - } - return; - } - - if (is_dir($path . $arr[$i])) // controller is hidden deeper - { - $path = $path . $arr[$i] . "/"; - continue; - } - - // throw exception controller not found - } - + try + { + $request = $_SERVER["PHP_SELF"]; + $splitreq = explode("/", $request); + $request = ""; + for($i = 0; $i < count($splitreq); $i++) + { + if ($splitreq[$i] == "index.php") + { + $request = implode("/", array_splice($splitreq, $i+1)); + } + } + + if ($request == "" || $request == "/") + { + require("application/controllers/" . $this->config["DEFAULT_ROUTE"] . ".php"); + $this->loadController(new $this->config["DEFAULT_ROUTE"]($this->config, $this, $this->tpl), $this->config["DEFAULT_ROUTE"], "index"); + return; + } + if ($request[strlen($request)-1] == "/") + $request = substr($request, 0, -1); + $arr = explode("/", $request); + $path = "application/controllers/"; + for($i = 0; $i < count($arr); $i++) + { + if (is_file($path . $arr[$i] . ".php")) // found the controller + { + include($path . $arr[$i] . ".php"); + if ($i + 1 < count($arr)) // if there is a define after the controller name - this would be the method name + { + $this->loadController(new $arr[$i]($this->config, $this, $this->tpl), $arr[$i], $arr[$i+1], array_slice ($arr, 2)); + } else { // call index + $this->loadController(new $arr[$i]($this->config, $this, $this->tpl), $arr[$i], "index"); + } + return; + } + + if (is_dir($path . $arr[$i])) // controller is hidden deeper + { + $path = $path . $arr[$i] . "/"; + continue; + } + + $this->load404Controller(); + break; + // throw exception controller not found + } + } catch (Exception $e) { + if ($this->config["DEBUG"]) + echo vdump($e, $this); + else + $this->mail_admins("[Exception - " . $this->config["SITE_NAME"] . "]", vdump($e, $this), true); + } } + + private function load404Controller() + { + if (is_file(getcwd() . "/application/status.php")) + { + include_once (getcwd() . "/application/status.php"); + $this->loadController(new status($this->config, $this, $this->tpl), "status", "Status404"); + } else { + include_once(getcwd() . "/system/engine/status.php"); + $this->loadController(new HF_Status($this->config, $this, $this->tpl), "HF_Status", "Status404"); + + } + } + + private function load500Controller() + { + if (is_file(getcwd() . "/application/status.php")) + { + include_once (getcwd() . "/application/status.php"); + $this->loadController(new status($this->config, $this, $this->tpl), "status", "Status500"); + } else { + include_once (getcwd() . "/system/engine/status.php"); + $this->loadController(new HF_Status($this->config, $this, $this->tpl), "HF_Status", "Status500"); + + } + } + + private function loadController($class, $classname, $method, $args = array()) + { + $this->class = $class; + $this->classname = $classname; + $this->method = $method; + $this->args = $args; + } - public function run() + public function run($err=false) { - $call = new ReflectionFunction($this->$classname, $this->$method); - $call->invokeArgs($this->$class, $this->$args); + try + { + $call = new ReflectionMethod($this->classname, $this->method); + if ($err) + { + $call->invokeArgs($this->class, $this->args); + return; + } + + $numOfReqPara = $call->getNumberOfRequiredParameters(); + $numOfOptPara = $call->getNumberOfParameters() - $numOfReqPara; + $remainparas = count($this->args) - $numOfReqPara; + if ($remainparas >= 0 && $remainparas <= $numOfOptPara) + { + $call->invokeArgs($this->class, $this->args); + } + else + { + $this->load404Controller(); + $this->run(true); + } + + } catch (ReflectionException $e) + { + if (strstr($e->getMessage(), "does not exist") !== false) + { + $this->load404Controller(); + } else { + $this->load500Controller(); + } + $this->run(true); + if ($this->config["DEBUG"]) + echo vdump($e, $this); + else + $this->mail_admins("[Exception - " . $this->config["SITE_NAME"] . "]", vdump($e, $this), true); + + + } catch (Exception $e) { + $this->load500Controller(); + $this->run(true); + if ($this->config["DEBUG"]) + echo vdump($e, $this); + else + $this->mail_admins("[Exception - " . $this->config["SITE_NAME"] . "]", vdump($e, $this), true); + } } + + public function mail_admins($subject, $msg, $html = false) + { + if (array_key_exists("ADMINS", $this->config)) + { + foreach($this->config["ADMINS"] as $email) + { + $this->mail_user($email, $subject, $msg, $html); + } + } + } + + public function mail_user($to, $subject, $msg, $html = false) + { + if ($this->config["USE_HF_SMTP"]) + { + $smtp = new HF_SMTP($this->config["SMTP_FROM"], $to, $subject, $msg, $this->config["SMTP_SERVER"], $this->config["SMTP_USER"], $this->config["SMTP_PASS"], $this->config["SMTP_PORT"]); + $smtp->send($html); + } else { + require_once "Mail.php"; + $smtp = null; + if ($this->$this->config["SMTP_USER"] && $this->config["SMTP_PASS"]) + $smtp = Mail::factory('smtp', array( + "host" => $this->config["SMTP_SERVER"], + "port" => $this->config["SMTP_PORT"], + "auth" => true, + 'username' => $this->config["SMTP_USER"], + 'password' => $this->config["SMTP_PASS"] + )); + else + $smtp = Mail::factory('smtp', array( + "host" => $this->config["SMTP_SERVER"], + "port" => $this->config["SMTP_PORT"] + )); + $headers = array ('From' => $this->config["SMTP_FROM"], + 'To' => $to, + 'Subject' => $subject); + $smtp->send($to, $headers, $msg); + } + } + + public static function error_handler($err_severity, $err_msg, $err_file, $err_line, array $err_context) + { + if (0 === error_reporting()) { return false;} + switch($err_severity) + { + case E_ERROR: throw new ErrorException ($err_msg, 0, $err_severity, $err_file, $err_line); + case E_WARNING: throw new WarningException ($err_msg, 0, $err_severity, $err_file, $err_line); + case E_PARSE: throw new ParseException ($err_msg, 0, $err_severity, $err_file, $err_line); + case E_NOTICE: throw new NoticeException ($err_msg, 0, $err_severity, $err_file, $err_line); + case E_CORE_ERROR: throw new CoreErrorException ($err_msg, 0, $err_severity, $err_file, $err_line); + case E_CORE_WARNING: throw new CoreWarningException ($err_msg, 0, $err_severity, $err_file, $err_line); + case E_COMPILE_ERROR: throw new CompileErrorException ($err_msg, 0, $err_severity, $err_file, $err_line); + case E_COMPILE_WARNING: throw new CoreWarningException ($err_msg, 0, $err_severity, $err_file, $err_line); + case E_USER_ERROR: throw new UserErrorException ($err_msg, 0, $err_severity, $err_file, $err_line); + case E_USER_WARNING: throw new UserWarningException ($err_msg, 0, $err_severity, $err_file, $err_line); + case E_USER_NOTICE: throw new UserNoticeException ($err_msg, 0, $err_severity, $err_file, $err_line); + case E_STRICT: throw new StrictException ($err_msg, 0, $err_severity, $err_file, $err_line); + case E_RECOVERABLE_ERROR: throw new RecoverableErrorException ($err_msg, 0, $err_severity, $err_file, $err_line); + case E_DEPRECATED: throw new DeprecatedException ($err_msg, 0, $err_severity, $err_file, $err_line); + case E_USER_DEPRECATED: throw new UserDeprecatedException ($err_msg, 0, $err_severity, $err_file, $err_line); + } + + } } \ No newline at end of file diff --git a/system/engine/exceptions.php b/system/engine/exceptions.php new file mode 100644 index 0000000..69b0076 --- /dev/null +++ b/system/engine/exceptions.php @@ -0,0 +1,18 @@ +](http://code.google.com/p/h2o-template/downloads) + +With Git + +`git clone http://github.com/speedmax/h2o-php.git` + +With SVN + +`svn checkout http://h2o-template.googlecode.com/svn/trunk/ h2o` + +### Installation + 1. Download and extract h2o into your project path or your php include path + + Sample file structure setup + + myawesome_app/ + index.php + templates/ + index.html + h2o/ + + + 2. Use `require 'h2o/h2o.php'` in your php files to include the h2o library. + 3. Below is a basic code sample to get your project going. + 3. Check out the *\example* and *\specs* dirs to see some of h2o's more interesting features in action. + +*templates/index.html* + + + Hello world + + Greetings {{ name }} + + + +*index.php* + + render(array('name'=>'Peter Jackson')); + ?> + + +Useful links +------------------------ + + * Please submit patches or bug reports to our [lighthouse bug tracker][issue]. + * Check out our [Google group][group] for h2o-related discussion. + + [issue]:http://idealian.lighthouseapp.com/projects/11041-h2o-template-language + [group]:http://groups.google.com/group/h2o-template-php + + +Syntax explanation +------------------------ + +## variable +`{{ variable }}` + +Use dot (.) to access attributes of a variable + +#### variable lookup order +1. key of an associative array +2. array-index +3. object attribute +4. object method call + +**Example** + +*in your template* + + {{ person.name }} + +*in php* + + 'Peter Jackson', 'age' => 25 + ); + $h2o->render(compact('person')); + ?> + +Let's say that you have assigned the value `Peter Jackson` to a 'person' variable in your php script. The following +variable tag will print out `Peter Jackson`. + +## Filters + +Filters are variable modifiers that manipulate or format the value of a variable. +A filter usually looks like this `{{ person.name|capitalize }}`, a pipe ( | ) +character after a variable, plus a filter name, will cause H2O to apply the filter. + +__Filter chaining__ + +![filter chaining](http://wiki.shopify.com/upload/8/8c/Filterchain.jpg) +Let me borrow the image from liquid template + +You can chain multiple filters together and use a pipe ( | ) character to separate +them. `{{ document.body|escape|nl2br }}` + +__Filter arguments__ +Filters can accept arguments. For example: +`{{ document.description|truncate 20 }}` +will display the first 20 characters of the document's description. + +Moreover, there are cases where you might want to pass in multiple arguments. +Use commas ( , ) to separate them: +`{{ person.bio|truncate 20, "..." }}` + +__Filter named arguments__ +h2o uses colons ( : ) for named arguments. These allow you to build 'optional argument' arrays. + +`{{ '/images/logo.png' | image_tag width:450, height:250, alt:"company logo" }}` + +The above code translated to php will be like the below snippet, which resembles what happens internally: + + 450, + 'height' => 250, + 'alt'=>'company logo' + )); + ?> + +Note: Difference with Django, Smarty +H2o does not use the colon ( : ) character to separate arguments for readability reasons, +H2o uses the comma ( , ) which is more logical. + + +## Tag + +`{% tag %}` + +Tags are very powerful, as they control the logical flow and structure of a template, +There are inline tags `{% inline_tag %}` or tags that requires a +close tag. For example: `{% if condition %} ... {% endif %}` + + +### The "if" tag + +`if` tags evaluate either a variable or a simple expression. If the result of the `if` +expression is *true*, then the contents of the `if` block will be allowed to render. + + {% if person.is_adult %} + You are old enough. + {% else %} + sorry, you are too young for that. + {% endif %} + +### The "for" tag + +`for` tags allow iteratation over an array of items. + + {% for task in tasks %} + {{ task }} + {% endfor %} + +The above snippet will print out each "task" in the "tasks" array. + +Template inheritance +------------------------ +H2o supports template inheritance. Inheritance allows you to factor out a lot +of common code that would otherwise be duplicated across most of your templates. + +Template inheritance is implemented using the `block` and `extends` tags, with child templates +*extending* their parent templates. +**Word of Caution:** + * H2o templates only support single inheritance (just like PHP!), and currently do not support deep inheritance chains. + + +Quote from the Django docs: +> ... a base skeleton template that contains all the common elements of your +> site and defines blocks that child templates can override. + + +*Example:* + +_base.html_ - defines the base structure of our pages. + + + {%block title %}This is a page title {% endblock %} + +
+ {% block content%} +

Page title

+

H2O template inheritance is a powerful tool

+ {% endblock %} +
+ + + + + +As you can see, the base template is a typical web page using a two column layout. +We defined two blocks (`content` and `sidebar`) and HTML code common across all of our pages. + + +_page.html_ - defines a page-specific template. + + {% extends 'base.html' %} + + {% block content %} +

extended page

+

Body of extended page

+ {% endblock %} + + {% block sidebar %} + Sidebar contains use links. + {% endblock %} + + +The `page.html` extends `base.html`, allowing us to override any block +previously defined in `base.html`. + +Below is an excellent article about template inheritance in Django. If you wish to understand H2o's +template-inheritance system, this would be a great spot to start, since H2o's template-inheritance system +is strongly influenced by Django's. + +[Power of inheritance][3] is a very good blog post explaining inheritance + + [3]:http://www2.jeffcroft.com/blog/2006/feb/25/django-templates-the-power-of-inheritance/ + +*Tips* + +* If you have found that you have several common elements inside the same template, it may be a + good idea to put that portion of the template inside a `block` in a base template. +* `block` give you a hook, which is useful, since these can help with javascript and css too. +* When defining a block use a short and distinctive name + + + +### Configuration +There are a range of options for configuring the template engine. + + [option_value] + )); + ?> + +#### Loader +The name of the loader or an instance of H2o_Loader + +__Use file loader [default]__ + +` $template = new H2o('index.html', array('loader'=>'file'); ` + + +__Advanced setup__ + $loader ); + ?> + +__Use dictionary loader__ + +If you want to load templates from resources other than files, then this will be your +friend. H2o uses `dict_loader()` for testing. + + 'Hello {{ person }}' + )); + $template = new H2o('index.html', array('loader' => $loader')); + ?> + +#### Searchpath + +default: this will be the base path of your template + +H2o use this path to load additional templates and extensions. + +You can either explicity set the search path, + +`$template = new H2o('index.html', array('searchpath' => '/sites/common_templates'));` + +or h2o will try to find the searchpath for you. + +`$template = new H2o('/sites/common_templates/index.html');` + +#### Cache +You can define the type of caching engine h2o should use, if any. +Set 'cache' to false to disable caching. +You can read more about performance and caching in following sections + +Use file cache [default] + +`$template = new H2o('index.html', array('cache'=>'file'));` + +Use apc cache: + +`$template = new H2o('index.html', array('cache'=>'apc'));` + +Use memcache cache + +`$template = new H2o('index.html', array('cache'=>'memcache'));` + +Disable caching + +`$template = new H2o('index.html', array('cache'=>false));` + +#### Cache_dir +When the file cache is used, you can define where you want templates to be cached. + +It will put a cached template in same location as the normal template + +`$template = new H2o('index.html', array('cache_dir'=>'/tmp/cached_templates'));` + +#### Cache_ttl +"cache_ttl" specifies how long a cached template should be used (defaults: 1 hour) before it is recompiled. The template fragment cache +that is bundled in the cache extension will use this as default ttl value. + +`$template = new H2o('index.html', array('cache_ttl' => 3600));` + + +Performance and Caching +------------------------ + +Caching can increase performance since it skips step of inefficient template parsing. +H2o caches the template objects (the internal data structure of a template) and the bundled +caching backends include File, APC, and Memcache. + +### File cache +By default h2o uses the file cache to store template objects. Change h2o option `cache_dir` to where you +want to store template cache (ie: /tmp). + + 'file', + 'cache_dir' => '/tmp' + )); + ?> + +### APC cache +APC is an op-code and object cache extension for php whose performance is +generally 10-30% better than just plain file caching. + + 'apc')); + ?> + +### Memcache +Currently not implemented + + +Extending H2o +------------------------ + + +Known issues +------------------------ +Yes, h2o has them. However, if you are careful, these shouldn't hinder your template development. +The deep inheritance issue is a bit problematic for some template architectures, but again, if you +are careful, and perhaps a bit inventive, it won't hinder you terribly much. + + * `{{ block.super }}` doesn't work with more than 1 level of inheritance yet, so if `{{ block.super }}` + invokes another `{{ block.super }}` it won't work just yet. + * 'if' conditions don't support multiple expressions or mathematical expressions yet, like: + `{% if something > 3 or something < 2 %}` or `{% if something + else > 12 %}` + These likely will not be implemented in the future unless some daring soul implements them and + contributes the code back to the h2o-php project. + + +Contributors +--- + - Taylor Luk - Founder of [Issue publishing](http://issueapp.com) + - jlogsdon - Major refactoring (wip) and bug fixes + - cyberklin - Added filter support for any context resolve + - idlesign - Added if_changed tag support + - metropolis - Improved our test coverage + - plus many others + + +Credit +------------------------ +H2o borrows ideas and/or concepts from the following projects: + + - Django template - Django web development framework. + - Ptemplates - Armin Ronacher's pet project for a django port in PHP. + - Jinja - Django inspired template in Python. + +Special Thanks: Armin Ronacher, since early versions of h2o were based off of his Ptemplates project. + +The MIT License +------------------------ +Copyright (c) 2008 Taylor Luk + +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. diff --git a/system/vendor/StackTracePrint.php b/system/vendor/StackTracePrint.php new file mode 100644 index 0000000..df4506d --- /dev/null +++ b/system/vendor/StackTracePrint.php @@ -0,0 +1,66 @@ +"; + + $ret .- "".htmlspecialchars(trim($code[$backtrace[0]['line']-1]))."\n"; + + $ret .= "\n"; + + ob_start(); + + foreach ($args as $arg) + var_dump($arg); + + $str = ob_get_contents(); + + ob_end_clean(); + + $str = preg_replace('/=>(\s+)/', ' => ', $str); + $str = preg_replace('/ => NULL/', ' → NULL', $str); + $str = preg_replace('/}\n(\s+)\[/', "}\n\n".'$1[', $str); + $str = preg_replace('/ (float|int)\((\-?[\d\.]+)\)/', " $1 $2", $str); + + $str = preg_replace('/array\((\d+)\) {\s+}\n/', "array•$1 []", $str); + $str = preg_replace('/ string\((\d+)\) \"(.*)\"/', " str•$1 '$2'", $str); + $str = preg_replace('/\[\"(.+)\"\] => /', "'$1' → ", $str); + $str = preg_replace('/object\((\S+)\)#(\d+) \((\d+)\) {/', "obj•$2 $1[$3] {", $str); + $str = str_replace("bool(false)", "bool•false", $str); + $str = str_replace("bool(true)", "bool•true", $str); + + $ret .= $str; + + + + $ret .= ""; + + return $ret; + + + +} + +// Original - http://www.php.net/manual/en/function.debug-print-backtrace.php#86932 +function debug_string_backtrace() { + ob_start(); + debug_print_backtrace(); + $trace = ob_get_contents(); + ob_end_clean(); + + // Remove first item from backtrace as it's this function which + // is redundant. + $trace = preg_replace ('/^#0\s+' . __FUNCTION__ . "[^\n]*\n/", '', $trace, 1); + + // Renumber backtrace items. + $trace = preg_replace ('/^#(\d+)/me', '\'#\' . ($1 - 1)', $trace); + + $trace = wordwrap($trace, 123, "
"); + return $trace; +} \ No newline at end of file diff --git a/system/vendor/h2o.php b/system/vendor/h2o.php new file mode 100644 index 0000000..25f7ac0 --- /dev/null +++ b/system/vendor/h2o.php @@ -0,0 +1,266 @@ +'file')); + * + * + * $h2o = new H2O('template.html', array("loader"=>'hash')); + */ +class H2o { + var $searchpath; + var $context; + var $loader = false; + + static $tags = array(); + static $filters = array(); + static $extensions = array(); + + static function getOptions($options = array()) { + return array_merge(array( + 'loader' => 'file', + 'cache' => 'file', // file | apc | memcache + 'cache_prefix' => 'h2o_', + 'cache_ttl' => 3600, // file | apc | memcache + 'searchpath' => false, + 'autoescape' => true, + + // Enviroment setting + 'BLOCK_START' => '{%', + 'BLOCK_END' => '%}', + 'VARIABLE_START' => '{{', + 'VARIABLE_END' => '}}', + 'COMMENT_START' => '{*', + 'COMMENT_END' => '*}', + 'TRIM_TAGS' => true + ), $options); + } + + function __construct($file = null, $options = array()) { + # Init a environment + $this->options = $this->getOptions($options); + $loader = $this->options['loader']; + + if (!$loader) + return true; + + if (is_object($loader)) { + $this->loader = $loader; + $this->loader->setOptions($this->options); + } else { + $loader = "H2o_{$loader}_Loader"; + if (!class_exists($loader, false)) + throw new Exception('Invalid template loader'); + + if (isset($options['searchpath'])){ + $this->searchpath = $options['searchpath']; + } else if ($file) { + $this->searchpath = dirname(realpath($file)).DS; + } else { + $this->searchpath = getcwd().DS; + } + + $loader_searchpath = is_array($this->searchpath) ? $this->searchpath : array($this->searchpath); + $this->loader = new $loader($loader_searchpath, $this->options); + } + $this->loader->runtime = $this; + + if (isset($options['i18n'])) { + h2o::load('i18n'); + $this->i18n = new H2o_I18n($this->searchpath, $options['i18n']); + } + + if ($file) { + $this->nodelist = $this->loadTemplate($file); + } + } + + function loadTemplate($file) { + return $this->nodelist = $this->loader->read_cache($file); + } + + function loadSubTemplate($file) { + return $this->loader->read($file); + } + + # Build a finalized nodelist from template ready to be cached + function parse($source, $filename = '', $env = null) { + if (!$env) + $env = $this->options; + + if (!class_exists('H2o_Parser', false)) + require H2O_ROOT.'h2o/parser.php'; + + $parser = new H2o_Parser($source, $filename, $this, $env); + $nodelist = $parser->parse(); + return $nodelist; + } + + function set($context, $value = null) { + # replace with new context object + if (is_object($context) && $context instanceof H2o_Context) { + return $this->context = $context; + } + + # Init context + if (!$this->context) { + $this->context = new H2o_Context($this->defaultContext(), $this->options); + } + + # Extend or set value + if (is_array($context)) { + return $this->context->extend($context); + } + elseif (is_string($context)) { + return $this->context[$context] = $value; + } + return false; + } + + # Render the nodelist + function render($context = array()) { + $this->set($context); + + $this->stream = new StreamWriter; + $this->nodelist->render($this->context, $this->stream); + return $this->stream->close(); + } + + static function parseString($source, $options = array()) { + $instance = new H2o(null, array_merge($options, array('loader' => false))); + $instance->nodelist = $instance->parse($source); + return $instance; + } + + static function &createTag($tag, $args = null, $parser, $position = 0) { + if (!isset(self::$tags[$tag])) { + throw new H2o_Error($tag . " tag doesn't exist"); + } + $tagClass = self::$tags[$tag]; + $tag = new $tagClass($args, $parser, $position); + return $tag; + } + + /** + * Register a new tag + * + * + * h2o::addTag('tag_name', 'ClassName'); + * + * h2o::addTag(array( + * 'tag_name' => 'MagClass', + * 'tag_name2' => 'TagClass2' + * )); + * + * h2o::addTag('tag_name'); // Tag_name_Tag + * + * h2o::addTag(array('tag_name', + * @param unknown_type $tag + * @param unknown_type $class + */ + static function addTag($tag, $class = null) { + $tags = array(); + if (is_string($tag)) { + if (is_null($class)) + $class = ucwords("{$tag}_Tag"); + $tags[$tag] = $class; + } elseif (is_array($tag)) { + $tags = $tag; + } + + foreach ($tags as $tag => $tagClass) { + if (is_integer($tag)) { + unset($tags[$tag]); + $tag = $tagClass; + $tagClass = ucwords("{$tagClass}_Tag"); + } + if (!class_exists($tagClass, false)) { + throw new H2o_Error("{$tagClass} tag is not found"); + } + $tags[$tag] = $tagClass; + } + self::$tags = array_merge(self::$tags, $tags); + } + + /** + * Register a new filter to h2o runtime + * + * @param unknown_type $filter + * @param unknown_type $callback + * @return unknown + */ + static function addFilter($filter, $callback = null) { + if (is_array($filter)) { + $filters = $filter; + foreach($filters as $key => $filter) { + if (is_numeric($key)) + $key = $filter; + self::addFilter($key, $filter); + } + return true; + } elseif (is_string($filter) && class_exists($filter, false) && is_subclass_of($filter, 'FilterCollection')) { + foreach (get_class_methods($filter) as $f) { + if (is_callable(array($filter, $f))) + self::$filters[$f] = array($filter, $f); + } + return true; + } + if (is_null($callback)) + $callback = $filter; + + if (!is_callable($callback)) { + return false; + } + self::$filters[$filter] = $callback; + } + + static function addLookup($callback) { + if (is_callable($callback)) { + H2o_Context::$lookupTable[] = $callback; + } else die('damm it'); + } + + static function load($extension, $file = null) { + if (!$file) { + $file = H2O_ROOT.'ext'.DS.$extension.'.php'; + } + if (is_file($file)) { + include_once ($file); + self::$extensions[$extension] = $file; + } + } + + function defaultContext() { + return array('h2o' => new H2o_Info); + } +} + +/** + * Convenient wrapper for loading template file or string + * @param $name + * @param $options - H2o options + * @return Instance of H2o Template + */ +function h2o($name, $options = array()) { + $is_file = '/([^\s]*?)(\.[^.\s]*$)/'; + + if (!preg_match($is_file, $name)) { + return H2o::parseString($name, $options); + } + + $instance = new H2o($name, $options); + return $instance; +} + +?> diff --git a/system/vendor/h2o/.htaccess b/system/vendor/h2o/.htaccess new file mode 100644 index 0000000..14249c5 --- /dev/null +++ b/system/vendor/h2o/.htaccess @@ -0,0 +1 @@ +Deny from all \ No newline at end of file diff --git a/system/vendor/h2o/context.php b/system/vendor/h2o/context.php new file mode 100644 index 0000000..20ab752 --- /dev/null +++ b/system/vendor/h2o/context.php @@ -0,0 +1,266 @@ + 0, 'last'=> 1, 'length'=> 2, 'size'=> 3); + static $lookupTable = array(); + + function __construct($context = array(), $options = array()){ + if (is_object($context)) + $context = get_object_vars($context); + $this->scopes = array($context); + + if (isset($options['safeClass'])) + $this->safeClass = array_merge($this->safeClass, $options['safeClass']); + + if (isset($options['autoescape'])) + $this->autoescape = $options['autoescape']; + + $this->options = $options; + } + + function push($layer = array()){ + return array_unshift($this->scopes, $layer); + } + + /** + * pop the most recent layer + */ + function pop() { + if (!isset($this->scopes[1])) + throw new Exception('cannnot pop from empty stack'); + return array_shift($this->scopes); + } + + function offsetExists($offset) { + foreach ($this->scopes as $layer) { + if (isset($layer[$offset])) return true; + } + return false; + } + + function offsetGet($key) { + foreach ($this->scopes as $layer) { + if (isset($layer[$key])) + return $layer[$key]; + } + return; + } + + function offsetSet($key, $value) { + if (strpos($key, '.') > -1) + throw new Exception('cannot set non local variable'); + return $this->scopes[0][$key] = $value; + } + + function offsetUnset($key) { + foreach ($this->scopes as $layer) { + if (isset($layer[$key])) unset($layer[$key]); + } + } + + function extend($context) { + $this->scopes[0] = array_merge($this->scopes[0], $context); + } + + function set($key, $value) { + return $this->offsetSet($key, $value); + } + + function get($key) { + return $this->offsetGet($key); + } + + function isDefined($key) { + return $this->offsetExists($key); + } + /** + * + * + * + * Variable name + * + * @param $var variable name or array(0 => variable name, 'filters' => filters array) + * @return unknown_type + */ + function resolve($var) { + + # if $var is array - it contains filters to apply + $filters = array(); + if ( is_array($var) ) { + + $name = array_shift($var); + $filters = isset($var['filters'])? $var['filters'] : array(); + + } + else $name = $var; + + $result = null; + + # Lookup basic types, null, boolean, numeric and string + # Variable starts with : (:users.name) to short-circuit lookup + if ($name[0] === ':') { + $object = $this->getVariable(substr($name, 1)); + if (!is_null($object)) $result = $object; + } else { + if ($name === 'true') { + $result = true; + } + elseif ($name === 'false') { + $result = false; + } + elseif (preg_match('/^-?\d+(\.\d+)?$/', $name, $matches)) { + $result = isset($matches[1])? floatval($name) : intval($name); + } + elseif (preg_match('/^"([^"\\\\]*(?:\\.[^"\\\\]*)*)"|' . + '\'([^\'\\\\]*(?:\\.[^\'\\\\]*)*)\'$/', $name)) { + $result = stripcslashes(substr($name, 1, -1)); + } + } + if (!empty(self::$lookupTable) && $result == Null) { + $result = $this->externalLookup($name); + } + $result = $this->applyFilters($result,$filters); + return $result; + } + + function getVariable($name) { + # Local variables. this gives as a bit of performance improvement + if (!strpos($name, '.')) + return $this[$name]; + + # Prepare for Big lookup + $parts = explode('.', $name); + $object = $this[array_shift($parts)]; + + # Lookup context + foreach ($parts as $part) { + if (is_array($object) or $object instanceof ArrayAccess) { + if (isset($object[$part])) { + $object = $object[$part]; + } elseif ($part === 'first') { + $object = isset($object[0])?$object[0]:null; + } elseif ($part === 'last') { + $last = count($object)-1; + $object = isset($object[$last])?$object[$last]:null; + } elseif ($part === 'size' or $part === 'length') { + return count($object); + } else { + return null; + } + } + elseif (is_object($object)) { + if (isset($object->$part)) + $object = $object->$part; + elseif (is_callable(array($object, $part))) { + $methodAllowed = in_array(get_class($object), $this->safeClass) || + (isset($object->h2o_safe) && ( + $object->h2o_safe === true || in_array($part, $object->h2o_safe) + ) + ); + $object = $methodAllowed ? $object->$part() : null; + } + else return null; + } + else return null; + } + return $object; + } + + function applyFilters($object, $filters) { + + foreach ($filters as $filter) { + $name = substr(array_shift($filter), 1); + $args = $filter; + + if (isset(h2o::$filters[$name])) { + foreach ($args as $i => $argument) { + # name args + if (is_array($argument)) { + foreach ($argument as $n => $arg) { + $args[$i][$n] = $this->resolve($arg); + } + } else { + # resolve argument values + $args[$i] = $this->resolve($argument); + } + } + array_unshift($args, $object); + $object = call_user_func_array(h2o::$filters[$name], $args); + } + } + return $object; + } + + function escape($value, $var) { + + $safe = false; + $filters = (is_array($var) && isset($var['filters']))? $var['filters'] : array(); + + foreach ( $filters as $filter ) { + + $name = substr(array_shift($filter), 1); + $safe = !$safe && ($name === 'safe'); + + $escaped = $name === 'escape'; + } + + $should_escape = $this->autoescape || isset($escaped) && $escaped; + + if ( ($should_escape && !$safe)) { + $value = htmlspecialchars($value); + } + + return $value; + } + + function externalLookup($name) { + if (!empty(self::$lookupTable)) { + foreach (self::$lookupTable as $lookup) { + $tmp = call_user_func_array($lookup, array($name, $this)); + if ($tmp !== null) + return $tmp; + } + } + return null; + } +} + +class BlockContext { + var $h2o_safe = array('name', 'depth', 'super'); + var $block, $index; + private $context; + + function __construct($block, $context, $index) { + $this->block =& $block; + $this->context = $context; + $this->index = $index; + } + + function name() { + return $this->block->name; + } + + function depth() { + return $this->index; + } + + function super() { + $stream = new StreamWriter; + $this->block->parent->render($this->context, $stream, $this->index+1); + return $stream->close(); + } + + function __toString() { + return "[BlockContext : {$this->block->name}, {$this->block->filename}]"; + } +} +?> diff --git a/system/vendor/h2o/datatype.php b/system/vendor/h2o/datatype.php new file mode 100644 index 0000000..da174af --- /dev/null +++ b/system/vendor/h2o/datatype.php @@ -0,0 +1,173 @@ +close = false; + } + + function write($data) { + if ($this->close) + new Exception('tried to write to closed stream'); + $this->buffer[] = $data; + } + + function close() { + $this->close = true; + return implode('', $this->buffer); + } +} + +class Evaluator { + static function gt($l, $r) { return $l > $r; } + static function ge($l, $r) { return $l >= $r; } + + static function lt($l, $r) { return $l < $r; } + static function le($l, $r) { return $l <= $r; } + + static function eq($l, $r) { return $l == $r; } + static function ne($l, $r) { return $l != $r; } + + static function not_($bool) { return !$bool; } + static function and_($l, $r) { return ($l && $r); } + static function or_($l, $r) { return ($l && $r); } + + # Currently only support single expression with no preceddence ,no boolean expression + # [expression] = [optional binary] ? operant [ optional compare operant] + # [operant] = variable|string|numeric|boolean + # [compare] = > | < | == | >= | <= + # [binary] = not | ! + static function exec($args, $context) { + $argc = count($args); + $first = array_shift($args); + $first = $context->resolve($first); + switch ($argc) { + case 1 : + return $first; + case 2 : + if (is_array($first) && isset($first['operator']) && $first['operator'] == 'not') { + $operant = array_shift($args); + $operant = $context->resolve($operant); + return !($operant); + } + case 3 : + list($op, $right) = $args; + $right = $context->resolve($right); + return call_user_func(array("Evaluator", $op['operator']), $first, $right); + default: + return false; + } + } +} + +/** + * $type of token, Block | Variable + */ +class H2o_Token { + function __construct ($type, $content, $position) { + $this->type = $type; + $this->content = $content; + $this->result=''; + $this->position = $position; + } + + function write($content){ + $this->result= $content; + } +} + +/** + * a token stream + */ +class TokenStream { + var $pushed; + var $stream; + var $closed; + var $c; + + function __construct() { + $this->pushed = array(); + $this->stream = array(); + $this->closed = false; + } + + function pop() { + if (count($this->pushed)) + return array_pop($this->pushed); + return array_pop($this->stream); + } + + function feed($type, $contents, $position) { + if ($this->closed) + throw new Exception('cannot feed closed stream'); + $this->stream[] = new H2o_Token($type, $contents, $position); + } + + function push($token) { + if (is_null($token)) + throw new Exception('cannot push NULL'); + if ($this->closed) + $this->pushed[] = $token; + else + $this->stream[] = $token; + } + + function close() { + if ($this->closed) + new Exception('cannot close already closed stream'); + $this->closed = true; + $this->stream = array_reverse($this->stream); + } + + function isClosed() { + return $this->closed; + } + + function current() { + return $this->c ; + } + + function next() { + return $this->c = $this->pop(); + } +} + +class H2o_Info { + var $h2o_safe = array('filters', 'extensions', 'tags'); + var $name = 'H2o Template engine'; + var $description = "Django inspired template system"; + var $version = H2O_VERSION; + + function filters() { + return array_keys(h2o::$filters); + } + + function tags() { + return array_keys(h2o::$tags); + } + + function extensions() { + return array_keys(h2o::$extensions); + } +} + +/** + * Functions + */ +function sym_to_str($string) { + return substr($string, 1); +} + +function is_sym($string) { + return isset($string[0]) && $string[0] === ':'; +} + +function symbol($string) { + return ':'.$string; +} + +function strip_regex($regex, $delimiter = '/') { + return substr($regex, 1, strrpos($regex, $delimiter)-1); +} +?> diff --git a/system/vendor/h2o/errors.php b/system/vendor/h2o/errors.php new file mode 100644 index 0000000..7959c2f --- /dev/null +++ b/system/vendor/h2o/errors.php @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/system/vendor/h2o/filters.php b/system/vendor/h2o/filters.php new file mode 100644 index 0000000..a8cf3d3 --- /dev/null +++ b/system/vendor/h2o/filters.php @@ -0,0 +1,369 @@ + $value) { + $result .= $name.'='.urlencode($value).'&'.$querystring; + } + $querystring = substr($result, 0, strlen($result)-1); + return htmlspecialchars($result); + } else { + return urlencode($data); + } + } + + static function hyphenize ($string) { + $rules = array('/[^\w\s-]+/'=>'','/\s+/'=>'-', '/-{2,}/'=>'-'); + $string = preg_replace(array_keys($rules), $rules, trim($string)); + return $string = trim(strtolower($string)); + } + + static function urlize( $string, $truncate = false ) { + $reg_exUrl = "/(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(\/\S*)?/"; + preg_match_all($reg_exUrl, $string, $matches); + $usedPatterns = array(); + foreach($matches[0] as $pattern){ + if(!array_key_exists($pattern, $usedPatterns)){ + $usedPatterns[$pattern]=true; + $string = str_replace($pattern, "{$pattern}", $string); + } + } + + $reg_exEmail = "/[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}/"; + preg_match_all($reg_exEmail, $string, $matches); + $usedPatterns = array(); + foreach($matches[0] as $pattern){ + if(!array_key_exists($pattern, $usedPatterns)){ + $usedPatterns[$pattern]=true; + $string = str_replace($pattern, "{$pattern}", $string); + } + } + return $string; + } + + static function set_default($object, $default) { + return !$object ? $default : $object; + } +} + +class StringFilters extends FilterCollection { + + static function humanize($string) { + $string = preg_replace('/\s+/', ' ', trim(preg_replace('/[^A-Za-z0-9()!,?$]+/', ' ', $string))); + return capfirst($string); + } + + static function capitalize($string) { + return ucwords(strtolower($string)) ; + } + + static function titlize($string) { + return self::capitalize($string); + } + + static function capfirst($string) { + $string = strtolower($string); + return strtoupper($string{0}). substr($string, 1, strlen($string)); + } + + static function tighten_space($value) { + return preg_replace("/\s{2,}/", ' ', $value); + } + + static function escape($value, $attribute = false) { + return htmlspecialchars($value, $attribute ? ENT_QUOTES : ENT_NOQUOTES); + } + + static function escapejson($value) { + // The standard django escapejs converts all non-ascii characters into hex codes. + // This function encodes the entire data structure, and strings get quotes around them. + return json_encode($value); + } + + static function force_escape($value, $attribute = false) { + return self::escape($value, $attribute); + } + + static function e($value, $attribute = false) { + return self::escape($value, $attribute); + } + + static function safe($value) { + return $value; + } + + static function truncate ($string, $max = 50, $ends = '...') { + return (strlen($string) > $max ? substr($string, 0, $max).$ends : $string); + } + + static function limitwords($text, $limit = 50, $ends = '...') { + if (strlen($text) > $limit) { + $words = str_word_count($text, 2); + $pos = array_keys($words); + + if (isset($pos[$limit])) { + $text = substr($text, 0, $pos[$limit]) . $ends; + } + } + return $text; + } +} + +class NumberFilters extends FilterCollection { + static function filesize ($bytes, $round = 1) { + if ($bytes === 0) + return '0 bytes'; + elseif ($bytes === 1) + return '1 byte'; + + $units = array( + 'bytes' => pow(2, 0), 'kB' => pow(2, 10), + 'BM' => pow(2, 20), 'GB' => pow(2, 30), + 'TB' => pow(2, 40), 'PB' => pow(2, 50), + 'EB' => pow(2, 60), 'ZB' => pow(2, 70) + ); + + $lastUnit = 'bytes'; + foreach ($units as $unitName => $unitFactor) { + if ($bytes >= $unitFactor) { + $lastUnit = $unitName; + } else { + $number = round( $bytes / $units[$lastUnit], $round ); + return number_format($number) . ' ' . $lastUnit; + } + } + } + + static function currency($amount, $currency = 'USD', $precision = 2, $negateWithParentheses = false) { + $definition = array( + 'EUR' => array('�','.',','), 'GBP' => '�', 'JPY' => '�', + 'USD'=>'$', 'AU' => '$', 'CAN' => '$' + ); + $negative = false; + $separator = ','; + $decimals = '.'; + $currency = strtoupper($currency); + + // Is negative + if (strpos('-', $amount) !== false) { + $negative = true; + $amount = str_replace("-","",$amount); + } + $amount = (float) $amount; + + if (!$negative) { + $negative = $amount < 0; + } + if ($negateWithParentheses) { + $amount = abs($amount); + } + + // Get rid of negative zero + $zero = round(0, $precision); + if (round($amount, $precision) === $zero) { + $amount = $zero; + } + + if (isset($definition[$currency])) { + $symbol = $definition[$currency]; + if (is_array($symbol)) + @list($symbol, $separator, $decimals) = $symbol; + } else { + $symbol = $currency; + } + $amount = number_format($amount, $precision, $decimals, $separator); + + return $negateWithParentheses ? "({$symbol}{$amount})" : "{$symbol}{$amount}"; + } +} + +class HtmlFilters extends FilterCollection { + static function base_url($url, $options = array()) { + return $url; + } + + static function asset_url($url, $options = array()) { + return self::base_url($url, $options); + } + + static function image_tag($url, $options = array()) { + $attr = self::htmlAttribute(array('alt','width','height','border'), $options); + return sprintf('', $url, $attr); + } + + static function css_tag($url, $options = array()) { + $attr = self::htmlAttribute(array('media'), $options); + return sprintf('', $url, $attr); + } + + static function script_tag($url, $options = array()) { + return sprintf('', $url); + } + + static function links_to($text, $url, $options = array()) { + $attrs = self::htmlAttribute(array('ref'), $options); + $url = self::base_url($url, $options); + return sprintf('%s', $url, $attrs, $text); + } + + static function links_with ($url, $text, $options = array()) { + return self::links_to($text, $url, $options); + } + + static function strip_tags($text) { + $text = preg_replace(array('//'), array(' <', '> '),$text); + return strip_tags($text); + } + + static function linebreaks($value, $format = 'p') { + if ($format === 'br') + return HtmlFilters::nl2br($value); + return HtmlFilters::nl2pbr($value); + } + + static function nl2br($value) { + return str_replace("\n", "
\n", $value); + } + + static function nl2pbr($value) { + $result = array(); + $parts = preg_split('/(\r?\n){2,}/m', $value); + foreach ($parts as $part) { + array_push($result, '

' . HtmlFilters::nl2br($part) . '

'); + } + return implode("\n", $result); + } + + protected static function htmlAttribute($attrs = array(), $data = array()) { + $attrs = self::extract(array_merge(array('id', 'class', 'title', "style"), $attrs), $data); + + $result = array(); + foreach ($attrs as $name => $value) { + $result[] = "{$name}=\"{$value}\""; + } + return join(' ', $result); + } + + protected static function extract($attrs = array(), $data=array()) { + $result = array(); + if (empty($data)) return array(); + foreach($data as $k => $e) { + if (in_array($k, $attrs)) $result[$k] = $e; + } + return $result; + } +} + +class DatetimeFilters extends FilterCollection { + static function date($time, $format = 'jS F Y H:i') { + if ($time instanceof DateTime) + $time = (int) $time->format('U'); + if (!is_numeric($time)) + $time = strtotime($time); + + return date($format, $time); + } + + static function relative_time($timestamp, $format = 'g:iA') { + if ($timestamp instanceof DateTime) + $timestamp = (int) $timestamp->format('U'); + + $timestamp = is_numeric($timestamp) ? $timestamp: strtotime($timestamp); + + $time = mktime(0, 0, 0); + $delta = time() - $timestamp; + $string = ''; + + if ($timestamp < $time - 86400) { + return date("F j, Y, g:i a", $timestamp); + } + if ($delta > 86400 && $timestamp < $time) { + return "Yesterday at " . date("g:i a", $timestamp); + } + + if ($delta > 7200) + $string .= floor($delta / 3600) . " hours, "; + else if ($delta > 3660) + $string .= "1 hour, "; + else if ($delta >= 3600) + $string .= "1 hour "; + $delta %= 3600; + + if ($delta > 60) + $string .= floor($delta / 60) . " minutes "; + else + $string .= $delta . " seconds "; + return "$string ago"; + } + + static function relative_date($time) { + if ($time instanceof DateTime) + $time = (int) $time->format('U'); + + $time = is_numeric($time) ? $time: strtotime($time); + $today = strtotime(date('M j, Y')); + $reldays = ($time - $today)/86400; + + if ($reldays >= 0 && $reldays < 1) + return 'today'; + else if ($reldays >= 1 && $reldays < 2) + return 'tomorrow'; + else if ($reldays >= -1 && $reldays < 0) + return 'yesterday'; + + if (abs($reldays) < 7) { + if ($reldays > 0) { + $reldays = floor($reldays); + return 'in ' . $reldays . ' day' . ($reldays != 1 ? 's' : ''); + } else { + $reldays = abs(floor($reldays)); + return $reldays . ' day' . ($reldays != 1 ? 's' : '') . ' ago'; + } + } + if (abs($reldays) < 182) + return date('l, F j',$time ? $time : time()); + else + return date('l, F j, Y',$time ? $time : time()); + } + + static function relative_datetime($time) { + $date = self::relative_date($time); + + if ($date === 'today') + return self::relative_time($time); + + return $date; + } +} + +/* Ultizie php funciton as Filters */ +h2o::addFilter(array('md5', 'sha1', 'numberformat'=>'number_format', 'wordwrap', 'trim', 'upper' => 'strtoupper', 'lower' => 'strtolower')); + +/* Add filter collections */ +h2o::addFilter(array('CoreFilters', 'StringFilters', 'NumberFilters', 'DatetimeFilters', 'HtmlFilters')); + +/* Alias default to set_default */ +h2o::addFilter('default', array('CoreFilters', 'set_default')); + +?> diff --git a/system/vendor/h2o/loaders.php b/system/vendor/h2o/loaders.php new file mode 100644 index 0000000..1a5975a --- /dev/null +++ b/system/vendor/h2o/loaders.php @@ -0,0 +1,290 @@ +searchpath = (array) $searchpath; + $this->setOptions($options); + } + + function setOptions($options = array()) { + if (isset($options['cache']) && $options['cache']) { + $this->cache = h2o_cache($options); + } + } + + function read($filename) { + + if (!is_file($filename)) + $filename = $this->get_template_path($this->searchpath,$filename); + + if (is_file($filename)) { + $source = file_get_contents($filename); + return $this->runtime->parse($source); + } else { + throw new TemplateNotFound($filename); + } + } + + function get_template_path($search_path, $filename){ + + + for ($i=0 ; $i < count($search_path) ; $i++) + { + + if(file_exists($search_path[$i] . $filename)) { + $filename = $search_path[$i] . $filename; + return $filename; + break; + } else { + continue; + } + + } + + throw new Exception('TemplateNotFound - Looked for template: ' . $filename); + + + + } + + function read_cache($filename) { + if (!$this->cache){ + $filename = $this->get_template_path($this->searchpath,$filename); + return $this->read($filename); + } + + if (!is_file($filename)){ + $filename = $this->get_template_path($this->searchpath,$filename); + } + + $filename = realpath($filename); + + $cache = md5($filename); + $object = $this->cache->read($cache); + $this->cached = $object && !$this->expired($object); + + if (!$this->cached) { + $nodelist = $this->read($filename); + $object = (object) array( + 'filename' => $filename, + 'content' => serialize($nodelist), + 'created' => time(), + 'templates' => $nodelist->parser->storage['templates'], + 'included' => $nodelist->parser->storage['included'] + array_values(h2o::$extensions) + ); + $this->cache->write($cache, $object); + } else { + foreach($object->included as $ext => $file) { + include_once (h2o::$extensions[$ext] = $file); + } + } + return unserialize($object->content); + } + + function flush_cache() { + $this->cache->flush(); + } + + function expired($object) { + if (!$object) return false; + + $files = array_merge(array($object->filename), $object->templates); + foreach ($files as $file) { + if (!is_file($file)) + $file = $this->get_template_path($this->searchpath, $file); + + if ($object->created < filemtime($file)) + return true; + } + return false; + } +} + +function file_loader($file) { + return new H2o_File_Loader($file); +} + +class H2o_Hash_Loader { + + function __construct($scope, $options = array()) { + $this->scope = $scope; + } + + function setOptions() {} + + function read($file) { + if (!isset($this->scope[$file])) + throw new TemplateNotFound; + return $this->runtime->parse($this->scope[$file], $file); + } + + function read_cache($file) { + return $this->read($file); + } +} + +function hash_loader($hash = array()) { + return new H2o_Hash_Loader($hash); +} + +/** + * Cache subsystem + * + */ +function h2o_cache($options = array()) { + $type = $options['cache']; + $className = "H2o_".ucwords($type)."_Cache"; + + if (class_exists($className, false)) { + return new $className($options); + } + return false; +} + +class H2o_File_Cache { + var $ttl = 3600; + var $prefix = 'h2o_'; + + function __construct($options = array()) { + if (isset($options['cache_dir']) && is_writable($options['cache_dir'])) { + $path = $options['cache_dir']; + } else { + $path = dirname($tmp = tempnam(uniqid(rand(), true), '')); + + if (file_exists($tmp)) unlink($tmp); + } + if (isset($options['cache_ttl'])) { + $this->ttl = $options['cache_ttl']; + } + if(isset($options['cache_prefix'])) { + $this->prefix = $options['cache_prefix']; + } + + $this->path = realpath($path). DS; + } + + function read($filename) { + if (!file_exists($this->path . $this->prefix. $filename)) + return false; + + $content = file_get_contents($this->path . $this->prefix. $filename); + $expires = (int)substr($content, 0, 10); + + if (time() >= $expires) + return false; + return unserialize(trim(substr($content, 10))); + } + + function write($filename, &$object) { + $expires = time() + $this->ttl; + $content = $expires . serialize($object); + return file_put_contents($this->path . $this->prefix. $filename, $content); + } + + function flush() { + foreach (glob($this->path. $this->prefix. '*') as $file) { + @unlink($file); + } + } +} + +class H2o_Apc_Cache { + var $ttl = 3600; + var $prefix = 'h2o_'; + + function __construct($options = array()) { + if (!function_exists('apc_add')) + throw new Exception('APC extension needs to be loaded to use APC cache'); + + if (isset($options['cache_ttl'])) { + $this->ttl = $options['cache_ttl']; + } + if(isset($options['cache_prefix'])) { + $this->prefix = $options['cache_prefix']; + } + } + + function read($filename) { + return apc_fetch($this->prefix.$filename); + } + + function write($filename, $object) { + return apc_store($this->prefix.$filename, $object, $this->ttl); + } + + function flush() { + return apc_clear_cache('user'); + } +} + + +class H2o_Memcache_Cache { + var $ttl = 3600; + var $prefix = 'h2o_'; + /** + * @var host default is file socket + */ + var $host = 'unix:///tmp/memcached.sock'; + var $port = 0; + var $object; + function __construct( $scope, $options = array() ) { + if ( !function_exists( 'memcache_set' ) ) + throw new Exception( 'Memcache extension needs to be loaded to use memcache' ); + + if ( isset( $options['cache_ttl'] ) ) { + $this->ttl = $options['cache_ttl']; + } + if( isset( $options['cache_prefix'] ) ) { + $this->prefix = $options['cache_prefix']; + } + + if( isset( $options['host'] ) ) { + $this->host = $options['host']; + } + + if( isset( $options['port'] ) ) { + $this->port = $options['port']; + } + + $this->object = memcache_connect( $this->host, $this->port ); + } + + function read( $filename ){ + return memcache_get( $this->object, $this->prefix.$filename ); + } + + function write( $filename, $content ) { + return memcache_set( $this->object,$this->prefix.$filename,$content , MEMCACHE_COMPRESSED,$this->ttl ); + } + + function flush(){ + return memcache_flush( $this->object ); + } +} \ No newline at end of file diff --git a/system/vendor/h2o/nodes.php b/system/vendor/h2o/nodes.php new file mode 100644 index 0000000..9368ea6 --- /dev/null +++ b/system/vendor/h2o/nodes.php @@ -0,0 +1,84 @@ +parser = $parser; + if (is_null($initial)) + $initial = array(); + $this->list = $initial; + $this->position = $position; + } + + function render($context, $stream) { + foreach($this->list as $node) { + $node->render($context, $stream); + } + } + + function append($node) { + array_push($this->list, $node); + } + + function extend($nodes) { + array_merge($this->list, $nodes); + } + + function getLength() { + return count($this->list); + } + + function getIterator() { + return new ArrayIterator( $this->list ); + } +} + +class VariableNode extends H2o_Node { + private $filters = array(); + var $variable; + + function __construct($variable, $filters, $position = 0) { + if (!empty($filters)) + $this->filters = $filters; + $this->variable = $variable; + } + + function render($context, $stream) { + $value = $context->resolve($this->variable); + $value = $context->escape($value, $this->variable); + $stream->write($value); + } +} + +class CommentNode extends H2o_Node {} + +class TextNode extends H2o_Node { + var $content; + function __construct($content, $position = 0) { + $this->content = $content; + $this->position = $position; + } + + function render($context, $stream) { + $stream->write($this->content); + } + + function is_blank() { + return strlen(trim($this->content)); + } +} + + +?> \ No newline at end of file diff --git a/system/vendor/h2o/parser.php b/system/vendor/h2o/parser.php new file mode 100644 index 0000000..530d00f --- /dev/null +++ b/system/vendor/h2o/parser.php @@ -0,0 +1,290 @@ +options = $options; + + $trim = ''; + if ($this->options['TRIM_TAGS']) + $trim = '(?:\r?\n)?'; + + $this->pattern = ('/\G(.*?)(?:' . + preg_quote($this->options['BLOCK_START']). '(.*?)' .preg_quote($this->options['BLOCK_END']) . $trim . '|' . + preg_quote($this->options['VARIABLE_START']). '(.*?)' .preg_quote($this->options['VARIABLE_END']) . '|' . + preg_quote($this->options['COMMENT_START']). '(.*?)' .preg_quote($this->options['COMMENT_END']) . $trim . ')/sm' + ); + } + + function tokenize($source) { + $result = new TokenStream; + $pos = 0; + $matches = array(); + preg_match_all($this->pattern, $source, $matches, PREG_SET_ORDER); + + foreach ($matches as $match) { + if ($match[1]) + $result->feed('text', $match[1], $pos); + $tagpos = $pos + strlen($match[1]); + if ($match[2]) + $result->feed('block', trim($match[2]), $tagpos); + elseif ($match[3]) + $result->feed('variable', trim($match[3]), $tagpos); + elseif ($match[4]) + $result->feed('comment', trim($match[4]), $tagpos); + $pos += strlen($match[0]); + } + if ($pos < strlen($source)){ + $result->feed('text', substr($source, $pos), $pos); + } + $result->close(); + return $result; + } +} + +class H2o_Parser { + var $first; + var $storage = array(); + var $filename; + var $runtime; + + function __construct($source, $filename, $runtime, $options) { + $this->options = $options; + //$this->source = $source; + $this->runtime = $runtime; + $this->filename = $filename; + $this->first = true; + + $this->lexer = new H2o_Lexer($options); + $this->tokenstream = $this->lexer->tokenize($source); + $this->storage = array( + 'blocks' => array(), + 'templates' => array(), + 'included' => array() + ); + } + + function &parse() { + $until = func_get_args(); + $nodelist = new NodeList($this); + while($token = $this->tokenstream->next()) { + //$token = $this->tokenstream->current(); + switch($token->type) { + case 'text' : + $node = new TextNode($token->content, $token->position); + break; + case 'variable' : + $args = H2o_Parser::parseArguments($token->content, $token->position); + $variable = array_shift($args); + $filters = $args; + $node = new VariableNode($variable, $filters, $token->position); + break; + case 'comment' : + $node = new CommentNode($token->content); + break; + case 'block' : + if (in_array($token->content, $until)) { + $this->token = $token; + return $nodelist; + } + $temp = preg_split('/\s+/',$token->content, 2); + $name = $temp[0]; + $args = (count($temp) > 1 ? $temp[1] : null); + $node = H2o::createTag($name, $args, $this, $token->position); + $this->token = $token; + } + $this->searching = join(',',$until); + $this->first = false; + $nodelist->append($node); + } + + if ($until) { + throw new TemplateSyntaxError('Unclose tag, expecting '. $until[0]); + } + return $nodelist; + } + + function skipTo($until) { + $this->parse($until); + return null; + } + + # Parse arguments + static function parseArguments($source = null, $fpos = 0){ + $parser = new ArgumentLexer($source, $fpos); + $result = array(); + $current_buffer = &$result; + $filter_buffer = array(); + $tokens = $parser->parse(); + foreach ($tokens as $token) { + list($token, $data) = $token; + if ($token == 'filter_start') { + $filter_buffer = array(); + $current_buffer = &$filter_buffer; + } + elseif ($token == 'filter_end') { + if (count($filter_buffer)) { + + $i = count($result)-1; + if ( is_array($result[$i]) ) $result[$i]['filters'][] = $filter_buffer; + else $result[$i] = array(0 => $result[$i], 'filters' => array($filter_buffer)); + } + $current_buffer = &$result; + } + elseif ($token == 'boolean') { + $current_buffer[] = ($data === 'true'? true : false); + } + elseif ($token == 'name') { + $current_buffer[] = symbol($data); + } + elseif ($token == 'number' || $token == 'string') { + $current_buffer[] = $data; + } + elseif ($token == 'named_argument') { + $last = $current_buffer[count($current_buffer) - 1]; + if (!is_array($last)) + $current_buffer[] = array(); + + $namedArgs =& $current_buffer[count($current_buffer) - 1]; + list($name,$value) = array_map('trim', explode(':', $data, 2)); + + # if argument value is variable mark it + $value = self::parseArguments($value); + $namedArgs[$name] = $value[0]; + } + elseif( $token == 'operator') { + $current_buffer[] = array('operator'=>$data); + } + } + return $result; + } +} + +class H2O_RE { + static $whitespace, $seperator, $parentheses, $pipe, $filter_end, $operator, $boolean, $number, $string, $i18n_string, $name, $named_args; + + static function init() { + $r = 'strip_regex'; + + self::$whitespace = '/\s+/m'; + self::$parentheses = '/\(|\)/m'; + self::$filter_end = '/;/'; + self::$boolean = '/true|false/'; + self::$seperator = '/,/'; + self::$pipe = '/\|/'; + self::$operator = '/\s?(>|<|>=|<=|!=|==|!|and |not |or )\s?/i'; + self::$number = '/\d+(\.\d*)?/'; + self::$name = '/[a-zA-Z][a-zA-Z0-9-_]*(?:\.[a-zA-Z_0-9][a-zA-Z0-9_-]*)*/'; + + self::$string = '/(?: + "([^"\\\\]*(?:\\\\.[^"\\\\]*)*)" | # Double Quote string + \'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\' # Single Quote String + )/xsm'; + self::$i18n_string = "/_\({$r(self::$string)}\) | {$r(self::$string)}/xsm"; + + self::$named_args = "{ + ({$r(self::$name)})(?:{$r(self::$whitespace)})? + : + (?:{$r(self::$whitespace)})?({$r(self::$i18n_string)}|{$r(self::$number)}|{$r(self::$name)}) + }x"; + } +} +H2O_RE::init(); + +class ArgumentLexer { + private $source; + private $match; + private $pos = 0, $fpos, $eos; + private $operator_map = array( + '!' => 'not', '!='=> 'ne', '==' => 'eq', '>' => 'gt', '<' => 'lt', '<=' => 'le', '>=' => 'ge' + ); + + function __construct($source, $fpos = 0){ + if (!is_null($source)) + $this->source = $source; + $this->fpos=$fpos; + } + + function parse(){ + $result = array(); + $filtering = false; + while (!$this->eos()) { + $this->scan(H2O_RE::$whitespace); + if (!$filtering) { + if ($this->scan(H2O_RE::$operator)){ + $operator = trim($this->match); + if(isset($this->operator_map[$operator])) + $operator = $this->operator_map[$operator]; + $result[] = array('operator', $operator); + } + elseif ($this->scan(H2O_RE::$boolean)) + $result[] = array('boolean', $this->match); + elseif ($this->scan(H2O_RE::$named_args)) + $result[] = array('named_argument', $this->match); + elseif ($this->scan(H2O_RE::$name)) + $result[] = array('name', $this->match); + elseif ($this->scan(H2O_RE::$pipe)) { + $filtering = true; + $result[] = array('filter_start', $this->match); + } + elseif ($this->scan(H2O_RE::$seperator)) + $result[] = array('separator', null); + elseif ($this->scan(H2O_RE::$i18n_string)) + $result[] = array('string', $this->match); + elseif ($this->scan(H2O_RE::$number)) + $result[] = array('number', $this->match); + else + throw new TemplateSyntaxError('unexpected character in filters : "'. $this->source[$this->pos]. '" at '.$this->getPosition()); + } + else { + // parse filters, with chaining and ";" as filter end character + if ($this->scan(H2O_RE::$pipe)) { + $result[] = array('filter_end', null); + $result[] = array('filter_start', null); + } + elseif ($this->scan(H2O_RE::$seperator)) + $result[] = array('separator', null); + elseif ($this->scan(H2O_RE::$filter_end)) { + $result[] = array('filter_end', null); + $filtering = false; + } + elseif ($this->scan(H2O_RE::$boolean)) + $result[] = array('boolean', $this->match); + elseif ($this->scan(H2O_RE::$named_args)) + $result[] = array('named_argument', $this->match); + elseif ($this->scan(H2O_RE::$name)) + $result[] = array('name', $this->match); + elseif ($this->scan(H2O_RE::$i18n_string)) + $result[] = array('string', $this->match); + elseif ($this->scan(H2O_RE::$number)) + $result[] = array('number', $this->match); + else + throw new TemplateSyntaxError('unexpected character in filters : "'. $this->source[$this->pos]. '" at '.$this->getPosition()); + } + } + // if we are still in the filter state, we add a filter_end token. + if ($filtering) + $result[] = array('filter_end', null); + return $result; + } + + # String scanner + function scan($regexp) { + if (preg_match($regexp . 'A', $this->source, $match, null, $this->pos)) { + $this->match = $match[0]; + $this->pos += strlen($this->match); + return true; + } + return false; + } + + function eos() { + return $this->pos >= strlen($this->source); + } + + /** + * return the position in the template + */ + function getPosition() { + return $this->fpos + $this->pos; + } +} +?> diff --git a/system/vendor/h2o/tags.php b/system/vendor/h2o/tags.php new file mode 100644 index 0000000..ea2fdbf --- /dev/null +++ b/system/vendor/h2o/tags.php @@ -0,0 +1,483 @@ +nodelist_true = $parser->parse('endifchanged', 'else'); + + if ($parser->token->content === 'else') + $this->nodelist_false = $parser->parse('endifchanged'); + + $this->_varlist = current(H2o_Parser::parseArguments($argstring)); + + if (!$this->_varlist) + throw new TemplateSyntaxError('H2o doesn\'t support lazy ifchanged yet. Please, supply a variable.'); + + } + + function render($context, $stream) { + + if ($this->_varlist) { + $compare_to = $context->resolve($this->_varlist); + } else { + /** + * @todo Rendering method $this->nodelist_true->render() should return a result. + * Further more $compare_to variable should be set to this result. + */ + $compare_to = ''; + } + + if ($compare_to != $this->_last_seen) { + $this->_last_seen = $compare_to; + $this->nodelist_true->render($context, $stream); + } elseif ($this->nodelist_false) { + $this->nodelist_false->render($context, $stream); + } + + } +} + +class If_Tag extends H2o_Node { + private $body; + private $else; + private $negate; + + function __construct($argstring, $parser, $position = 0) { + if (preg_match('/\s(and|or)\s/', $argstring)) + throw new TemplateSyntaxError('H2o doesn\'t support multiple expressions'); + + $this->body = $parser->parse('endif', 'else'); + + if ($parser->token->content === 'else') + $this->else = $parser->parse('endif'); + + $this->args = H2o_Parser::parseArguments($argstring); + + $first = current($this->args); + if (isset($first['operator']) && $first['operator'] === 'not') { + array_shift($this->args); + $this->negate = true; + } + } + + function render($context, $stream) { + if ($this->test($context)) + $this->body->render($context, $stream); + elseif ($this->else) + $this->else->render($context, $stream); + } + + function test($context) { + $test = Evaluator::exec($this->args, $context); + return $this->negate? !$test : $test; + } +} + +class For_Tag extends H2o_Node { + public $position; + private $iteratable, $key, $item, $body, $else, $limit, $reversed; + private $syntax = '{ + ([a-zA-Z][a-zA-Z0-9-_]*)(?:,\s?([a-zA-Z][a-zA-Z0-9-_]*))? + \s+in\s+ + ([a-zA-Z][a-zA-Z0-9-_]*(?:\.[a-zA-Z_0-9][a-zA-Z0-9_-]*)*)\s* # Iteratable name + (?:limit\s*:\s*(\d+))?\s* + (reversed)? # Reverse keyword + }x'; + + function __construct($argstring, $parser, $position) { + if (!preg_match($this->syntax, $argstring, $match)) + throw new TemplateSyntaxError("Invalid for loop syntax "); + + $this->body = $parser->parse('endfor', 'else'); + + if ($parser->token->content === 'else') + $this->else = $parser->parse('endfor'); + + $match = array_pad($match, 6, ''); + list(,$this->key, $this->item, $this->iteratable, $this->limit, $this->reversed) = $match; + + if ($this->limit) + $this->limit = (int) $this->limit; + + # Swap value if no key found + if (!$this->item) { + list($this->key, $this->item) = array($this->item, $this->key); + } + $this->iteratable = symbol($this->iteratable); + $this->reversed = (bool) $this->reversed; + } + + function render($context, $stream) { + $iteratable = $context->resolve($this->iteratable); + + if ($this->reversed) + $iteratable = array_reverse($iteratable); + + if ($this->limit) + $iteratable = array_slice($iteratable, 0, $this->limit); + + $length = count($iteratable); + + if ($length) { + $parent = $context['loop']; + $context->push(); + $rev_count = $is_even = $idx = 0; + foreach($iteratable as $key => $value) { + $is_even = $idx % 2; + $rev_count = $length - $idx; + + if ($this->key) { + $context[$this->key] = $key; + } + $context[$this->item] = $value; + $context['loop'] = array( + 'parent' => $parent, + 'first' => $idx === 0, + 'last' => $rev_count === 1, + 'odd' => !$is_even, + 'even' => $is_even, + 'length' => $length, + 'counter' => $idx + 1, + 'counter0' => $idx, + 'revcounter' => $rev_count, + 'revcounter0' => $rev_count - 1 + ); + $this->body->render($context, $stream); + ++$idx; + } + $context->pop(); + } elseif ($this->else) + $this->else->render($context, $stream); + } +} + +class Block_Tag extends H2o_Node { + public $name; + public $position; + public $stack; + private $syntax = '/^[a-zA-Z_][a-zA-Z0-9_-]*$/'; + + function __construct($argstring, $parser, $position) { + if (!preg_match($this->syntax, $argstring)) + throw new TemplateSyntaxError('Block tag expects a name, example: block [content]'); + + $this->name = $argstring; + + if (isset($parser->storage['blocks'][$this->name])) + throw new TemplateSyntaxError('Block name exists, Please select a different block name'); + + $this->filename = $parser->filename; + $this->stack = array($parser->parse('endblock', "endblock {$this->name}")); + + $parser->storage['blocks'][$this->name] = $this; + $this->position = $position; + } + + function addLayer(&$nodelist) { + $nodelist->parent = $this; + array_push($this->stack, $nodelist); + } + + function render($context, $stream, $index = 1) { + $key = count($this->stack) - $index; + + if (isset($this->stack[$key])) { + $context->push(); + $context['block'] = new BlockContext($this, $context, $index); + $this->stack[$key]->render($context, $stream); + $context->pop(); + } + } +} + +class Extends_Tag extends H2o_Node { + public $filename; + public $position; + public $nodelist; + private $syntax = '/^["\'](.*?)["\']$/'; + + function __construct($argstring, $parser, $position = 0) { + if (!$parser->first) + throw new TemplateSyntaxError('extends must be first in file'); + + if (!preg_match($this->syntax, $argstring)) + throw new TemplatesyntaxError('filename must be quoted'); + + $this->filename = stripcslashes(substr($argstring, 1, -1)); + + # Parse the current template + $parser->parse(); + + # Parse parent template + $this->nodelist = $parser->runtime->loadSubTemplate($this->filename, $parser->options); + $parser->storage['templates'] = array_merge( + $parser->storage['templates'], $this->nodelist->parser->storage['templates'] + ); + $parser->storage['templates'][] = $this->filename; + + if (!isset($this->nodelist->parser->storage['blocks']) || !isset($parser->storage['blocks'])) + return ; + + # Blocks of parent template + $blocks =& $this->nodelist->parser->storage['blocks']; + + # Push child blocks on top of parent blocks + foreach($parser->storage['blocks'] as $name => &$block) { + if (isset($blocks[$name])) { + $blocks[$name]->addLayer($block); + } + } + } + + function render($context, $stream) { + $this->nodelist->render($context, $stream); + } +} + +/** + * include tag + * + * Usage: + * + * Simple inclusion + * {% include "./subtemplate.html" %} + * + * + * Inclusion with additional context variables passing: + * {% include "./subtemplate.html" with foo=bar spam="eggs" %} + * + * Note: Double quotes matter. In this example 'foo' template variable of subtemplate.html + * would be initialized with 'bar' variable contents (from main template context), + * while 'spam' template variable of subtemplate.html would be set to simple string ('eggs'). + * + */ +class Include_Tag extends H2o_Node { + private $nodelist; + private $syntax = '/^["\'](.*?)["\'](\s+with\s+(.+))?$/'; + private $_additional_context = array(); + + function __construct($argstring, $parser, $position = 0) { + if (!preg_match($this->syntax, $argstring, $matches)) { + throw new TemplateSyntaxError(); + } + + $matches_count = count($matches); + + if ($matches_count > 2) { + // "with" statement supplied. + $with_vars = explode(' ', $matches[3]); + foreach ($with_vars as $var_str) { + $eq_pos = strpos($var_str, '='); + $this->_additional_context[substr($var_str, 0, $eq_pos)] = substr($var_str, $eq_pos+1); + } + } + + $this->filename = stripcslashes($matches[1]); + $this->nodelist = $parser->runtime->loadSubTemplate($this->filename, $parser->options); + $parser->storage['templates'] = array_merge( + $this->nodelist->parser->storage['templates'], $parser->storage['templates'] + ); + $parser->storage['templates'][] = $this->filename; + } + + function render($context, $stream) { + foreach ($this->_additional_context as $key => $value) { + if (strpos($value, '"') === false) { + // Context variable supplied as value. Needs to be resolved. + $value = $context->getVariable($value); + } else { + $value = trim($value, '"'); + } + $context[$key] = $value; + } + + $this->nodelist->render($context, $stream); + } +} + +class With_Tag extends H2o_Node { + public $position; + private $variable, $shortcut; + private $nodelist; + private $syntax = '/^([\w]+(:?\.[\w\d]+)*)\s+as\s+([\w]+(:?\.[\w\d]+)?)$/'; + + function __construct($argstring, $parser, $position = 0) { + if (!preg_match($this->syntax, $argstring, $matches)) + throw new TemplateSyntaxError('Invalid with tag syntax'); + + # extract the long name and shortcut + list(,$this->variable, ,$this->shortcut) = $matches; + $this->nodelist = $parser->parse('endwith'); + } + + function render($context, $stream) { + $variable = $context->getVariable($this->variable); + + $context->push(array($this->shortcut => $variable)); + $this->nodelist->render($context, $stream); + $context->pop(); + } +} + +class Cycle_Tag extends H2o_Node { + private $uid; + private $sequence; + + function __construct($argstring, $parser, $pos) { + $args = h2o_parser::parseArguments($argstring); + + if (count($args) < 2) { + throw new Exception('Cycle tag require more than two items'); + } + $this->sequence = $args; + $this->uid = '__cycle__'.$pos; + } + + function render($context, $stream) { + if (!is_null($item = $context->getVariable($this->uid))) { + $item = ($item + 1) % count($this->sequence); + } else { + $item = 0; + } + $stream->write($context->resolve($this->sequence[$item])); + $context->set($this->uid, $item); + } +} + +class Load_Tag extends H2o_Node { + public $position; + private $searchpath = array(H2O_ROOT); + private $extension; + + function __construct($argstring, $parser, $pos = 0) { + $this->extension = stripcslashes(preg_replace("/^[\"'](.*)[\"']$/", "$1", $argstring)); + + if ($parser->runtime->searchpath) + $this->appendPath($parser->runtime->searchpath); + + $parser->storage['included'][$this->extension] = $file = $this->load(); + $this->position = $pos; + } + + function render($context, $stream) { + $this->load(); + } + + function appendPath($path) { + $this->searchpath[] = $path; + } + + private function load() { + if (isset(h2o::$extensions[$this->extension])) { + return true; + } + foreach($this->searchpath as $path) { + $file = $path.'ext'.DS.$this->extension.'.php'; + if (is_file($file)) { + h2o::load($this->extension, $file); + return $file; + } + } + throw new H2o_Error( + "Extension: {$this->extension} cannot be loaded, please confirm it exist in extension path" + ); + } +} + +class Debug_Tag extends H2o_Node { + private $argument; + function __construct($argstring, $parser, $pos = 0) { + $this->argument = $argstring; + } + + function render($context, $stream) { + if ($this->argument) { + $object = $context->resolve(symbol($this->argument)); + } else { + $object = $context->scopes[0]; + } + $output = "
" . htmlspecialchars( print_r($object, true) ) . "
"; + $stream->write($output); + } +} + +class Comment_Tag extends H2o_Node { + function __construct($argstring, $parser, $position) { + $parser->parse('endcomment'); + } + + function render($context, $stream, $index = 1) { + } +} + +class Now_Tag extends H2o_Node { + function __construct($argstring, $parser, $pos=0) { + $this->format = $argstring; + if (!$this->format) { + $this->format = "D M j G:i:s T Y"; + } + } + + function render($contxt, $stream) { + $time = date($this->format); + $stream->write($time); + } +} + +class Autoescape_Tag extends H2o_Node { + protected $enable; + + function __construct($argstring, $parser, $pos = 0) { + if ($argstring === 'on') + $this->enable = true; + elseif ($argstring === 'off') + $this->enable = false; + else throw new H2o_Error( + "Invalid syntax : autoescape on|off " + ); + } + + function render($context, $stream) { + $context->autoescape = $this->enable; + } +} + +class Csrf_token_Tag extends H2o_Node { + function render($context, $stream) { + $token = ""; + if (isset($_COOKIE["csrftoken"])) + $token = $_COOKIE["csrftoken"]; + else { + global $SECRET_KEY; + if (defined('SECRET_KEY')) + $token = md5(mt_rand() . SECRET_KEY); + else + $token = md5(mt_rand()); + } + setcookie("csrftoken", $token, time()+60*60*24*365, "/"); + $stream->write("
"); + } +} + +H2o::addTag(array('block', 'extends', 'include', 'if', 'ifchanged', 'for', 'with', 'cycle', 'load', 'debug', 'comment', 'now', 'autoescape', 'csrf_token'));