PageRenderTime 85ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/index.php

https://bitbucket.org/scottywz/portal
PHP | 2412 lines | 1659 code | 228 blank | 525 comment | 387 complexity | fe9b94ef26a93542010e351d22aa7e50 MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. /**
  3. * Spyc -- A Simple PHP YAML Class
  4. * @version 0.5
  5. * @author Vlad Andersen <vlad.andersen@gmail.com>
  6. * @author Chris Wanstrath <chris@ozmm.org>
  7. * @link http://code.google.com/p/spyc/
  8. * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2011 Vlad Andersen
  9. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  10. * @package Spyc
  11. */
  12. if (!function_exists('spyc_load')) {
  13. /**
  14. * Parses YAML to array.
  15. * @param string $string YAML string.
  16. * @return array
  17. */
  18. function spyc_load ($string) {
  19. return Spyc::YAMLLoadString($string);
  20. }
  21. }
  22. if (!function_exists('spyc_load_file')) {
  23. /**
  24. * Parses YAML to array.
  25. * @param string $file Path to YAML file.
  26. * @return array
  27. */
  28. function spyc_load_file ($file) {
  29. return Spyc::YAMLLoad($file);
  30. }
  31. }
  32. /**
  33. * The Simple PHP YAML Class.
  34. *
  35. * This class can be used to read a YAML file and convert its contents
  36. * into a PHP array. It currently supports a very limited subsection of
  37. * the YAML spec.
  38. *
  39. * Usage:
  40. * <code>
  41. * $Spyc = new Spyc;
  42. * $array = $Spyc->load($file);
  43. * </code>
  44. * or:
  45. * <code>
  46. * $array = Spyc::YAMLLoad($file);
  47. * </code>
  48. * or:
  49. * <code>
  50. * $array = spyc_load_file($file);
  51. * </code>
  52. * @package Spyc
  53. */
  54. class Spyc {
  55. // SETTINGS
  56. const REMPTY = "\0\0\0\0\0";
  57. /**
  58. * Setting this to true will force YAMLDump to enclose any string value in
  59. * quotes. False by default.
  60. *
  61. * @var bool
  62. */
  63. public $setting_dump_force_quotes = false;
  64. /**
  65. * Setting this to true will forse YAMLLoad to use syck_load function when
  66. * possible. False by default.
  67. * @var bool
  68. */
  69. public $setting_use_syck_is_possible = false;
  70. /**#@+
  71. * @access private
  72. * @var mixed
  73. */
  74. private $_dumpIndent;
  75. private $_dumpWordWrap;
  76. private $_containsGroupAnchor = false;
  77. private $_containsGroupAlias = false;
  78. private $path;
  79. private $result;
  80. private $LiteralPlaceHolder = '___YAML_Literal_Block___';
  81. private $SavedGroups = array();
  82. private $indent;
  83. /**
  84. * Path modifier that should be applied after adding current element.
  85. * @var array
  86. */
  87. private $delayedPath = array();
  88. /**#@+
  89. * @access public
  90. * @var mixed
  91. */
  92. public $_nodeId;
  93. /**
  94. * Load a valid YAML string to Spyc.
  95. * @param string $input
  96. * @return array
  97. */
  98. public function load ($input) {
  99. return $this->__loadString($input);
  100. }
  101. /**
  102. * Load a valid YAML file to Spyc.
  103. * @param string $file
  104. * @return array
  105. */
  106. public function loadFile ($file) {
  107. return $this->__load($file);
  108. }
  109. /**
  110. * Load YAML into a PHP array statically
  111. *
  112. * The load method, when supplied with a YAML stream (string or file),
  113. * will do its best to convert YAML in a file into a PHP array. Pretty
  114. * simple.
  115. * Usage:
  116. * <code>
  117. * $array = Spyc::YAMLLoad('lucky.yaml');
  118. * print_r($array);
  119. * </code>
  120. * @access public
  121. * @return array
  122. * @param string $input Path of YAML file or string containing YAML
  123. */
  124. public static function YAMLLoad($input) {
  125. $Spyc = new Spyc;
  126. return $Spyc->__load($input);
  127. }
  128. /**
  129. * Load a string of YAML into a PHP array statically
  130. *
  131. * The load method, when supplied with a YAML string, will do its best
  132. * to convert YAML in a string into a PHP array. Pretty simple.
  133. *
  134. * Note: use this function if you don't want files from the file system
  135. * loaded and processed as YAML. This is of interest to people concerned
  136. * about security whose input is from a string.
  137. *
  138. * Usage:
  139. * <code>
  140. * $array = Spyc::YAMLLoadString("---\n0: hello world\n");
  141. * print_r($array);
  142. * </code>
  143. * @access public
  144. * @return array
  145. * @param string $input String containing YAML
  146. */
  147. public static function YAMLLoadString($input) {
  148. $Spyc = new Spyc;
  149. return $Spyc->__loadString($input);
  150. }
  151. /**
  152. * Dump YAML from PHP array statically
  153. *
  154. * The dump method, when supplied with an array, will do its best
  155. * to convert the array into friendly YAML. Pretty simple. Feel free to
  156. * save the returned string as nothing.yaml and pass it around.
  157. *
  158. * Oh, and you can decide how big the indent is and what the wordwrap
  159. * for folding is. Pretty cool -- just pass in 'false' for either if
  160. * you want to use the default.
  161. *
  162. * Indent's default is 2 spaces, wordwrap's default is 40 characters. And
  163. * you can turn off wordwrap by passing in 0.
  164. *
  165. * @access public
  166. * @return string
  167. * @param array $array PHP array
  168. * @param int $indent Pass in false to use the default, which is 2
  169. * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
  170. */
  171. public static function YAMLDump($array,$indent = false,$wordwrap = false) {
  172. $spyc = new Spyc;
  173. return $spyc->dump($array,$indent,$wordwrap);
  174. }
  175. /**
  176. * Dump PHP array to YAML
  177. *
  178. * The dump method, when supplied with an array, will do its best
  179. * to convert the array into friendly YAML. Pretty simple. Feel free to
  180. * save the returned string as tasteful.yaml and pass it around.
  181. *
  182. * Oh, and you can decide how big the indent is and what the wordwrap
  183. * for folding is. Pretty cool -- just pass in 'false' for either if
  184. * you want to use the default.
  185. *
  186. * Indent's default is 2 spaces, wordwrap's default is 40 characters. And
  187. * you can turn off wordwrap by passing in 0.
  188. *
  189. * @access public
  190. * @return string
  191. * @param array $array PHP array
  192. * @param int $indent Pass in false to use the default, which is 2
  193. * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
  194. */
  195. public function dump($array,$indent = false,$wordwrap = false) {
  196. // Dumps to some very clean YAML. We'll have to add some more features
  197. // and options soon. And better support for folding.
  198. // New features and options.
  199. if ($indent === false or !is_numeric($indent)) {
  200. $this->_dumpIndent = 2;
  201. } else {
  202. $this->_dumpIndent = $indent;
  203. }
  204. if ($wordwrap === false or !is_numeric($wordwrap)) {
  205. $this->_dumpWordWrap = 40;
  206. } else {
  207. $this->_dumpWordWrap = $wordwrap;
  208. }
  209. // New YAML document
  210. $string = "---\n";
  211. // Start at the base of the array and move through it.
  212. if ($array) {
  213. $array = (array)$array;
  214. $previous_key = -1;
  215. foreach ($array as $key => $value) {
  216. if (!isset($first_key)) $first_key = $key;
  217. $string .= $this->_yamlize($key,$value,0,$previous_key, $first_key, $array);
  218. $previous_key = $key;
  219. }
  220. }
  221. return $string;
  222. }
  223. /**
  224. * Attempts to convert a key / value array item to YAML
  225. * @access private
  226. * @return string
  227. * @param $key The name of the key
  228. * @param $value The value of the item
  229. * @param $indent The indent of the current node
  230. */
  231. private function _yamlize($key,$value,$indent, $previous_key = -1, $first_key = 0, $source_array = null) {
  232. if (is_array($value)) {
  233. if (empty ($value))
  234. return $this->_dumpNode($key, array(), $indent, $previous_key, $first_key, $source_array);
  235. // It has children. What to do?
  236. // Make it the right kind of item
  237. $string = $this->_dumpNode($key, self::REMPTY, $indent, $previous_key, $first_key, $source_array);
  238. // Add the indent
  239. $indent += $this->_dumpIndent;
  240. // Yamlize the array
  241. $string .= $this->_yamlizeArray($value,$indent);
  242. } elseif (!is_array($value)) {
  243. // It doesn't have children. Yip.
  244. $string = $this->_dumpNode($key, $value, $indent, $previous_key, $first_key, $source_array);
  245. }
  246. return $string;
  247. }
  248. /**
  249. * Attempts to convert an array to YAML
  250. * @access private
  251. * @return string
  252. * @param $array The array you want to convert
  253. * @param $indent The indent of the current level
  254. */
  255. private function _yamlizeArray($array,$indent) {
  256. if (is_array($array)) {
  257. $string = '';
  258. $previous_key = -1;
  259. foreach ($array as $key => $value) {
  260. if (!isset($first_key)) $first_key = $key;
  261. $string .= $this->_yamlize($key, $value, $indent, $previous_key, $first_key, $array);
  262. $previous_key = $key;
  263. }
  264. return $string;
  265. } else {
  266. return false;
  267. }
  268. }
  269. /**
  270. * Returns YAML from a key and a value
  271. * @access private
  272. * @return string
  273. * @param $key The name of the key
  274. * @param $value The value of the item
  275. * @param $indent The indent of the current node
  276. */
  277. private function _dumpNode($key, $value, $indent, $previous_key = -1, $first_key = 0, $source_array = null) {
  278. // do some folding here, for blocks
  279. if (is_string ($value) && ((strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false ||
  280. strpos($value,"*") !== false || strpos($value,"#") !== false || strpos($value,"<") !== false || strpos($value,">") !== false || strpos ($value, ' ') !== false ||
  281. strpos($value,"[") !== false || strpos($value,"]") !== false || strpos($value,"{") !== false || strpos($value,"}") !== false) || strpos($value,"&") !== false || strpos($value, "'") !== false || strpos($value, "!") === 0 ||
  282. substr ($value, -1, 1) == ':')
  283. ) {
  284. $value = $this->_doLiteralBlock($value,$indent);
  285. } else {
  286. $value = $this->_doFolding($value,$indent);
  287. }
  288. if ($value === array()) $value = '[ ]';
  289. if (in_array ($value, array ('true', 'TRUE', 'false', 'FALSE', 'y', 'Y', 'n', 'N', 'null', 'NULL'), true)) {
  290. $value = $this->_doLiteralBlock($value,$indent);
  291. }
  292. if (trim ($value) != $value)
  293. $value = $this->_doLiteralBlock($value,$indent);
  294. if (is_bool($value)) {
  295. $value = ($value) ? "true" : "false";
  296. }
  297. if ($value === null) $value = 'null';
  298. if ($value === "'" . self::REMPTY . "'") $value = null;
  299. $spaces = str_repeat(' ',$indent);
  300. //if (is_int($key) && $key - 1 == $previous_key && $first_key===0) {
  301. if (is_array ($source_array) && array_keys($source_array) === range(0, count($source_array) - 1)) {
  302. // It's a sequence
  303. $string = $spaces.'- '.$value."\n";
  304. } else {
  305. // if ($first_key===0) throw new Exception('Keys are all screwy. The first one was zero, now it\'s "'. $key .'"');
  306. // It's mapped
  307. if (strpos($key, ":") !== false || strpos($key, "#") !== false) { $key = '"' . $key . '"'; }
  308. $string = rtrim ($spaces.$key.': '.$value)."\n";
  309. }
  310. return $string;
  311. }
  312. /**
  313. * Creates a literal block for dumping
  314. * @access private
  315. * @return string
  316. * @param $value
  317. * @param $indent int The value of the indent
  318. */
  319. private function _doLiteralBlock($value,$indent) {
  320. if ($value === "\n") return '\n';
  321. if (strpos($value, "\n") === false && strpos($value, "'") === false) {
  322. return sprintf ("'%s'", $value);
  323. }
  324. if (strpos($value, "\n") === false && strpos($value, '"') === false) {
  325. return sprintf ('"%s"', $value);
  326. }
  327. $exploded = explode("\n",$value);
  328. $newValue = '|';
  329. $indent += $this->_dumpIndent;
  330. $spaces = str_repeat(' ',$indent);
  331. foreach ($exploded as $line) {
  332. $newValue .= "\n" . $spaces . ($line);
  333. }
  334. return $newValue;
  335. }
  336. /**
  337. * Folds a string of text, if necessary
  338. * @access private
  339. * @return string
  340. * @param $value The string you wish to fold
  341. */
  342. private function _doFolding($value,$indent) {
  343. // Don't do anything if wordwrap is set to 0
  344. if ($this->_dumpWordWrap !== 0 && is_string ($value) && strlen($value) > $this->_dumpWordWrap) {
  345. $indent += $this->_dumpIndent;
  346. $indent = str_repeat(' ',$indent);
  347. $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent");
  348. $value = ">\n".$indent.$wrapped;
  349. } else {
  350. if ($this->setting_dump_force_quotes && is_string ($value) && $value !== self::REMPTY)
  351. $value = '"' . $value . '"';
  352. }
  353. return $value;
  354. }
  355. // LOADING FUNCTIONS
  356. private function __load($input) {
  357. $Source = $this->loadFromSource($input);
  358. return $this->loadWithSource($Source);
  359. }
  360. private function __loadString($input) {
  361. $Source = $this->loadFromString($input);
  362. return $this->loadWithSource($Source);
  363. }
  364. private function loadWithSource($Source) {
  365. if (empty ($Source)) return array();
  366. if ($this->setting_use_syck_is_possible && function_exists ('syck_load')) {
  367. $array = syck_load (implode ('', $Source));
  368. return is_array($array) ? $array : array();
  369. }
  370. $this->path = array();
  371. $this->result = array();
  372. $cnt = count($Source);
  373. for ($i = 0; $i < $cnt; $i++) {
  374. $line = $Source[$i];
  375. $this->indent = strlen($line) - strlen(ltrim($line));
  376. $tempPath = $this->getParentPathByIndent($this->indent);
  377. $line = self::stripIndent($line, $this->indent);
  378. if (self::isComment($line)) continue;
  379. if (self::isEmpty($line)) continue;
  380. $this->path = $tempPath;
  381. $literalBlockStyle = self::startsLiteralBlock($line);
  382. if ($literalBlockStyle) {
  383. $line = rtrim ($line, $literalBlockStyle . " \n");
  384. $literalBlock = '';
  385. $line .= $this->LiteralPlaceHolder;
  386. $literal_block_indent = strlen($Source[$i+1]) - strlen(ltrim($Source[$i+1]));
  387. while (++$i < $cnt && $this->literalBlockContinues($Source[$i], $this->indent)) {
  388. $literalBlock = $this->addLiteralLine($literalBlock, $Source[$i], $literalBlockStyle, $literal_block_indent);
  389. }
  390. $i--;
  391. }
  392. while (++$i < $cnt && self::greedilyNeedNextLine($line)) {
  393. $line = rtrim ($line, " \n\t\r") . ' ' . ltrim ($Source[$i], " \t");
  394. }
  395. $i--;
  396. if (strpos ($line, '#')) {
  397. if (strpos ($line, '"') === false && strpos ($line, "'") === false)
  398. $line = preg_replace('/\s+#(.+)$/','',$line);
  399. }
  400. $lineArray = $this->_parseLine($line);
  401. if ($literalBlockStyle)
  402. $lineArray = $this->revertLiteralPlaceHolder ($lineArray, $literalBlock);
  403. $this->addArray($lineArray, $this->indent);
  404. foreach ($this->delayedPath as $indent => $delayedPath)
  405. $this->path[$indent] = $delayedPath;
  406. $this->delayedPath = array();
  407. }
  408. return $this->result;
  409. }
  410. private function loadFromSource ($input) {
  411. if (!empty($input) && strpos($input, "\n") === false && file_exists($input))
  412. return file($input);
  413. return $this->loadFromString($input);
  414. }
  415. private function loadFromString ($input) {
  416. $lines = explode("\n",$input);
  417. foreach ($lines as $k => $_) {
  418. $lines[$k] = rtrim ($_, "\r");
  419. }
  420. return $lines;
  421. }
  422. /**
  423. * Parses YAML code and returns an array for a node
  424. * @access private
  425. * @return array
  426. * @param string $line A line from the YAML file
  427. */
  428. private function _parseLine($line) {
  429. if (!$line) return array();
  430. $line = trim($line);
  431. if (!$line) return array();
  432. $array = array();
  433. $group = $this->nodeContainsGroup($line);
  434. if ($group) {
  435. $this->addGroup($line, $group);
  436. $line = $this->stripGroup ($line, $group);
  437. }
  438. if ($this->startsMappedSequence($line))
  439. return $this->returnMappedSequence($line);
  440. if ($this->startsMappedValue($line))
  441. return $this->returnMappedValue($line);
  442. if ($this->isArrayElement($line))
  443. return $this->returnArrayElement($line);
  444. if ($this->isPlainArray($line))
  445. return $this->returnPlainArray($line);
  446. return $this->returnKeyValuePair($line);
  447. }
  448. /**
  449. * Finds the type of the passed value, returns the value as the new type.
  450. * @access private
  451. * @param string $value
  452. * @return mixed
  453. */
  454. private function _toType($value) {
  455. if ($value === '') return null;
  456. $first_character = $value[0];
  457. $last_character = substr($value, -1, 1);
  458. $is_quoted = false;
  459. do {
  460. if (!$value) break;
  461. if ($first_character != '"' && $first_character != "'") break;
  462. if ($last_character != '"' && $last_character != "'") break;
  463. $is_quoted = true;
  464. } while (0);
  465. if ($is_quoted)
  466. return strtr(substr ($value, 1, -1), array ('\\"' => '"', '\'\'' => '\'', '\\\'' => '\''));
  467. if (strpos($value, ' #') !== false && !$is_quoted)
  468. $value = preg_replace('/\s+#(.+)$/','',$value);
  469. if (!$is_quoted) $value = str_replace('\n', "\n", $value);
  470. if ($first_character == '[' && $last_character == ']') {
  471. // Take out strings sequences and mappings
  472. $innerValue = trim(substr ($value, 1, -1));
  473. if ($innerValue === '') return array();
  474. $explode = $this->_inlineEscape($innerValue);
  475. // Propagate value array
  476. $value = array();
  477. foreach ($explode as $v) {
  478. $value[] = $this->_toType($v);
  479. }
  480. return $value;
  481. }
  482. if (strpos($value,': ')!==false && $first_character != '{') {
  483. $array = explode(': ',$value);
  484. $key = trim($array[0]);
  485. array_shift($array);
  486. $value = trim(implode(': ',$array));
  487. $value = $this->_toType($value);
  488. return array($key => $value);
  489. }
  490. if ($first_character == '{' && $last_character == '}') {
  491. $innerValue = trim(substr ($value, 1, -1));
  492. if ($innerValue === '') return array();
  493. // Inline Mapping
  494. // Take out strings sequences and mappings
  495. $explode = $this->_inlineEscape($innerValue);
  496. // Propagate value array
  497. $array = array();
  498. foreach ($explode as $v) {
  499. $SubArr = $this->_toType($v);
  500. if (empty($SubArr)) continue;
  501. if (is_array ($SubArr)) {
  502. $array[key($SubArr)] = $SubArr[key($SubArr)]; continue;
  503. }
  504. $array[] = $SubArr;
  505. }
  506. return $array;
  507. }
  508. if ($value == 'null' || $value == 'NULL' || $value == 'Null' || $value == '' || $value == '~') {
  509. return null;
  510. }
  511. if ( is_numeric($value) && preg_match ('/^(-|)[1-9]+[0-9]*$/', $value) ){
  512. $intvalue = (int)$value;
  513. if ($intvalue != PHP_INT_MAX)
  514. $value = $intvalue;
  515. return $value;
  516. }
  517. if (in_array($value,
  518. array('true', 'on', '+', 'yes', 'y', 'True', 'TRUE', 'On', 'ON', 'YES', 'Yes', 'Y'))) {
  519. return true;
  520. }
  521. if (in_array(strtolower($value),
  522. array('false', 'off', '-', 'no', 'n'))) {
  523. return false;
  524. }
  525. if (is_numeric($value)) {
  526. if ($value === '0') return 0;
  527. if (rtrim ($value, 0) === $value)
  528. $value = (float)$value;
  529. return $value;
  530. }
  531. return $value;
  532. }
  533. /**
  534. * Used in inlines to check for more inlines or quoted strings
  535. * @access private
  536. * @return array
  537. */
  538. private function _inlineEscape($inline) {
  539. // There's gotta be a cleaner way to do this...
  540. // While pure sequences seem to be nesting just fine,
  541. // pure mappings and mappings with sequences inside can't go very
  542. // deep. This needs to be fixed.
  543. $seqs = array();
  544. $maps = array();
  545. $saved_strings = array();
  546. // Check for strings
  547. $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/';
  548. if (preg_match_all($regex,$inline,$strings)) {
  549. $saved_strings = $strings[0];
  550. $inline = preg_replace($regex,'YAMLString',$inline);
  551. }
  552. unset($regex);
  553. $i = 0;
  554. do {
  555. // Check for sequences
  556. while (preg_match('/\[([^{}\[\]]+)\]/U',$inline,$matchseqs)) {
  557. $seqs[] = $matchseqs[0];
  558. $inline = preg_replace('/\[([^{}\[\]]+)\]/U', ('YAMLSeq' . (count($seqs) - 1) . 's'), $inline, 1);
  559. }
  560. // Check for mappings
  561. while (preg_match('/{([^\[\]{}]+)}/U',$inline,$matchmaps)) {
  562. $maps[] = $matchmaps[0];
  563. $inline = preg_replace('/{([^\[\]{}]+)}/U', ('YAMLMap' . (count($maps) - 1) . 's'), $inline, 1);
  564. }
  565. if ($i++ >= 10) break;
  566. } while (strpos ($inline, '[') !== false || strpos ($inline, '{') !== false);
  567. $explode = explode(', ',$inline);
  568. $stringi = 0; $i = 0;
  569. while (1) {
  570. // Re-add the sequences
  571. if (!empty($seqs)) {
  572. foreach ($explode as $key => $value) {
  573. if (strpos($value,'YAMLSeq') !== false) {
  574. foreach ($seqs as $seqk => $seq) {
  575. $explode[$key] = str_replace(('YAMLSeq'.$seqk.'s'),$seq,$value);
  576. $value = $explode[$key];
  577. }
  578. }
  579. }
  580. }
  581. // Re-add the mappings
  582. if (!empty($maps)) {
  583. foreach ($explode as $key => $value) {
  584. if (strpos($value,'YAMLMap') !== false) {
  585. foreach ($maps as $mapk => $map) {
  586. $explode[$key] = str_replace(('YAMLMap'.$mapk.'s'), $map, $value);
  587. $value = $explode[$key];
  588. }
  589. }
  590. }
  591. }
  592. // Re-add the strings
  593. if (!empty($saved_strings)) {
  594. foreach ($explode as $key => $value) {
  595. while (strpos($value,'YAMLString') !== false) {
  596. $explode[$key] = preg_replace('/YAMLString/',$saved_strings[$stringi],$value, 1);
  597. unset($saved_strings[$stringi]);
  598. ++$stringi;
  599. $value = $explode[$key];
  600. }
  601. }
  602. }
  603. $finished = true;
  604. foreach ($explode as $key => $value) {
  605. if (strpos($value,'YAMLSeq') !== false) {
  606. $finished = false; break;
  607. }
  608. if (strpos($value,'YAMLMap') !== false) {
  609. $finished = false; break;
  610. }
  611. if (strpos($value,'YAMLString') !== false) {
  612. $finished = false; break;
  613. }
  614. }
  615. if ($finished) break;
  616. $i++;
  617. if ($i > 10)
  618. break; // Prevent infinite loops.
  619. }
  620. return $explode;
  621. }
  622. private function literalBlockContinues ($line, $lineIndent) {
  623. if (!trim($line)) return true;
  624. if (strlen($line) - strlen(ltrim($line)) > $lineIndent) return true;
  625. return false;
  626. }
  627. private function referenceContentsByAlias ($alias) {
  628. do {
  629. if (!isset($this->SavedGroups[$alias])) { echo "Bad group name: $alias."; break; }
  630. $groupPath = $this->SavedGroups[$alias];
  631. $value = $this->result;
  632. foreach ($groupPath as $k) {
  633. $value = $value[$k];
  634. }
  635. } while (false);
  636. return $value;
  637. }
  638. private function addArrayInline ($array, $indent) {
  639. $CommonGroupPath = $this->path;
  640. if (empty ($array)) return false;
  641. foreach ($array as $k => $_) {
  642. $this->addArray(array($k => $_), $indent);
  643. $this->path = $CommonGroupPath;
  644. }
  645. return true;
  646. }
  647. private function addArray ($incoming_data, $incoming_indent) {
  648. // print_r ($incoming_data);
  649. if (count ($incoming_data) > 1)
  650. return $this->addArrayInline ($incoming_data, $incoming_indent);
  651. $key = key ($incoming_data);
  652. $value = isset($incoming_data[$key]) ? $incoming_data[$key] : null;
  653. if ($key === '__!YAMLZero') $key = '0';
  654. if ($incoming_indent == 0 && !$this->_containsGroupAlias && !$this->_containsGroupAnchor) { // Shortcut for root-level values.
  655. if ($key || $key === '' || $key === '0') {
  656. $this->result[$key] = $value;
  657. } else {
  658. $this->result[] = $value; end ($this->result); $key = key ($this->result);
  659. }
  660. $this->path[$incoming_indent] = $key;
  661. return;
  662. }
  663. $history = array();
  664. // Unfolding inner array tree.
  665. $history[] = $_arr = $this->result;
  666. foreach ($this->path as $k) {
  667. $history[] = $_arr = $_arr[$k];
  668. }
  669. if ($this->_containsGroupAlias) {
  670. $value = $this->referenceContentsByAlias($this->_containsGroupAlias);
  671. $this->_containsGroupAlias = false;
  672. }
  673. // Adding string or numeric key to the innermost level or $this->arr.
  674. if (is_string($key) && $key == '<<') {
  675. if (!is_array ($_arr)) { $_arr = array (); }
  676. $_arr = array_merge ($_arr, $value);
  677. } else if ($key || $key === '' || $key === '0') {
  678. if (!is_array ($_arr))
  679. $_arr = array ($key=>$value);
  680. else
  681. $_arr[$key] = $value;
  682. } else {
  683. if (!is_array ($_arr)) { $_arr = array ($value); $key = 0; }
  684. else { $_arr[] = $value; end ($_arr); $key = key ($_arr); }
  685. }
  686. $reverse_path = array_reverse($this->path);
  687. $reverse_history = array_reverse ($history);
  688. $reverse_history[0] = $_arr;
  689. $cnt = count($reverse_history) - 1;
  690. for ($i = 0; $i < $cnt; $i++) {
  691. $reverse_history[$i+1][$reverse_path[$i]] = $reverse_history[$i];
  692. }
  693. $this->result = $reverse_history[$cnt];
  694. $this->path[$incoming_indent] = $key;
  695. if ($this->_containsGroupAnchor) {
  696. $this->SavedGroups[$this->_containsGroupAnchor] = $this->path;
  697. if (is_array ($value)) {
  698. $k = key ($value);
  699. if (!is_int ($k)) {
  700. $this->SavedGroups[$this->_containsGroupAnchor][$incoming_indent + 2] = $k;
  701. }
  702. }
  703. $this->_containsGroupAnchor = false;
  704. }
  705. }
  706. private static function startsLiteralBlock ($line) {
  707. $lastChar = substr (trim($line), -1);
  708. if ($lastChar != '>' && $lastChar != '|') return false;
  709. if ($lastChar == '|') return $lastChar;
  710. // HTML tags should not be counted as literal blocks.
  711. if (preg_match ('#<.*?>$#', $line)) return false;
  712. return $lastChar;
  713. }
  714. private static function greedilyNeedNextLine($line) {
  715. $line = trim ($line);
  716. if (!strlen($line)) return false;
  717. if (substr ($line, -1, 1) == ']') return false;
  718. if ($line[0] == '[') return true;
  719. if (preg_match ('#^[^:]+?:\s*\[#', $line)) return true;
  720. return false;
  721. }
  722. private function addLiteralLine ($literalBlock, $line, $literalBlockStyle, $indent = -1) {
  723. $line = self::stripIndent($line, $indent);
  724. if ($literalBlockStyle !== '|') {
  725. $line = self::stripIndent($line);
  726. }
  727. $line = rtrim ($line, "\r\n\t ") . "\n";
  728. if ($literalBlockStyle == '|') {
  729. return $literalBlock . $line;
  730. }
  731. if (strlen($line) == 0)
  732. return rtrim($literalBlock, ' ') . "\n";
  733. if ($line == "\n" && $literalBlockStyle == '>') {
  734. return rtrim ($literalBlock, " \t") . "\n";
  735. }
  736. if ($line != "\n")
  737. $line = trim ($line, "\r\n ") . " ";
  738. return $literalBlock . $line;
  739. }
  740. function revertLiteralPlaceHolder ($lineArray, $literalBlock) {
  741. foreach ($lineArray as $k => $_) {
  742. if (is_array($_))
  743. $lineArray[$k] = $this->revertLiteralPlaceHolder ($_, $literalBlock);
  744. else if (substr($_, -1 * strlen ($this->LiteralPlaceHolder)) == $this->LiteralPlaceHolder)
  745. $lineArray[$k] = rtrim ($literalBlock, " \r\n");
  746. }
  747. return $lineArray;
  748. }
  749. private static function stripIndent ($line, $indent = -1) {
  750. if ($indent == -1) $indent = strlen($line) - strlen(ltrim($line));
  751. return substr ($line, $indent);
  752. }
  753. private function getParentPathByIndent ($indent) {
  754. if ($indent == 0) return array();
  755. $linePath = $this->path;
  756. do {
  757. end($linePath); $lastIndentInParentPath = key($linePath);
  758. if ($indent <= $lastIndentInParentPath) array_pop ($linePath);
  759. } while ($indent <= $lastIndentInParentPath);
  760. return $linePath;
  761. }
  762. private function clearBiggerPathValues ($indent) {
  763. if ($indent == 0) $this->path = array();
  764. if (empty ($this->path)) return true;
  765. foreach ($this->path as $k => $_) {
  766. if ($k > $indent) unset ($this->path[$k]);
  767. }
  768. return true;
  769. }
  770. private static function isComment ($line) {
  771. if (!$line) return false;
  772. if ($line[0] == '#') return true;
  773. if (trim($line, " \r\n\t") == '---') return true;
  774. return false;
  775. }
  776. private static function isEmpty ($line) {
  777. return (trim ($line) === '');
  778. }
  779. private function isArrayElement ($line) {
  780. if (!$line) return false;
  781. if ($line[0] != '-') return false;
  782. if (strlen ($line) > 3)
  783. if (substr($line,0,3) == '---') return false;
  784. return true;
  785. }
  786. private function isHashElement ($line) {
  787. return strpos($line, ':');
  788. }
  789. private function isLiteral ($line) {
  790. if ($this->isArrayElement($line)) return false;
  791. if ($this->isHashElement($line)) return false;
  792. return true;
  793. }
  794. private static function unquote ($value) {
  795. if (!$value) return $value;
  796. if (!is_string($value)) return $value;
  797. if ($value[0] == '\'') return trim ($value, '\'');
  798. if ($value[0] == '"') return trim ($value, '"');
  799. return $value;
  800. }
  801. private function startsMappedSequence ($line) {
  802. return ($line[0] == '-' && substr ($line, -1, 1) == ':');
  803. }
  804. private function returnMappedSequence ($line) {
  805. $array = array();
  806. $key = self::unquote(trim(substr($line,1,-1)));
  807. $array[$key] = array();
  808. $this->delayedPath = array(strpos ($line, $key) + $this->indent => $key);
  809. return array($array);
  810. }
  811. private function returnMappedValue ($line) {
  812. $array = array();
  813. $key = self::unquote (trim(substr($line,0,-1)));
  814. $array[$key] = '';
  815. return $array;
  816. }
  817. private function startsMappedValue ($line) {
  818. return (substr ($line, -1, 1) == ':');
  819. }
  820. private function isPlainArray ($line) {
  821. return ($line[0] == '[' && substr ($line, -1, 1) == ']');
  822. }
  823. private function returnPlainArray ($line) {
  824. return $this->_toType($line);
  825. }
  826. private function returnKeyValuePair ($line) {
  827. $array = array();
  828. $key = '';
  829. if (strpos ($line, ':')) {
  830. // It's a key/value pair most likely
  831. // If the key is in double quotes pull it out
  832. if (($line[0] == '"' || $line[0] == "'") && preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) {
  833. $value = trim(str_replace($matches[1],'',$line));
  834. $key = $matches[2];
  835. } else {
  836. // Do some guesswork as to the key and the value
  837. $explode = explode(':',$line);
  838. $key = trim($explode[0]);
  839. array_shift($explode);
  840. $value = trim(implode(':',$explode));
  841. }
  842. // Set the type of the value. Int, string, etc
  843. $value = $this->_toType($value);
  844. if ($key === '0') $key = '__!YAMLZero';
  845. $array[$key] = $value;
  846. } else {
  847. $array = array ($line);
  848. }
  849. return $array;
  850. }
  851. private function returnArrayElement ($line) {
  852. if (strlen($line) <= 1) return array(array()); // Weird %)
  853. $array = array();
  854. $value = trim(substr($line,1));
  855. $value = $this->_toType($value);
  856. $array[] = $value;
  857. return $array;
  858. }
  859. private function nodeContainsGroup ($line) {
  860. $symbolsForReference = 'A-z0-9_\-';
  861. if (strpos($line, '&') === false && strpos($line, '*') === false) return false; // Please die fast ;-)
  862. if ($line[0] == '&' && preg_match('/^(&['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1];
  863. if ($line[0] == '*' && preg_match('/^(\*['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1];
  864. if (preg_match('/(&['.$symbolsForReference.']+)$/', $line, $matches)) return $matches[1];
  865. if (preg_match('/(\*['.$symbolsForReference.']+$)/', $line, $matches)) return $matches[1];
  866. if (preg_match ('#^\s*<<\s*:\s*(\*[^\s]+).*$#', $line, $matches)) return $matches[1];
  867. return false;
  868. }
  869. private function addGroup ($line, $group) {
  870. if ($group[0] == '&') $this->_containsGroupAnchor = substr ($group, 1);
  871. if ($group[0] == '*') $this->_containsGroupAlias = substr ($group, 1);
  872. //print_r ($this->path);
  873. }
  874. private function stripGroup ($line, $group) {
  875. $line = trim(str_replace($group, '', $line));
  876. return $line;
  877. }
  878. }
  879. // Enable use of Spyc from command line
  880. // The syntax is the following: php spyc.php spyc.yaml
  881. define ('SPYC_FROM_COMMAND_LINE', false);
  882. do {
  883. if (!SPYC_FROM_COMMAND_LINE) break;
  884. if (empty ($_SERVER['argc']) || $_SERVER['argc'] < 2) break;
  885. if (empty ($_SERVER['PHP_SELF']) || $_SERVER['PHP_SELF'] != 'spyc.php') break;
  886. $file = $argv[1];
  887. printf ("Spyc loading file: %s\n", $file);
  888. print_r (spyc_load_file ($file));
  889. } while (0);
  890. /*
  891. *
  892. * The MIT License
  893. *
  894. * Copyright (c) 2009, ZX, Ferry Boender
  895. *
  896. * Permission is hereby granted, free of charge, to any person obtaining a copy
  897. * of this software and associated documentation files (the "Software"), to deal
  898. * in the Software without restriction, including without limitation the rights
  899. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  900. * copies of the Software, and to permit persons to whom the Software is
  901. * furnished to do so, subject to the following conditions:
  902. *
  903. * The above copyright notice and this permission notice shall be included in
  904. * all copies or substantial portions of the Software.
  905. *
  906. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  907. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  908. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  909. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  910. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  911. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  912. * THE SOFTWARE.
  913. *
  914. * Modified by Scott Zeid <s@srwz.us> to fix Template::templateFromString().
  915. *
  916. */
  917. define("TEMPLUM_VERSION", "0.4.0-sz-1");
  918. /**
  919. * @brief Templum errors.
  920. *
  921. * This exception is thrown by the Templum class when errors occur
  922. * during instantiation or when loading and parsing templates.
  923. */
  924. class TemplumError extends Exception {
  925. /**
  926. * @brief Create a new TemplumError instance
  927. * @param $message (string) The error message.
  928. * @param $code (int) The error code
  929. */
  930. public function TemplumError($message, $code = 0) {
  931. parent::__construct($message, $code);
  932. }
  933. }
  934. /**
  935. * @brief TemplumTemplate errors.
  936. *
  937. * This exception is thrown by the TemplumTemplate class when errors occur
  938. * during the execution of templates. PHP errors, warnings and notices that
  939. * occur during the template execution are captured by the TemplumTemplate class and
  940. * are thrown as TemplumTemplateError exceptions.
  941. */
  942. class TemplumTemplateError extends Exception {
  943. protected $template = NULL; /**< The TemplumTemplate instance causing the error. */
  944. /**
  945. * @brief Create a new TemplumTemplateError instance
  946. * @param $message (string) The error message.
  947. * @param $code (int) The error code
  948. * @param $template (TemplumTemplate) The template containing the error.
  949. */
  950. public function TemplumTemplateError($message, $code = 0, $template = NULL) {
  951. $this->template = $template;
  952. parent::__construct($message, $code);
  953. }
  954. /**
  955. * @brief Return the TemplumTemplate instance that contains the error.
  956. * @return (TemplumTemplate) The template containing the error or NULL if not available.
  957. */
  958. public function getTemplate() {
  959. return($this->template);
  960. }
  961. }
  962. /**
  963. * @brief Templum Templating Engine.
  964. *
  965. * This is the main Templum class. It takes care of retrieval, caching and
  966. * compiling of (translated) templates.
  967. */
  968. class Templum {
  969. /**
  970. * @brief Create a new Templum instance.
  971. * @param $templatePath (string) The full or relative path to the template directory.
  972. * @param $varsUniversal (array) An array of key/value pairs that will be exported to every template retrieved using this template engine instance.
  973. * @param $locale (string) The locale for the templates to retrieve. If a file with the suffix noted in $locale is available, it will be returned instead of the default .tpl file.
  974. * @throw TemplumError if the $templatePath can't be found or isn't a directory.
  975. */
  976. public function Templum($templatePath, $varsUniversal = array(), $locale = NULL) {
  977. if (!file_exists($templatePath)) {
  978. throw new TemplumError("No such file or directory: $templatePath", 1);
  979. }
  980. if (!is_dir($templatePath)) {
  981. throw new TemplumError("Not a directory: $templatePath", 2);
  982. }
  983. $this->templatePath = rtrim(realpath($templatePath), '/');
  984. $this->varsUniversal = $varsUniversal;
  985. $this->locale = $locale;
  986. $this->autoEscape = True;
  987. $this->cache = array();
  988. }
  989. /**
  990. * @brief Set a universal variable which will available in each template created with this Templum instance.
  991. * @param $varName (string) The name of the variable. This will become available in the template as $VARNAME.
  992. * @param $varValue (mixed) The value of the variable.
  993. */
  994. public function setVar($varName, $varValue) {
  995. $this->varsUniversal[$varName] = $varValue;
  996. }
  997. /**
  998. * @brief Turn the auto escape on or off. If on, all content rendered using {{ and }} will automatically be escaped with htmlspecialchars().
  999. * @param $escape (boolean) True of False. If True, auto escaping is turned on (this is the default). If False, it is turned off for templates retrieved with this Templum engine.
  1000. * @note Auto escaping can be overridden by passing the $autoEscape option to the template() and templateFromString() methods.
  1001. */
  1002. public function setAutoEscape($escape = True) {
  1003. $this->autoEscape = $escape;
  1004. }
  1005. /**
  1006. * @brief Set the locale for templates.
  1007. * @param $locale (string) The locale for the templates to retrieve. If a file with the suffix noted in $locale is available, it will be returned instead of the default .tpl file.
  1008. */
  1009. public function setLocale($locale) {
  1010. $this->locale = $locale;
  1011. }
  1012. /**
  1013. * @brief Retrieve a template by from disk (caching it in memory for the duration of the Templum instance lifetime) or from cache.
  1014. * @param $path (string) TemplumTemplate path, without the .tpl extension, relative to the templatePath.
  1015. * @param $varsGlobal (array) Array of key/value pairs that will be exported to the returned template and all templates included by that template.
  1016. * @param $autoEscape (boolean) Whether to auto escape {{ and }} output with htmlspecialchars()
  1017. * @throw TemplumError if the template couldn't be read.
  1018. */
  1019. public function template($path, $varsGlobal = array(), $autoEscape = NULL) {
  1020. $fpath = $this->templatePath . '/' . trim($path, '/').'.tpl';
  1021. if ($autoEscape === NULL) {
  1022. $autoEscape = $this->autoEscape;
  1023. }
  1024. // Check for translated version of this template.
  1025. if (!empty($this->locale)) {
  1026. // Check if the translated template exists in the cache. If it
  1027. // does, returned the cached result. Otherwise check the disk for
  1028. // the translated template.
  1029. $fpathTrans = realpath($fpath.'.'.$this->locale);
  1030. if ($fpathTrans !== False) {
  1031. if (array_key_exists($fpathTrans, $this->cache)) {
  1032. return($this->cache[$fpathTrans]);
  1033. } else {
  1034. if (file_exists($fpathTrans)) {
  1035. $fpath = $fpathTrans;
  1036. }
  1037. }
  1038. }
  1039. // Check the non-translated version of this template
  1040. } else {
  1041. // Check the cache for the non-translated template.
  1042. $rpath = realpath($fpath);
  1043. if($rpath === False) {
  1044. throw new TemplumError("Template not found or not a file: $fpath", 3);
  1045. }
  1046. if (array_key_exists($rpath, $this->cache)) {
  1047. return($this->cache[$rpath]);
  1048. }
  1049. $fpath = $rpath;
  1050. }
  1051. // Check if the template exists.
  1052. if (!is_file($fpath)) {
  1053. throw new TemplumError("Template not found or not a file: $fpath", 3);
  1054. }
  1055. if (!is_readable($fpath)) {
  1056. throw new TemplumError("Template not readable: $fpath", 4);
  1057. }
  1058. // Load the base or translated template.
  1059. $template = new TemplumTemplate(
  1060. $this,
  1061. $fpath,
  1062. $this->compile(file_get_contents($fpath), $autoEscape),
  1063. array_merge($this->varsUniversal, $varsGlobal)
  1064. );
  1065. $this->cache[$fpath] = $template;
  1066. return($template);
  1067. }
  1068. /**
  1069. * @brief Create a TemplumTemplate from a string.
  1070. *
  1071. * Create a TemplumTemplate instance using $contents as the template contents.
  1072. * This severely limited what you can do with the TemplumTemplate. There will be
  1073. * no including from the template, no translations, no caching, etc.
  1074. *
  1075. * @param $contents (string) The template contents.
  1076. * @param $autoEscape (boolean) Whether to auto escape {{ and }} output with htmlspecialchars()
  1077. * @returns (TemplumTemplate) TemplumTemplate class instance.
  1078. */
  1079. public static function templateFromString($contents, $autoEscape = True) {
  1080. //if ($autoEscape === Null) {
  1081. // $autoEscape = $this->autoEscape;
  1082. //}
  1083. // Load the base or translated template.
  1084. $template = new TemplumTemplate(
  1085. NULL,
  1086. "FROM_STRING",
  1087. self::compile($contents, $autoEscape),
  1088. array()
  1089. );
  1090. return($template);
  1091. }
  1092. /**
  1093. * @brief Compile a template string to PHP code.
  1094. * @param $contents (string) String to compile to PHP code.
  1095. * @param $autoEscape (boolean) Whether to auto escape {{ and }} output with htmlspecialchars()
  1096. * @note This method is used by the Templum class itself, and shouldn't be called directly yourself. Use templateFromString() instead.
  1097. */
  1098. private function compile($contents, $autoEscape = True) {
  1099. // Parse custom short-hand tags to PHP code.
  1100. $contents = preg_replace(
  1101. array(
  1102. "/{{/",
  1103. "/}}\n/",
  1104. "/}}/",
  1105. "/\[\[/",
  1106. "/\]\]/",
  1107. '/^\s*@(.*)$/m',
  1108. '/\[:\s*block\s(.*)\s*:\](.*)\[:\s*endblock\s*:\]/Usm',
  1109. ),
  1110. array(
  1111. $autoEscape ? "<?php echo(htmlspecialchars(" : "<?php echo(",
  1112. $autoEscape ? ")); ?>\n\n" : "); ?>\n\n",
  1113. $autoEscape ? ")); ?>" : "); ?>",
  1114. "<?php ",
  1115. " ?>",
  1116. "<?php \\1 ?>",
  1117. "<?php if (array_key_exists('\\1', \$this->inheritBlocks)) { print(\$this->inheritBlocks['\\1']); } else if (\$this->inheritFrom === NULL) { ?>\\2<?php } else { ob_start(); ?>\\2<?php \$this->inheritBlocks['\\1'] = ob_get_contents(); ob_end_clean(); } ?>",
  1118. ),
  1119. $contents
  1120. );
  1121. return($contents);
  1122. }
  1123. }
  1124. /**
  1125. * @brief Template class
  1126. *
  1127. * This is the TemplumTemplate class. It represents a template and handles the
  1128. * actual rendering of the template, as well as catching errors during
  1129. * rendering. It also contains helper methods which can be used in templates.
  1130. */
  1131. class TemplumTemplate {
  1132. /**
  1133. * @brief Create a new TemplumTemplate instance. You'd normally get an instance from a Templum class instance.
  1134. * @param $templum (Templum instance) The Templum class instance that generated this TemplumTemplate instance.
  1135. * @param $filename (string) The filename of this template.
  1136. * @param $contents (string) The compiled contents of this template.
  1137. * @param $varsGlobal (array) An array of key/value pairs which represent the global variables for this template and the templates it includes.
  1138. */
  1139. public function TemplumTemplate($templum, $filename, $contents, $varsGlobal = array()) {
  1140. $this->templum = $templum;
  1141. $this->filename = $filename;
  1142. $this->contents = $contents;
  1143. $this->varsGlobal = $varsGlobal;
  1144. $this->inheritFrom = NULL;
  1145. $this->inheritBlocks = array();
  1146. }
  1147. /**
  1148. * @brief Add an global variable. The global variable will be available to this templates and all the templates it includes.
  1149. * @param $varName (string) The name of the variable.
  1150. * @param $varValue (mixed) The value of the variable.
  1151. */
  1152. public function setVar($varName, $varValue) {
  1153. $this->varsGlobal[$varName] = $varValue;
  1154. }
  1155. /**
  1156. * @brief Render the contents of the template and return it as a string.
  1157. * @param $varsLocal (array) An array of key/value pairs which represent the local variables for this template.
  1158. * @return (string) The rendered contents of the template.
  1159. */
  1160. public function render($varsLocal = array()) {
  1161. // Extract the Universal (embedded in global), Global and
  1162. // Localvariables into the current scope.
  1163. extract($this->varsGlobal);
  1164. extract($varsLocal);
  1165. // Start output buffering so we can capture the output of the eval.
  1166. ob_start();
  1167. // Temporary set the error handler so we can show the faulty template
  1168. // file. Render the contents, reset the error handler and return the
  1169. // rendered contents.
  1170. $this->errorHandlerOrig = set_error_handler(array($this, 'errorHandler'));
  1171. eval("?>" . $this->contents);
  1172. restore_error_handler();
  1173. // Stop output buffering and return the contents of the buffer
  1174. // (rendered template).
  1175. $result = ob_get_contents();
  1176. ob_end_clean();
  1177. if ($this->inheritFrom !== NULL) {
  1178. $this->inheritFrom->inheritBlocks = $this->inheritBlocks;
  1179. $result = $this->inheritFrom->render();
  1180. }
  1181. return($result);
  1182. }
  1183. /**
  1184. * @brief The error handler that handles errors during the parsing of the template.
  1185. * @param $nr (int) Error code
  1186. * @param $string (string) Error message
  1187. * @param $file (string) Filename of the file in which the erorr occurred.
  1188. * @param $line (int) Linenumber of the line on which the error occurred.
  1189. * @note Do not call this yourself. It is used internally by Templum but must be public.
  1190. */
  1191. public function errorHandler($nr, $string, $file, $line) {
  1192. // We can restore the old error handler, otherwise this error handler
  1193. // will stay active because we throw an exception below.
  1194. restore_error_handler();
  1195. // If this is reached, it means we were still in Output Buffering mode.
  1196. // Stop output buffering, or we'll end up with template text on the
  1197. // Stdout.
  1198. ob_end_clean();
  1199. // Throw the exception
  1200. throw new TemplumTemplateError("$string (file: {$this->filename}, line $line)", 1, $this);
  1201. }
  1202. /**
  1203. * @brief Include another template.
  1204. * @param $template (string) The template to include.
  1205. * @param $varsLocal (array) An array of key/value pairs which represent the local variables for this template.
  1206. */
  1207. public function inc($template, $varsLocal = array()) {
  1208. if (!isset($this->templum)) {
  1209. throw new TemplumTemplateError("Cannot include templates in a TemplumTemplate instance created from a string.", 2, $this);
  1210. }
  1211. $t = $this->templum->template($template, $varsLocal);
  1212. print($t->render());
  1213. }
  1214. /**
  1215. * @brief Inherit from a parent template.
  1216. * @param $template (string) The template to inherit from.
  1217. */
  1218. public function inherit($template) {
  1219. $this->inheritFrom = $this->templum->template($template);
  1220. }
  1221. }
  1222. /* is_mobile()
  1223. * Shitty mobile device detection based on shitty user agent strings.
  1224. *
  1225. * Copyright (C) 2009-2012 Scott Zeid
  1226. *
  1227. * Permission is hereby granted, free of charge, to any person obtaining a copy
  1228. * of this software and associated documentation files (the "Software"), to deal
  1229. * in the Software without restriction, including without limitation the rights
  1230. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  1231. * copies of the Software, and to permit persons to whom the Software is
  1232. * furnished to do so, subject to the following conditions:
  1233. *
  1234. * The above copyright notice and this permission notice shall be included in
  1235. * all copies or substantial portions of the Software.
  1236. *
  1237. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  1238. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  1239. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  1240. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  1241. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  1242. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  1243. * THE SOFTWARE.
  1244. *
  1245. * Except as contained in this notice, the name(s) of the above copyright holders
  1246. * shall not be used in advertising or otherwise to promote the sale, use or
  1247. * other dealings in this Software without prior written authorization.
  1248. */
  1249. /**
  1250. * Determine whether the user agent represents a mobile device.
  1251. *
  1252. * The user can use the following query string parameters to override this
  1253. * function's output:
  1254. * * !mobile, nomobile - Cause this function to return False.
  1255. * * mobile - Cause this function to return True.
  1256. * * mobile=[...], device=[...] - Override the device type.
  1257. *
  1258. * device=[...] takes precedence over mobile=[...].
  1259. *
  1260. * Valid device types are firefox-tablet, firefox, chrome-tablet, chrome,
  1261. * android-tablet, android, webos, tablet, unknown, apple, and apple-tablet
  1262. * (listed in descending order of the author's personal preference). Android
  1263. * tablets and iPads are not considered to be mobile devices, but is_mobile()
  1264. * will still return a device name ending in "-tablet", as appropriate for the
  1265. * device in question.
  1266. *
  1267. * If the user is running Firefox Mobile, the device name would be "firefox",
  1268. * or "firefox-tablet" if it is a tablet. The same is true for users using
  1269. * Chrome, except (obviously) "firefox" would be replaced with "chrome".
  1270. * Although it has been discontinued for a while, support for the HP Touchpad
  1271. * may be added in the future; its device name would be "webos-tablet".
  1272. *
  1273. * @param bool $return_device Return a string representing the type of device.
  1274. * @param bool $use_get Allow overriding default behavior using query strings.
  1275. * @return mixed If $return_device is false, returns a boolean value.
  1276. */
  1277. function is_mobile($return_device = False, $use_get = True) {
  1278. # config
  1279. $user_agent = $_SERVER["HTTP_USER_AGENT"];
  1280. $nomobile = False; $forcemobile = False; $forcedevice = "";
  1281. if ($use_get) {
  1282. if (isset($_GET["!mobile"]) || isset($_GET["nomobile"]))
  1283. $nomobile = True;
  1284. elseif (isset($_GET["mobile"])) {
  1285. $forcedevice = strtolower($_GET["mobile"]);
  1286. if (!stristr($forcedevice, "tablet") && $forcedevice != "ipad")
  1287. $forcemobile = True;
  1288. if (!$forcedevice) $forcedevice = "unknown";
  1289. }
  1290. if (!empty($_GET["device"])) {
  1291. $forcedevice = strtolower($_GET["device"]);
  1292. if (!stristr($forcedevice, "tablet") && $forcedevice != "ipad") {
  1293. $forcemobile = True;
  1294. $nomobile = False;
  1295. }
  1296. }
  1297. }
  1298. # is mobile device?
  1299. if (((
  1300. (stristr($user_agent, "Android") && !stristr($user_agent, "Android 3.") &&
  1301. stristr($user_agent, "Mobile")) ||
  1302. stristr($user_agent, "webOS") ||
  1303. ((stristr($user_agent, "Firefox") || stristr($user_agent, "Fennec")) &&
  1304. stristr($user_agent, "Mobile")) ||
  1305. stristr($user_agent, "iPhone") || stristr($user_agent, "iPod")
  1306. ) && !stristr($user_agent, "Tablet") && $nomobile == False) ||
  1307. $forcemobile == True)
  1308. $mobile = True;
  1309. else
  1310. $mobile = False;
  1311. # which mobile device
  1312. $device = "unknown";
  1313. if (stristr($user_agent, "Android")) {
  1314. if (!stristr($user_agent, "Mobile") || stristr($user_agent, "Android 3."))
  1315. $device = "android-tablet";
  1316. else $device = "android";
  1317. if (stristr($user_agent, "Chrome"))
  1318. $device = str_replace("android", "chrome", $device);
  1319. }
  1320. if (stristr($user_agent, "Firefox") || stristr($user_agent, "Fennec")) {
  1321. if (stristr($user_agent, "Tablet")) $device = "firefox-tablet";
  1322. else $device = "firefox";
  1323. }
  1324. if (stristr($user_agent, "webOS")) $device = "webos";
  1325. if (stristr($user_agent, "iPhone") || stristr($user_agent, "iPod"))
  1326. $device = "apple";
  1327. if (stristr($user_agent, "iPad")) $device = "apple-tablet";
  1328. if ($forcedevice != "") $device = $forcedevice;
  1329. if (stristr($forcedevice, "fennec"))
  1330. $device = str_replace("fennec", "firefox", $device);
  1331. if ($forcedevice == "iphone" || $forcedevice == "ipod") $device = "apple";
  1332. if (stristr($forcedevice, "ipad")) $device = "apple-tablet";
  1333. if (((!$mobile && !$forcemobile) || $nomobile || $forcedevice === "") &&
  1334. !stristr($device, "tablet"))
  1335. $device = "";
  1336. # return value
  1337. if ($return_device == False) return $mobile;
  1338. else return $device;
  1339. }
  1340. function qmark_icon() {
  1341. return "data:image/png;base64,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";
  1342. }
  1343. /* Portal {{{1
  1344. *
  1345. * Copyright (C) 2006-2018 Scott Zeid
  1346. * https://code.s.zeid.me/portal
  1347. *
  1348. * Permission is hereby granted, free of charge, to any person obtaining a copy
  1349. * of this software and associated documentation files (the "Software"), to deal
  1350. * in the Software without restriction, including without limitation the rights
  1351. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  1352. * copies of the Software, and to permit persons to whom the Software is
  1353. * furnished to do so, subject to the following conditions:
  1354. *
  1355. * The above copyright notice and this permission notice shall be included in
  1356. * all copies or substantial portions of the Software.
  1357. *
  1358. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  1359. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  1360. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  1361. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  1362. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  1363. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  1364. * THE SOFTWARE.
  1365. *
  1366. * Except as contained in this notice, the name(s) of the above copyright holders
  1367. * shall not be used in advertising or otherwise to promote the sale, use or
  1368. * other dealings in this Software without prior written authorization.
  1369. */
  1370. // portal-data path and $debug {{{1
  1371. /* Relative path to the settings folder; default is "portal-data".
  1372. * This should be the same on both the filesystem and in URLs.
  1373. * Use "." for the current directory.
  1374. */
  1375. if (!isset($CONFIG_DIR))
  1376. $CONFIG_DIR = "portal-data";
  1377. // Set to True to get the $portal array when visiting the portal
  1378. // Use only for debugging purposes.
  1379. $debug = False;
  1380. ////////////////////////////////////////////////////////////////////////////////
  1381. // Setup {{{1
  1382. // Workaround for templates raising fatal errors in PHP >= 5.4.0 when
  1383. // date.timezone is not set. If that is the case, then this line will
  1384. // raise a warning.
  1385. date_default_timezone_set(date_default_timezone_get());
  1386. // Configuration loading and sanitation {{{1
  1387. $portal = spyc_load(
  1388. str_replace("\r\n", "\n", str_replace("\r", "\n", file_get_contents(
  1389. "$CONFIG_DIR/settings.yaml"
  1390. )))
  1391. );
  1392. $portal["CONFIG_DIR"] = $CONFIG_DIR;
  1393. $name = $portal["name"] = $portal["name"];
  1394. $theme = $portal["theme"] = $portal["theme"];
  1395. if (!isset($portal["banner"]))
  1396. $portal["banner"] = array("type" => "text", "content" => $name);
  1397. $portal["banner"]["type"] = strtolower($portal["banner"]["type"]);
  1398. if (!in_array($portal["banner"]["type"], array("text", "image", "none")))
  1399. $portal["banner"]["type"] = "text";
  1400. $use_templum_for_banner_content = isset($portal["banner"]["content"]) &&
  1401. $portal["banner"]["type"] != "none";
  1402. $openid_enabled = !empty($portal["openid"]["xrds"]) &&
  1403. ((!empty($portal["openid"]["provider"]) &&
  1404. !empty($portal["openid"]["local_id"])) ||
  1405. (!empty($portal["openid"]["server"]) &&
  1406. !empty($portal["openid"]["delegate"])));
  1407. $ga_enabled = !empty($portal["google-analytics"]["account"]) &&
  1408. !empty($portal["google-analytics"]["style"]) &&
  1409. in_array($portal["google-analytics"]["style"],array("new","old"));
  1410. $request_uri = (!empty($_SERVER["REQUEST_URI"])) ? $_SERVER["REQUEST_URI"] : "";
  1411. $url_scheme = ((!empty($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] != "off")
  1412. || (!empty($_SERVER["HTTP_X_FORWARDED_PROTO"])
  1413. && $_SERVER["HTTP_X_FORWARDED_PROTO"] == "https")
  1414. || (!empty($_SERVER["HTTP_X_FORWARDED_PROTOCOL"])
  1415. && $_SERVER["HTTP_X_FORWARDED_PROTOCOL"] == "https")
  1416. || (!empty($_SERVER["HTTP_X_FORWARDED_SSL"])
  1417. && $_SERVER["HTTP_X_FORWARDED_SSL"] == "on")
  1418. || (!empty($_SERVER["HTTP_FRONT_END_HTTPS"])
  1419. && $_SERVER["HTTP_FRONT_END_HTTPS"] == "on")) ?
  1420. "https" : "http";
  1421. if (!empty($portal["url-root"]))
  1422. $url_root = $portal["url-root"] = rtrim($portal["url-root"], "/");
  1423. else {
  1424. $url_root = "$url_scheme://{$_SERVER["HTTP_HOST"]}";
  1425. $url_root .= implode("/",explode("/", $_SERVER["PHP_SELF"], -1));
  1426. $portal["url-root"] = $url_root;
  1427. }
  1428. // Mobile device detection {{{1
  1429. $mobile = is_mobile(False, True);
  1430. $device = is_mobile(True, True);
  1431. // Template namespace {{{1
  1432. $namespace = array();
  1433. $names = explode(",", "CONFIG_DIR,device,ga_enabled,mobile,name,"
  1434. ."openid_enabled,portal,request_uri,url_scheme");
  1435. foreach ($names as $n) {
  1436. $namespace[$n] = &$$n;
  1437. }
  1438. // Debug output {{{1
  1439. if ($debug) {
  1440. header("Content-type: text/plain");
  1441. print_r($portal);
  1442. exit();
  1443. }
  1444. // JSON output {{{1
  1445. if (isset($_GET["json"])) {
  1446. // Update namespace
  1447. $names = explode(",", "_403,_404,action,highlight,minibar,narrow,orientation,"
  1448. ."request_uri,small,target,theme,url_root");
  1449. foreach ($names as $n) {
  1450. $namespace[$n] = &$$n;
  1451. }
  1452. if ($use_templum_for_banner_content)
  1453. $portal["banner"]["content"] = tpl($portal["banner"]["content"], $namespace);
  1454. if (is_array($portal["sites"])) {
  1455. foreach ($portal["sites"] as $slug => &$site) {
  1456. $keys = array("name", "icon", "url", "desc");
  1457. foreach ($keys as $key) {
  1458. if (!empty($site[$key])) {
  1459. $site[$key] = $v = tpl($site[$key], $namespace);
  1460. if ($key == "url") {
  1461. if (strpos($v, "/") === 0 && strpos($v, "//") !== 0)
  1462. $site[$key] = $v = "$url_scheme://{$_SERVER["HTTP_HOST"]}/$v";
  1463. }
  1464. if ($key == "icon") {
  1465. if (preg_match("/(((http|ftp)s|file|data)?\:|\/\/)/i", $v))
  1466. $site[$key] = $v = array("large" => $v, "small" => $v);
  1467. else if (strpos($v, "/") === 0) {
  1468. $v = "$url_scheme://{$_SERVER["HTTP_HOST"]}/$v";
  1469. $site[$key] = $v = array("large" => $v, "small" => $v);
  1470. } else {
  1471. $site[$key] = $v = array(
  1472. "large" => $url_root."/$CONFIG_DIR/icons/".$v,
  1473. "small" => $url_root."/$CONFIG_DIR/icons/small/".$v
  1474. );
  1475. }
  1476. }
  1477. }
  1478. }
  1479. }
  1480. }
  1481. header("Content-Type: application/json; charset=utf-8");
  1482. $data = array(
  1483. "name" => $portal["name"],
  1484. "url" => $portal["url"],
  1485. "url-root" => $portal["url-root"],
  1486. "config-dir" => $CONFIG_DIR,
  1487. "banner" => $portal["banner"],
  1488. "sites" => $portal["sites"]
  1489. );
  1490. if (defined("JSON_PRETTY_PRINT") && defined("JSON_UNESCAPED_SLASHES"))
  1491. echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
  1492. else
  1493. echo json_encode($data);
  1494. } // JSON output
  1495. // HTML output {{{1
  1496. else if (!isset($_GET["css"]) || !trim($_GET["css"]) != "") {
  1497. $action = "index";
  1498. if (isset($_GET["minibar"])) {
  1499. $minibar = True;
  1500. $action = "minibar";
  1501. $highlight = (!empty($_GET["highlight"])) ? $_GET["highlight"] : "";
  1502. $orientation = $portal["minibar-orientation"];
  1503. if (!isset($_GET["horizontal"]) || !isset($_GET["vertical"])) {
  1504. if (isset($_GET["horizontal"])) $orientation = "horizontal";
  1505. elseif (isset($_GET["vertical"])) $orientation = "vertical";
  1506. }
  1507. } else
  1508. $minibar = False;
  1509. $target = (!empty($_GET["target"])) ? $_GET["target"] : $portal["link-target"];
  1510. $theme = (!empty($_GET["theme"])) ? $_GET["theme"] : $theme;
  1511. $narrow = (isset($_GET["narrow"])) ? True : $portal["narrow"];
  1512. if (isset($_GET["!narrow"]) || isset($_GET["wide"])) $narrow = False;
  1513. $small = (isset($_GET["small"])) ? True : $portal["small"];
  1514. if (isset($_GET["!small"]) || isset($_GET["large"]) || isset($_GET["big"]))
  1515. $small = False;
  1516. $_403 = isset($_GET["403"]);
  1517. $_404 = isset($_GET["404"]);
  1518. if ($_403 || $_404) {
  1519. $action = "error";
  1520. $request_uri = $portal["url"];
  1521. if ($_403) header("HTTP/1.0 403 Forbidden");
  1522. else if ($_404) header("HTTP/1.0 404 Not Found");
  1523. }
  1524. // Update namespace
  1525. $names = explode(",", "_403,_404,action,highlight,minibar,narrow,orientation,"
  1526. ."request_uri,small,target,theme,url_root");
  1527. foreach ($names as $n) {
  1528. $namespace[$n] = &$$n;
  1529. }
  1530. // Template expansion for config values
  1531. if ($use_templum_for_banner_content)
  1532. $portal["banner"]["content"] = tpl($portal["banner"]["content"], $namespace);
  1533. if (isset($portal["custom-head-content"]))
  1534. $portal["custom-head-content"] = tpl($portal["custom-head-content"],
  1535. $namespace);
  1536. else
  1537. $portal["custom-head-content"] = "";
  1538. if (isset($portal["custom-footer-content"]))
  1539. $portal["custom-footer-content"] = tpl($portal["custom-footer-content"],
  1540. $namespace);
  1541. else
  1542. $portal["custom-footer-content"] = "";
  1543. $any_icons = false;
  1544. if (is_array($portal["sites"])) {
  1545. foreach ($portal["sites"] as $slug => &$site) {
  1546. $keys = array("name", "icon", "url", "desc");
  1547. if (!empty($site["icon"]))
  1548. $any_icons = true;
  1549. foreach ($keys as $key) {
  1550. if (!empty($site[$key]))
  1551. $site[$key] = tpl($site[$key], $namespace);
  1552. }
  1553. }
  1554. }
  1555. $div_body_classes = "";
  1556. if ($narrow)
  1557. $div_body_classes .= " narrow";
  1558. if ($small)
  1559. $div_body_classes .= " small";
  1560. if ($any_icons)
  1561. $div_body_classes .= " any-icons";
  1562. $div_body_classes = trim($div_body_classes);
  1563. // Update namespace
  1564. $names = explode(",", "any_icons,div_body_classes");
  1565. foreach ($names as $n) {
  1566. $namespace[$n] = &$$n;
  1567. }
  1568. // Yadis XRDS header; needs to be sent as a proper header instead of a meta
  1569. // tag in order to validate as HTML5
  1570. if ($openid_enabled)
  1571. header("X-XRDS-Location: ".rawurlencode($portal["openid"]["xrds"]));
  1572. // HTML template {{{1
  1573. echo htmlsymbols(tpl(<<<HTML
  1574. <!DOCTYPE html>
  1575. <html>
  1576. <head>
  1577. <meta charset="utf-8" />
  1578. <!--
  1579. Portal
  1580. Copyright (C) 2006-2018 Scott Zeid
  1581. https://code.s.zeid.me/portal
  1582. Permission is hereby granted, free of charge, to any person obtaining a copy
  1583. of this software and associated documentation files (the "Software"), to deal
  1584. in the Software without restriction, including without limitation the rights
  1585. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  1586. copies of the Software, and to permit persons to whom the Software is
  1587. furnished to do so, subject to the following conditions:
  1588. The above copyright notice and this permission notice shall be included in
  1589. all copies or substantial portions of the Software.
  1590. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  1591. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  1592. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  1593. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  1594. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  1595. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  1596. THE SOFTWARE.
  1597. Except as contained in this notice, the name(s) of the above copyright holders
  1598. shall not be used in advertising or otherwise to promote the sale, use or
  1599. other dealings in this Software without prior written authorization.
  1600. -->
  1601. <title>{{\$portal["name"]}}</title>
  1602. @if (file_exists("\$CONFIG_DIR/favicon.png")):
  1603. <link rel="shortcut icon" type="image/png" href="{{\$CONFIG_DIR}}/favicon.png" />
  1604. @endif
  1605. @if (\$_403 || \$_404):
  1606. <base href="{{\$url_root}}/" />
  1607. @endif
  1608. @if (\$openid_enabled):
  1609. @ /* OpenID */
  1610. <!--openid-->
  1611. @if (!empty(\$portal["openid"]["provider"])):
  1612. <link rel="openid2.provider" href="{{\$portal["openid"]["provider"]}}" />
  1613. @endif
  1614. @if (!empty(\$portal["openid"]["local_id"])):
  1615. <link rel="openid2.local_id" href="{{\$portal["openid"]["local_id"]}}" />
  1616. @endif
  1617. @if (!empty(\$portal["openid"]["server"])):
  1618. <link rel="openid.server" href="{{\$portal["openid"]["server"]}}" />
  1619. @endif
  1620. @if (!empty(\$portal["openid"]["delegate"])):
  1621. <link rel="openid.delegate" href="{{\$portal["openid"]["delegate"]}}" />
  1622. @endif
  1623. <!--/openid-->
  1624. @endif // OpenID
  1625. <meta name="generator" content="Portal by Scott Zeid; X11 License; https://code.s.zeid.me/portal" />
  1626. <link rel="stylesheet" type="text/css" href="{{\$url_scheme}}://fonts.googleapis.com/css?family=Ubuntu:regular,italic,bold,bolditalic" />
  1627. <link rel="stylesheet" type="text/css" href="?css={{\$theme}}&amp;.css" />
  1628. @if (\$mobile):
  1629. <meta name="viewport" content="width=532; initial-scale=0.6; minimum-scale: 0.6" />
  1630. @endif
  1631. @if (\$ga_enabled && \$portal["google-analytics"]["style"] == "new"):
  1632. @ /* Google Analytics - New style */
  1633. <script type="text/javascript">
  1634. var _gaq = _gaq || [];
  1635. _gaq.push(['_setAccount', '{{\$portal["google-analytics"]["account"]}}']);
  1636. _gaq.push(['_trackPageview']);
  1637. (function() {
  1638. var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  1639. ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  1640. var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  1641. })();
  1642. </script>
  1643. @endif // Google Analytics - New style
  1644. [[if (\$portal["custom-head-content"])
  1645. echo indent(htmlsymbols(trim(\$portal["custom-head-content"], "\r\n")), 2)."\n";]]
  1646. </head>
  1647. <body id="action_{{\$action}}"[[if (\$mobile || \$device) {
  1648. ]] class="[[if (\$mobile) echo "mobile "; if (\$device) echo "device_\$device";]]"[[
  1649. }]]>
  1650. @if (\$minibar):
  1651. @ /* Minibar */
  1652. <div id="minibar" class="{{\$orientation}}">
  1653. [[
  1654. /* Minibar site list */
  1655. foreach (\$portal["sites"] as \$slug => &\$site) {
  1656. if ((!isset(\$site["minibar"]) || \$site["minibar"] !== False) &&
  1657. !empty(\$site["url"])) {
  1658. \$code = "";
  1659. if (\$orientation == "vertical") \$code .= "<div>";
  1660. // Link
  1661. \$code .= "<a href=\"".htmlentitiesu8(\$site["url"], True)."\" target=\"_blank\"";
  1662. // Highlight
  1663. if (\$highlight == \$slug) \$code .= ' class="highlight"';
  1664. // Site name
  1665. if (!empty(\$site["name"])) {
  1666. \$name = str_replace("\n", " ", htmlentitiesu8(strip_tags(\$site["name"]), False));
  1667. \$name = str_replace("&amp;", "&", \$name);
  1668. } else {
  1669. \$name = htmlentitiesu8(\$site["url"], True);
  1670. }
  1671. \$code .= " title=\"".\$name;
  1672. // Site description
  1673. if (isset(\$site["desc"]) && trim(\$site["desc"])) {
  1674. \$desc = str_replace("\n", "&#x0a;",
  1675. htmlentitiesu8(strip_tags(\$site["desc"]), False));
  1676. \$desc = str_replace("&amp;", "&", \$desc);
  1677. \$code .= " &mdash; ".\$desc;
  1678. }
  1679. // Icon
  1680. if (!empty(\$site["icon"])) {
  1681. \$icon_url = htmlentitiesu8(\$site["icon"], True);
  1682. if (preg_match("/(((http|ftp)s|file|data)?\:|\/\/)/i", \$site["icon"]))
  1683. \$icon_url = \$icon_url;
  1684. else if (strpos(\$site["icon"], "/") === 0)
  1685. \$icon_url = "\$url_scheme://{\$_SERVER["HTTP_HOST"]}/\$icon_url";
  1686. else
  1687. \$icon_url = "\$CONFIG_DIR/icons/small/\$icon_url";
  1688. \$code .= "\"><img src=\"\$icon_url\" alt=\"Icon\" /></a>";
  1689. } else {
  1690. \$icon_url = htmlentitiesu8("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQI12NgAAIAAAUAAeImBZsAAAAASUVORK5CYII=", True);
  1691. \$code .= "\"><img src=\"\$icon_url\" class=\"empty\" alt=\"Icon\" /></a>";
  1692. }
  1693. if (\$orientation == "vertical") \$code .= "</div>";
  1694. echo \$code;
  1695. }
  1696. }
  1697. ]]
  1698. </div>
  1699. @//Minibar
  1700. @else:
  1701. @ /* Normal mode */
  1702. @if (\$portal["banner"]["type"] != "none"):
  1703. @ /* Banner */
  1704. <div id="header" class="{{\$portal["banner"]["type"]}}[[if (\$small) echo ' small';]]">
  1705. <h1 id="title">
  1706. <span>
  1707. <a href="{{\$request_uri}}">
  1708. @if (\$portal["banner"]["type"] == "image"):
  1709. @ /* Image banner */
  1710. <img src="[[echo htmlentitiesu8((!empty(\$portal["banner"]["content"]))
  1711. ? \$portal["banner"]["content"]
  1712. : "\$CONFIG_DIR/images/banner.png", True
  1713. );]]" alt="{{\$name}}" />
  1714. @else: // Image banner
  1715. @ /* Text banner */
  1716. [[echo indent(htmlsymbols((!empty(\$portal["banner"]["content"]))
  1717. ? \$portal["banner"]["content"] : \$name), 6)."\n";]]
  1718. @endif // Text banner
  1719. </a>
  1720. </span>
  1721. </h1>
  1722. </div>
  1723. @endif // Banner
  1724. <div id="body"[[if (\$div_body_classes) {
  1725. ]] class="[[echo "\$div_body_classes";]]"[[
  1726. }]]>
  1727. @if (\$_403):
  1728. <p>You don't have permission to view this page.</p>
  1729. @elseif (\$_404):
  1730. <p>The page you were looking for was not found.</p>
  1731. @else:
  1732. [[
  1733. /* Normal site list */
  1734. foreach (\$portal["sites"] as \$slug => &\$site) {
  1735. if (!isset(\$site["index"]) || \$site["index"] !== False) {
  1736. \$code = "";
  1737. \$code .= "<p class=\"site";
  1738. if (!empty(\$site["url"])) \$code .= " has-url";
  1739. if (empty(\$site["name"])) \$code .= " no-name";
  1740. if (empty(\$site["icon"])) \$code .= " no-icon";
  1741. \$code .= "\">\n";
  1742. // Link
  1743. if (!empty(\$site["url"])) {
  1744. \$code .= " <a href=\"".htmlentitiesu8(\$site["url"], True)."\"";
  1745. // Link target
  1746. if (\$target) \$code .= " target=\"\$target\"";
  1747. \$code .= ">\n";
  1748. } else
  1749. \$code .= " <span>\n";
  1750. // Image
  1751. if (!empty(\$site["icon"])) {
  1752. \$icon_url = htmlentitiesu8(\$site["icon"], True);
  1753. if (preg_match("/(((http|ftp)s|file|data)?\:|\/\/)/i", \$site["icon"]))
  1754. \$icon_url = \$icon_url;
  1755. else if (strpos(\$site["icon"], "/") === 0)
  1756. \$icon_url = "\$url_scheme://{\$_SERVER["HTTP_HOST"]}/\$icon_url";
  1757. else
  1758. \$icon_url = "\$CONFIG_DIR/icons".((\$small)?"/small":"")."/\$icon_url";
  1759. \$code .= " <span><img src=\"\$icon_url\" alt=\" \" />";
  1760. } else
  1761. \$code .= " <span>";
  1762. // Site name
  1763. if (isset(\$site["name"]) && trim(\$site["name"])) {
  1764. \$code .= "<strong class=\"name\">".htmlsymbols(\$site["name"])."</strong>";
  1765. }
  1766. \$code .= "</span>";
  1767. // Site description
  1768. if (isset(\$site["desc"]) && trim(\$site["desc"])) {
  1769. \$code .= "<br />\n <span class=\"desc\">";
  1770. \$code .= str_replace("\n", "&#x0a;", htmlsymbols(\$site["desc"]))."</span>";
  1771. }
  1772. // Close stuff
  1773. \$code .= "\n ".((!empty(\$site["url"])) ? "</a>" : "</span>")."\n</p>";
  1774. echo indent(\$code, 3);
  1775. echo "\n";
  1776. }
  1777. }
  1778. ]]
  1779. @endif
  1780. </div>
  1781. <div id="footer" class="footer[[if (\$small) echo " small";]]">
  1782. <p>
  1783. <a href="https://code.s.zeid.me/portal">Portal software</a>
  1784. copyright &copy; [[echo copyright_year(2006);]] <a href="https://s.zeid.me/">Scott Zeid</a>.
  1785. </p>
  1786. [[if (\$portal["custom-footer-content"])
  1787. echo indent(htmlsymbols(trim(\$portal["custom-footer-content"], "\r\n")), 3)."\n";]]
  1788. @if (\$portal["show-validator-links"]):
  1789. @ /* W3C Validator links */
  1790. <p>
  1791. <a href="http://validator.w3.org/check?uri=referer">
  1792. <img src="{{\$CONFIG_DIR}}/images/html5.png" alt="Valid HTML5" class="button_80x15" />
  1793. </a>&nbsp;
  1794. <a href="http://jigsaw.w3.org/css-validator/check/referer?profile=css3">
  1795. <img src="{{\$CONFIG_DIR}}/images/css.png" alt="Valid CSS" class="button_80x15" />
  1796. </a>
  1797. </p>
  1798. @endif // W3C Validator links
  1799. </div>
  1800. @if (\$ga_enabled && \$portal["google-analytics"]["style"] != "new"):
  1801. @ /* Google Analytics - Old style */
  1802. <script type="text/javascript">
  1803. var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
  1804. document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
  1805. </script>
  1806. <script type="text/javascript">
  1807. try{
  1808. var pageTracker = _gat._getTracker("{{\$portal["google-analytics"]["account"]}}");
  1809. pageTracker._trackPageview();
  1810. } catch(err) {}
  1811. </script>
  1812. @endif // Google Analytics - Old style
  1813. @endif // Normal link listing
  1814. </body>
  1815. </html>
  1816. HTML
  1817. , $namespace));
  1818. } // HTML template and output
  1819. // CSS output {{{1
  1820. else {
  1821. $theme = $portal["themes"][$_GET["css"]];
  1822. $custom_css = (isset($theme["custom_css"])) ? $theme["custom_css"] : "";
  1823. $theme = tpl_r($theme);
  1824. // Update namespace
  1825. $names = explode(",", "theme");
  1826. foreach ($names as $n) {
  1827. $namespace[$n] = &$$n;
  1828. }
  1829. header("Content-type: text/css");
  1830. // CSS template {{{1
  1831. echo tpl(<<<CSS
  1832. /* Portal
  1833. *
  1834. * Copyright (C) 2006-2018 Scott Zeid
  1835. * https://code.s.zeid.me/portal
  1836. *
  1837. * Permission is hereby granted, free of charge, to any person obtaining a copy
  1838. * of this software and associated documentation files (the "Software"), to deal
  1839. * in the Software without restriction, including without limitation the rights
  1840. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  1841. * copies of the Software, and to permit persons to whom the Software is
  1842. * furnished to do so, subject to the following conditions:
  1843. *
  1844. * The above copyright notice and this permission notice shall be included in
  1845. * all copies or substantial portions of the Software.
  1846. *
  1847. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  1848. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  1849. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  1850. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  1851. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  1852. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  1853. * THE SOFTWARE.
  1854. *
  1855. * Except as contained in this notice, the name(s) of the above copyright holders
  1856. * shall not be used in advertising or otherwise to promote the sale, use or
  1857. * other dealings in this Software without prior written authorization.
  1858. */
  1859. body {
  1860. margin: 0px;
  1861. background: {{\$theme["bg"]}};
  1862. font-family: "Ubuntu", "DejaVu Sans", "Bitstream Vera Sans", "Verdana",
  1863. sans-serif;
  1864. font-size: 1em;
  1865. text-align: center;
  1866. color: {{\$theme["fg"][0]}};
  1867. }
  1868. :focus {
  1869. outline: none;
  1870. }
  1871. a {
  1872. color: {{\$theme["fg"][1]}}; text-decoration: none;
  1873. }
  1874. a:hover, .site.has-url:hover * {
  1875. color: {{\$theme["fg"][2]}};
  1876. }
  1877. a:active, .site.has-url:active * {
  1878. color: {{\$theme["fg"][3]}};
  1879. }
  1880. h1, .h1 {
  1881. font-size: 2.5em;
  1882. font-weight: normal;
  1883. }
  1884. h2, .h2, .name {
  1885. font-size: 1.5em;
  1886. font-weight: normal;
  1887. }
  1888. img {
  1889. border-style: none;
  1890. }
  1891. .monospace {
  1892. font-family: "Courier New", "Courier", monospace;
  1893. }
  1894. .small {
  1895. font-size: .6em;
  1896. }
  1897. #header {
  1898. margin: 1em;
  1899. }
  1900. #header.text #title span {
  1901. background: {{\$theme["logo_bg"]}};
  1902. @if (stripos(\$theme["logo_bg"], "transparent") === False):
  1903. border: 1px solid {{\$theme["logo_border"]}};
  1904. @endif
  1905. padding: .2em;
  1906. }
  1907. #header a {
  1908. color: {{\$theme["fg"][0]}};
  1909. }
  1910. #body {
  1911. width: 500px;
  1912. margin: 1em auto;
  1913. }
  1914. #body.narrow {
  1915. width: 250px;
  1916. }
  1917. #body.small {
  1918. width: 312px;
  1919. margin: 0.5em auto;
  1920. }
  1921. #body.narrow.small {
  1922. width: 156px;
  1923. }
  1924. .site {
  1925. margin-top: 1em; margin-bottom: 1em;
  1926. text-align: left;
  1927. background: {{\$theme["link_bg"]}};
  1928. }
  1929. .site.has-url:hover, #minibar a:hover {
  1930. background: {{\$theme["link_bg_h"]}};
  1931. }
  1932. .site.has-url:active, #minibar a:active {
  1933. background: {{\$theme["link_bg_a"]}};
  1934. }
  1935. .site a, .site > span {
  1936. display: block;
  1937. }
  1938. .site img {
  1939. width: 32px; height: 32px;
  1940. margin: 10px;
  1941. vertical-align: top;
  1942. /*background: {{\$theme["ico_bg"]}};*/
  1943. }
  1944. .small .site img {
  1945. width: 16px; height: 16px;
  1946. margin: 6px;
  1947. }
  1948. .site.no-icon .name {
  1949. margin-left: 15px;
  1950. }
  1951. .any-icons .site.no-icon .name {
  1952. margin-left: 52px;
  1953. }
  1954. .small .site.no-icon .name {
  1955. margin-left: 9px;
  1956. }
  1957. .small.any-icons .site.no-icon .name {
  1958. margin-left: 28px;
  1959. }
  1960. .site .name {
  1961. display: inline-block;
  1962. width: 436px;
  1963. margin-right: 12px;
  1964. padding: 12px 0;
  1965. vertical-align: middle;
  1966. }
  1967. .narrow .site .name {
  1968. width: 186px;
  1969. }
  1970. .small .site .name {
  1971. width: 276px;
  1972. margin-right: 8px;
  1973. padding: 5px 0 7px 0;
  1974. }
  1975. .narrow.small .site .name {
  1976. width: 120px;
  1977. }
  1978. .site .desc {
  1979. display: block;
  1980. margin-left: 15px; margin-right: 12px;
  1981. padding-bottom: 12px;
  1982. text-align: justify;
  1983. }
  1984. .site.no-name .desc {
  1985. margin-top: -0.375em;
  1986. }
  1987. .any-icons .site .desc {
  1988. margin-left: 52px;
  1989. }
  1990. .small .site .desc {
  1991. margin-left: 9px; margin-right: 8px;
  1992. padding-bottom: 8px;
  1993. }
  1994. .small.any-icons .site .desc {
  1995. margin-left: 28px;
  1996. }
  1997. #footer {
  1998. font-size: .6em;
  1999. }
  2000. #footer.small {
  2001. font-size: .5em;
  2002. }
  2003. .button_80x15 {
  2004. width: 80px; height: 15px;
  2005. }
  2006. .mobile {
  2007. background-attachment: scroll;
  2008. }
  2009. .mobile #body {
  2010. font-size: 1.5em;
  2011. width: 484px;
  2012. }
  2013. .mobile #body.narrow {
  2014. width: 363px;
  2015. }
  2016. .mobile #body.small {
  2017. width: 363px;
  2018. font-size: 0.9em;
  2019. }
  2020. .mobile #body.narrow.small {
  2021. width: 230px;
  2022. }
  2023. .mobile .site img {
  2024. width: 48px; height: 48px;
  2025. }
  2026. .mobile .small .site img {
  2027. width: 24px; height: 24px;
  2028. }
  2029. .mobile .any-icons .site.no-icon .name {
  2030. margin-left: 68px;
  2031. }
  2032. .mobile .small.any-icons .site.no-icon .name {
  2033. margin-left: 36px;
  2034. }
  2035. .mobile .site .name {
  2036. width: 396px;
  2037. }
  2038. .mobile .narrow .site .name {
  2039. width: 283px;
  2040. }
  2041. .mobile .small .site .name {
  2042. width: 319px;
  2043. }
  2044. .mobile .narrow.small .site .name {
  2045. width: 186px;
  2046. }
  2047. .mobile .site.no-name .desc {
  2048. margin-top: -0.625em;
  2049. }
  2050. .mobile .any-icons .site .desc {
  2051. margin-left: 68px;
  2052. }
  2053. .mobile .any-icons.small .site .desc {
  2054. margin-left: 36px;
  2055. }
  2056. .mobile .button_80x15 {
  2057. width: 120px; height: 22.5px;
  2058. }
  2059. .mobile #footer {
  2060. font-size: 1.2em;
  2061. }
  2062. .mobile.device_apple #footer {
  2063. font-size: 0.75em;
  2064. }
  2065. #action_minibar {
  2066. overflow: hidden;
  2067. }
  2068. #action_minibar.horizontal {
  2069. background-image: none;
  2070. }
  2071. #minibar div, #minibar.horizontal {
  2072. margin-top: -1px;
  2073. }
  2074. #minibar a {
  2075. width: 24px; height: 25px;
  2076. margin: 0;
  2077. padding: 4px 4px 0 4px;
  2078. }
  2079. #minibar.horizontal a {
  2080. height: 26px;
  2081. padding-bottom: 5px;
  2082. }
  2083. #minibar a.highlight {
  2084. background: {{\$theme["link_bg"]}};
  2085. }
  2086. #minibar a img {
  2087. margin-top: 4px;
  2088. width: 16px; height: 16px;
  2089. }
  2090. #minibar a img.empty {
  2091. background-image: url("{{qmark_icon()}}");
  2092. background-position: center center;
  2093. background-repeat: no-repeat;
  2094. background-size: 16px 16px;
  2095. }
  2096. #action_minibar.mobile {
  2097. font-size: 1em;
  2098. }
  2099. CSS
  2100. , $namespace, False);
  2101. if ($custom_css) echo "\n\n".tpl($custom_css, $namespace, False);
  2102. } // CSS template and output
  2103. // Helper functions {{{1
  2104. function copyright_year($start = Null, $end = Null) {
  2105. if (!$start) $start = date("Y");
  2106. if (!$end) $end = date("Y");
  2107. if ($start == $end) return $start;
  2108. return $start."-".$end;
  2109. }
  2110. function htmlentitiesu8($s, $encode_twice = False) {
  2111. if ($encode_twice) $s = htmlentitiesu8($s, False);
  2112. return htmlentities($s, ENT_COMPAT, "UTF-8");
  2113. }
  2114. function htmlspecialcharsu8($s, $encode_twice = False) {
  2115. if ($encode_twice) $s = htmlspecialcharsu8($s, False);
  2116. return htmlspecialchars($s, ENT_COMPAT, "UTF-8");
  2117. }
  2118. function htmlsymbols($s, $encode_twice = False) {
  2119. return htmlspecialchars_decode(htmlentitiesu8($s, $encode_twice));
  2120. }
  2121. function indent($s, $n) {
  2122. $s = explode("\n", $s);
  2123. foreach ($s as $i => $l) {
  2124. $s[$i] = str_repeat(" ", $n).$l;
  2125. }
  2126. return implode("\n", $s);
  2127. }
  2128. function tpl($s, $namespace = Null, $esc = True) {
  2129. global $portal;
  2130. if (is_null($namespace)) $namespace = $portal;
  2131. return Templum::templateFromString($s, $esc)->render($namespace);
  2132. }
  2133. function tpl_r($s, $namespace = Null, $esc = True) {
  2134. if (is_array($s)) {
  2135. foreach ($s as $k => &$v) {
  2136. if (is_array($v) || is_string($v))
  2137. $s[$k] = tpl_r($v, $namespace, $esc);
  2138. }
  2139. return $s;
  2140. }
  2141. elseif (is_string($s))
  2142. return tpl($s, $namespace, $esc);
  2143. else
  2144. return $s;
  2145. }
  2146. // Helper functions
  2147. // vim: set fdm=marker: "{{{1
  2148. ?>