/** Retrieve value of framework variable and apply locale rules @return mixed @param $key string @param $args mixed @public **/ static function get($key, $args = NULL) { if (preg_match('/{{.+}}/', $key)) { // Variable variable $key = self::resolve($key); } if (!self::valid($key)) { return self::$null; } $val = self::ref($key, FALSE); if (is_string($val)) { return class_exists('ICU', FALSE) && $args ? ICU::format($val, $args) : $val; } elseif (is_null($val)) { // Attempt to retrieve from cache $hash = 'var.' . self::hash(self::remix($key)); if (Cache::cached($hash)) { $val = Cache::get($hash); } } return $val; }
function template() { $this->set('title', 'Template Engine'); $this->set('CACHE', TRUE); $this->expect(is_null($this->get('ERROR')), 'No errors expected at this point', 'ERROR variable is set: ' . $this->get('ERROR.text')); $this->set('a', 123); $this->set('b', '{{@a}}'); $this->expect(Template::resolve('{{@b}}') == '123', 'Template token substituted; string value returned', 'Token substition failed: ' . Template::resolve('{{@b}}')); $this->expect(Template::resolve('{{@a}}') == '123', 'Template engine confirms substitution', 'Template engine failed: ' . Template::resolve('{{@a}}')); $this->set('a', 345); $this->expect(Template::resolve('{{@a}}') == '345', 'Template engine confirms replacement', 'Template engine failed: ' . Template::resolve('{{@a}}')); $this->expect(Template::resolve('{{@a+1}}') == '346', 'Mixed expression correct', 'Mixed expression failed: ' . Template::resolve('{{@a+1}}')); $this->expect(Template::resolve('{{@a + 1}}') == '346', 'Mixed expression (with whitespaces) correct', 'Mixed expression (with whitespaces) failed: ' . Template::resolve('{{@a + 1}}')); $this->set('x', '{{123}}'); $this->expect(Template::resolve('{{@x}}') == '123', 'Integer constant in template expression correct', 'Template expression is wrong: ' . Template::resolve('{{@x}}')); $this->set('i', 'hello'); $this->set('j', 'there'); $this->expect(Template::resolve('{{@i.@j}}') == 'hellothere', 'String concatenation works', 'String concatenation problem: ' . Template::resolve('{{@i.@j}}')); $this->expect(Template::resolve('{{@i. @j}}') == 'hellothere', 'String concatenation (with whitespaces) works', 'String concatenation (with whitespaces) problem: ' . Template::resolve('{{@i. @j}}')); $this->expect(Template::resolve('{{@i .@j}}') == 'hellothere', 'Variation in string concatenation (with whitespaces) works', 'Variation in string concatenation (with whitespaces) problem: ' . Template::resolve('{{@i .@j}}')); $this->expect(Template::resolve('{{ @i . @j }}') == 'hellothere', 'Liberal amounts of whitespaces produces the correct result', 'Liberal amounts of whitespaces produces strange result: ' . Template::resolve('{{ @i . @j }}')); $this->set('x', '{{345+5}}'); $this->expect(Template::resolve('{{@x}}') == '350', 'Arithmetic expression in template expression evaluated', 'Arithmetic expression is wrong: ' . Template::resolve('{{@x}}')); $this->set('x', '{{1+0.23e-4}}'); $this->expect(Template::resolve('{{@x}}') == '1.000023', 'Negative exponential float in template expression correct', 'Negative exponential float is wrong: ' . Template::resolve('{{@x}}')); $this->set('x', '{{1+0.23e+4}}'); $this->expect(Template::resolve('{{@x}}') == '2301', 'Positive exponential float in template expression correct', 'Positive exponential float is wrong: ' . Template::resolve('{{@x}}')); $this->set('x', '{{1+0.23e4}}'); $this->expect(Template::resolve('{{@x}}') == '2301', 'Unsigned exponential float in template expression correct', 'Unsigned exponential float is wrong: ' . Template::resolve('{{@x}}')); $this->set('x', '{{456+7.5}}'); $this->expect(Template::resolve('{{@x}}') == '463.5', 'Integer + float in template expression correct', 'Integer + float is wrong: ' . Template::resolve('{{@x}}')); $this->set('x', '{{(1+2)*3}}'); $this->expect(Template::resolve('{{@x}}') == '9', 'Parenthesized arithmetic expression evaluated', 'Parenthesized expression is wrong: ' . Template::resolve('{{@x}}')); $this->expect(Template::resolve('{{(@a+1)*2}}') == '692', 'Variable + arithmetic expression evaluated', 'Variable + arithmetic expression is wrong: ' . Template::resolve('{{(@a+1)*2}}')); $this->set('x', '{{(intval(1+2.25))*3}}'); $this->expect(Template::resolve('{{@x}}') == '9', 'Allowed function and nested parentheses evaluated', 'Allowed function/parentheses failed: ' . Template::resolve('{{@x}}')); $this->set('x', '{{(round(234.567,1)+(-1)+1)*2}}'); $this->expect(Template::resolve('{{@x}}') == '469.2', 'Function with multiple arguments evaluated', 'Function with multiple arguments failed: ' . Template::resolve('{{@x}}')); $this->set('x', NULL); $this->expect(Template::resolve('{{@x}}') == '', 'NULL converted to empty string', 'NULL not converted to empty string: ' . Template::resolve('{{@x}}')); $this->set('x', '{{array()}}'); $this->expect(Template::resolve('{{@x}}') == '', 'Empty array converted to empty string', 'Array conversion failed: ' . Template::resolve('{{@x}}')); $this->set('x', '{{array(1,2,3)}}'); $this->expect(Template::resolve('{{@x}}') == 'Array', 'Array converted to string \'Array\'', 'Array conversion failed: ' . Template::resolve('{{@x}}')); $this->set('x', '{{NULL}}'); $this->expect(Template::resolve('{{@x}}') == '', 'NULL value evaluated', 'Incorrect NULL evaluation: ' . Template::resolve('{{@x}}')); $this->set('x', '{{null}}'); $this->expect(Template::resolve('{{@x}}') == '', 'NULL value evaluated (case-insensitive)', 'Incorrect NULL evaluation: ' . Template::resolve('{{@x}}')); $this->set('x', '{{TRUE}}'); $this->expect(Template::resolve('{{@x}}') == '1', 'Boolean TRUE expression evaluated', 'Incorrect boolean evaluation: ' . Template::resolve('{{@x}}')); $this->set('x', '{{FALSE}}'); $this->expect(Template::resolve('{{@x}}') == '', 'Boolean FALSE expression converted to empty string', 'Incorrect boolean evaluation: ' . Template::resolve('{{@x}}')); $this->set('x', '{{0}}'); $this->expect(Template::resolve('{{@x}}') == '0', 'Zero remains as-is', 'Incorrect evaluation of integer zero: ' . Template::resolve('{{@x}}')); $this->set('x', '{{a@b.com}}'); $this->expect(Template::resolve('{{@x}}') == '\'a@b.com\'', 'E-mail address preserved', 'Incorrect interpretation of e-mail address: ' . Template::resolve('{{@x}}')); $this->set('x', '{{new CustomObj}}'); $this->expect(Template::resolve('{{@x}}') == '\'new CustomObj\'', 'Object instantiation using template engine prohibited', 'Object instantiation issue: ' . Template::resolve('{{@x}}')); $this->set('func', function ($x) { return 123; }); $this->expect(Template::resolve('{{@func("hello")}}') == 123, 'Variable containing anonymous function interpreted correctly', 'Template misunderstood variable containing anonymous function: ' . Template::resolve('{{@func("hello")}}')); $z = new stdClass(); $z->a = 123; $z->b = 345; $this->set('var', $z); $this->expect(Template::resolve('{{@var->a}}') == 123 && Template::resolve('{{@var->b}}') == 345, 'Variable containing an object interpreted correctly', 'Template misunderstood variable containing an object/properties: ' . Template::resolve('{{@var->a}}')); $z->c = function () { return 'foo'; }; $this->expect(Template::resolve('{{@var->c()}}') == 'foo', 'Variable containing an anonymous function rendered properly', 'Variable containing an anonymous function evaluated wrong: ' . Template::resolve('{{@var->c()}}')); $this->set('z.x', 'good idea'); $this->expect(Template::resolve('{{@z.x}}') == 'good idea', 'Array element evaluated', 'Array element failed: ' . Template::resolve('{{@z.x}}')); $this->expect(Template::resolve('{{@z.y}}') == '', 'Non-existent array element converted to empty string', 'Non-existent element failed: ' . Template::resolve('{{@z.y}}')); $this->set('q', ' indeed'); $this->expect(Template::resolve('{{@z.@q}}') == 'Array indeed', 'Concatenation of array and string produces expected result', 'Illegal concatenation: ' . Template::resolve('{{@z.@q}}')); $this->expect(Template::resolve('{{@z.x.@q}}') == 'good idea indeed', 'Concatenation of array element and string correct', 'Incorrect concatenation: ' . Template::resolve('{{@z.x.@q}}')); $this->set('my_plans', array('test' => 1, 'plan' => array('city_name' => 2))); $this->expect(Template::resolve('{{@my_plans[plan][city_name]}}') == 2, 'Got the right value of a deeply-nested array element', 'Incorrect evaluation of a deeply-nested array element'); $out = Template::serve('template/layout.htm'); $this->expect($out === "", 'Subtemplate not defined - none inserted', 'Subtemplate insertion issue: ' . $out); $this->set('sub', 'sub1.htm'); $this->set('test', '<i>italics</i>'); $out = Template::serve('template/layout.htm'); $this->expect($out == "<i>italics</i>", 'HTML special characters retained', 'Problem with HTML insertion: ' . $out); $this->set('sub', 'sub1.htm'); $this->set('test', '©'); $out = Template::serve('template/layout.htm'); $this->expect($out == "©", 'HTML entity inserted: ' . Template::serve('template/layout.htm'), 'Problem with HTML insertion: ' . $out); $this->set('sub', 'sub1.htm'); $this->set('test', 'אני יכול לאכול זכוכית וזה לא מזיק לי.'); $out = Template::serve('template/layout.htm'); $this->expect(Template::serve('template/layout.htm') == "אני יכול לאכול זכוכית וזה לא מזיק לי.", 'UTF-8 character set rendered correctly: ' . $out, 'UTF-8 issue: ' . $out); $this->set('sub', 'sub1.htm'); $this->set('test', 'I am here.'); $out = Template::serve('template/layout.htm'); $this->expect(Template::serve('template/layout.htm') == "I am here.", 'HTML entities preserved: ' . $out, 'HTML entities converted: ' . $out); $this->set('sub', 'sub2.htm'); $this->set('src', '/test/image'); $this->set('alt', htmlspecialchars('this is "the" life')); $out = Template::serve('template/layout.htm'); $this->expect($out == '<img src="/test/image" alt="this is "the" life"/>', 'Double-quotes inside HTML attributes converted to XML entities', 'Problem with double-quotes inside HTML attributes: ' . $out); $this->set('sub', 'sub3.htm'); $out = Template::serve('template/layout.htm'); $this->clear('div'); $this->expect($out == '', 'Undefined array renders empty output', 'Output not empty: ' . $out); $this->set('sub', 'sub3.htm'); $out = Template::serve('template/layout.htm'); $this->set('div', NULL); $this->expect($out == '', 'NULL used as group attribute renders empty output', 'Output not empty: ' . $out); $this->set('sub', 'sub3.htm'); $out = Template::serve('template/layout.htm'); $this->set('div', array()); $this->expect($out == '', 'Empty array used as group attribute renders empty output', 'Output not empty: ' . $out); $this->set('sub', 'sub3.htm'); $this->set('div', array('coffee' => array('arabica', 'barako', 'liberica', 'kopiluwak'), 'tea' => array('darjeeling', 'pekoe', 'samovar'))); $out = Template::serve('template/layout.htm'); $this->expect(preg_match('#' . '<div>\\s+' . '<p><span><b>coffee</b></span></p>\\s+' . '<p>\\s+' . '<span>arabica</span>\\s+' . '<span>barako</span>\\s+' . '<span>liberica</span>\\s+' . '<span>kopiluwak</span>\\s+' . '</p>\\s+' . '</div>\\s+' . '<div>\\s+' . '<p><span><b>tea</b></span></p>\\s+' . '<p>\\s+' . '<span>darjeeling</span>\\s+' . '<span>pekoe</span>\\s+' . '<span>samovar</span>\\s+' . '</p>\\s+' . '</div>' . '#s', $out), 'Subtemplate inserted; nested repeat directives rendered correctly', 'Template rendering issue: ' . $out); $this->set('sub', 'sub4.htm'); $this->set('group', array('world', 'me', 'others')); $out = Template::serve('template/layout.htm'); $this->expect(preg_match('#' . '<script type="text/javascript">\\s*' . 'function hello\\(\\) {\\s*' . 'alert\\(\'Javascript works\'\\);\\s*' . '}\\s*' . '</script>\\s*' . '<script type="text/javascript">alert\\(unescape\\("%3Cscript src=\'" \\+ gaJsHost \\+ "google-analytics\\.com/ga\\.js\' type=\'text/javascript\'%3E%3C/script%3E"\\)\\);</script>\\s' . 'world,\\s+me,\\s+others,\\s+' . '#s', $out), 'Javascript preserved', 'Javascript mangled: ' . htmlentities($out)); $this->set('sub', 'sub5.htm'); $this->set('cond1', FALSE); $this->set('cond3', FALSE); $out = trim(Template::serve('template/layout.htm')); $this->expect($out == 'c1:F,c3:F', 'Conditional directives evaluated correctly: FALSE, FALSE', 'Incorrect evaluation of conditional directives: ' . $out); $this->set('cond1', FALSE); $this->set('cond3', TRUE); $out = trim(Template::serve('template/layout.htm')); $this->expect($out == 'c1:F,c3:T', 'Conditional directives evaluated correctly: FALSE, TRUE', 'Incorrect evaluation of conditional directives: ' . $out); $this->set('cond1', TRUE); $this->set('cond2', FALSE); $out = trim(Template::serve('template/layout.htm')); $this->expect($out == 'c1:T,c2:F', 'Conditional directives evaluated correctly: TRUE, FALSE', 'Incorrect evaluation of conditional directives: ' . $out); $this->set('cond1', TRUE); $this->set('cond2', TRUE); $out = trim(Template::serve('template/layout.htm')); $this->expect($out == 'c1:T,c2:T', 'Conditional directives evaluated correctly: TRUE, TRUE', 'Incorrect evaluation of conditional directives: ' . $out); $this->set('pi_val', $pi = 3.141592654); $money = 63950.25; $this->set('sub', 'sub6.htm'); $this->set('LANGUAGE', 'en'); $out = trim(Template::serve('template/layout.htm')); // PHP 5.3.2 inserts a line feed at end of translation $this->expect($out == "<h3>I love Fat-Free!</h3>\n" . "<p>Today is " . ICU::format('{0,date}', array(time())) . "</p>\n" . "<p>The quick brown fox jumps over the lazy dog.</p>\n" . "<p>" . ICU::format('{0,number}', array($pi)) . "</p>\n" . "<p>" . ICU::format('{0,number,currency}', array($money)) . "</p>", 'English locale (i18n)', 'English locale mangled: ' . $out); $this->set('sub', 'sub6.htm'); $this->set('LANGUAGE', 'fr-FR'); $out = trim(Template::serve('template/layout.htm')); // PHP 5.3.2 inserts a line feed at end of translation $this->expect($out == "<h3>J'aime Fat-Free!</h3>\n" . "<p>Aujourd'hui, c'est " . ICU::format('{0,date}', array(time())) . "</p>\n" . "<p>Les naïfs ægithales hâtifs pondant à Noël où il gèle sont sûrs d'être déçus et de voir leurs drôles d'œufs abîmés.</p>\n" . "<p>" . ICU::format('{0,number}', array($pi)) . "</p>\n" . "<p>" . ICU::format('{0,number,currency}', array($money)) . "</p>", 'Translated properly to French', 'French translation mangled: ' . $out); $this->set('sub', 'sub6.htm'); $this->set('LANGUAGE', 'es-AR'); $out = trim(Template::serve('template/layout.htm')); // PHP 5.3.2 inserts a line feed at end of translation $this->expect($out == "<h3>Me encanta Fat-Free!</h3>\n" . "<p>Hoy es " . ICU::format('{0,date}', array(time())) . "</p>\n" . "<p>El pingüino Wenceslao hizo kilómetros bajo exhaustiva lluvia y frío, añoraba a su querido cachorro.</p>\n" . "<p>" . ICU::format('{0,number}', array($pi)) . "</p>\n" . "<p>" . ICU::format('{0,number,currency}', array($money)) . "</p>", 'Translated properly to Spanish', 'Spanish translation mangled: ' . $out); $this->set('sub', 'sub6.htm'); $this->set('LANGUAGE', 'de-DE'); $out = trim(Template::serve('template/layout.htm')); // PHP 5.3.2 inserts a line feed at end of translation $this->expect($out == "<h3>Ich liebe Fat-Free!</h3>\n" . "<p>Heute ist " . ICU::format('{0,date}', array(time())) . "</p>\n" . "<p>Im finsteren Jagdschloß am offenen Felsquellwasser patzte der affig-flatterhafte kauzig-höfliche Bäcker über seinem versifften kniffligen Xylophon.</p>\n" . "<p>" . ICU::format('{0,number}', array($pi)) . "</p>\n" . "<p>" . ICU::format('{0,number,currency}', array($money)) . "</p>", 'Translated properly to German', 'German translation mangled: ' . $out); $this->set('LANGUAGE', 'en'); $this->set('sub', 'sub7.htm'); $this->set('array', array('a' => 'apple', 'b' => 'blueberry', 'c' => 'cherry')); $this->set('element', 'b'); $out = trim(Template::serve('template/layout.htm')); $this->expect($out == 'blueberry', 'Array with variable element rendered correctly', 'Array variable element failed to render: ' . var_export($out, TRUE)); $this->set('sub', 'sub8.htm'); $this->set('func', function ($arg1, $arg2) { return 'hello, ' . $arg1 . ' ' . $arg2; }); $this->set('arg1', 'wise'); $this->set('arg2', 'guy'); $out = trim(Template::serve('template/layout.htm')); $this->expect($out == 'hello, wise guy', 'Function with variable arguments rendered correctly', 'Array with variable arguments failed to render: ' . var_export($out, TRUE)); $this->set('benchmark', array_fill(1, 100, array('a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7, 'h' => 8, 'i' => 9, 'j' => 10))); $time = microtime(TRUE); Template::serve('template/benchmark.htm'); $elapsed = round(microtime(TRUE) - $time, 3); $this->expect($elapsed < 0.05, 'Template containing ' . count($this->get('benchmark')) * 10 . '+ HTML elements/calculations rendered in ' . $elapsed . ' seconds', 'Template rendering too slow on this server: ' . $elapsed . ' seconds'); echo $this->render('basic/results.htm'); }