PageRenderTime 43ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/phpmyfaq/src/phpMyFAQ/Template.php

http://github.com/thorsten/phpMyFAQ
PHP | 401 lines | 214 code | 46 blank | 141 comment | 31 complexity | 34683b171bfda90d1b3e8073dc64bd14 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-2.1, LGPL-3.0
  1. <?php
  2. /**
  3. * The Template class provides methods and functions for the
  4. * template parser.
  5. * This Source Code Form is subject to the terms of the Mozilla Public License,
  6. * v. 2.0. If a copy of the MPL was not distributed with this file, You can
  7. * obtain one at http://mozilla.org/MPL/2.0/.
  8. *
  9. * @package phpMyFAQ
  10. * @author Thorsten Rinne <thorsten@phpmyfaq.de>
  11. * @author Jan Mergler <jan.mergler@gmx.de>
  12. * @copyright 2002-2021 phpMyFAQ Team
  13. * @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
  14. * @link https://www.phpmyfaq.de
  15. * @since 2002-08-22
  16. */
  17. namespace phpMyFAQ;
  18. use phpMyFAQ\Template\FileNotFoundException;
  19. use phpMyFAQ\Template\TemplateHelper;
  20. /**
  21. * Class Template
  22. *
  23. * @package phpMyFAQ
  24. */
  25. class Template
  26. {
  27. /**
  28. * Name of active template set.
  29. *
  30. * @var string
  31. */
  32. private static $tplSetName;
  33. /**
  34. * The template array.
  35. *
  36. * @var array<string, string>
  37. */
  38. public $templates = [];
  39. /**
  40. * The output array.
  41. *
  42. * @var array<string, string|null>
  43. */
  44. private $outputs = [];
  45. /**
  46. * The blocks array.
  47. *
  48. * @var array<string, array<string>>
  49. */
  50. private $blocks = [];
  51. /**
  52. * array containing the touched blocks.
  53. *
  54. * @var array<string>
  55. */
  56. private $blocksTouched = [];
  57. /** @var array<string> Array containing the errors */
  58. private $errors = [];
  59. /** @var TemplateHelper */
  60. private $tplHelper;
  61. /**
  62. * Combine all template files into the main templates array
  63. *
  64. * @param array<string, string> $myTemplate Templates
  65. * @param TemplateHelper $tplHelper
  66. * @param string $tplSetName Active template name
  67. */
  68. public function __construct(array $myTemplate, TemplateHelper $tplHelper, $tplSetName = 'default')
  69. {
  70. $this->tplHelper = $tplHelper;
  71. self::$tplSetName = $tplSetName;
  72. foreach ($myTemplate as $templateName => $filename) {
  73. try {
  74. $this->templates[$templateName] = $this->readTemplateFile(
  75. 'assets/themes/' . $tplSetName . '/templates/' . $filename,
  76. $templateName
  77. );
  78. } catch (FileNotFoundException $e) {
  79. $this->errors[] = $e->getMessage();
  80. }
  81. }
  82. }
  83. /**
  84. * This function reads a template file.
  85. *
  86. * @param string $filename Filename
  87. * @param string $tplName Name of the template
  88. * @return string
  89. * @throws FileNotFoundException
  90. */
  91. protected function readTemplateFile(string $filename, string $tplName): string
  92. {
  93. if (file_exists($filename) && is_file($filename)) {
  94. $tplContent = file_get_contents($filename);
  95. $this->blocks[$tplName] = $this->readBlocks($tplContent);
  96. return $tplContent;
  97. }
  98. throw new FileNotFoundException('Cannot open the file ' . $filename);
  99. }
  100. /**
  101. * This function reads the block.
  102. *
  103. * @param string $block Block to read
  104. * @return array<string, string>
  105. */
  106. private function readBlocks(string $block): array
  107. {
  108. $tmpBlocks = $tplBlocks = [];
  109. // read all blocks into $tmpBlocks
  110. Strings::preg_match_all('/\[([[:alpha:]]+)\]\s*[\W\w\s\{\{\}\}\<\>\=\"\/]*?\s*\[\/\1\]/', $block, $tmpBlocks);
  111. $unblocked = $block;
  112. if (isset($tmpBlocks)) {
  113. $blockCount = count($tmpBlocks[0]);
  114. for ($i = 0; $i < $blockCount; ++$i) {
  115. $name = '';
  116. // find block name
  117. Strings::preg_match('/\[.+\]/', $tmpBlocks[0][$i], $name);
  118. $name = Strings::preg_replace('/[\[\[\/\]]/', '', $name);
  119. // remove block tags from block
  120. $res = str_replace('[' . $name[0] . ']', '', $tmpBlocks[0][$i]);
  121. $res = str_replace('[/' . $name[0] . ']', '', $res);
  122. $tplBlocks[$name[0]] = $res;
  123. // unblocked content
  124. $unblocked = str_replace($tplBlocks[$name[0]], '', $unblocked);
  125. $unblocked = str_replace('[' . $name[0] . ']', '', $unblocked);
  126. $unblocked = str_replace('[/' . $name[0] . ']', '', $unblocked);
  127. }
  128. $hits = [];
  129. Strings::preg_match_all('/\{\{.+?\}\}/', $unblocked, $hits);
  130. $tplBlocks['unblocked'] = $hits[0];
  131. } else {
  132. // no blocks defined
  133. $tplBlocks = $block;
  134. }
  135. return $tplBlocks;
  136. }
  137. /**
  138. * Get name of the actual template set.
  139. *
  140. * @return string
  141. */
  142. public static function getTplSetName(): string
  143. {
  144. return self::$tplSetName;
  145. }
  146. /**
  147. * Set the template set name to use.
  148. *
  149. * @param string $tplSetName
  150. */
  151. public static function setTplSetName(string $tplSetName): void
  152. {
  153. self::$tplSetName = $tplSetName;
  154. }
  155. /**
  156. * This function merges two templates.
  157. *
  158. * @param string $from Name of the template to include
  159. * @param string $into Name of the new template
  160. */
  161. public function merge(string $from, string $into): void
  162. {
  163. $this->outputs[$into] = str_replace('{{ ' . $from . ' }}', $this->outputs[$from], $this->outputs[$into]);
  164. $this->outputs[$from] = null;
  165. }
  166. /**
  167. * Parses the template.
  168. *
  169. * @param string $templateName Name of the template
  170. * @param array<int, array<string>> $templateContent Content of the template
  171. */
  172. public function parse(string $templateName, array $templateContent): void
  173. {
  174. $tmp = $this->templates[$templateName];
  175. $rawBlocks = $this->readBlocks($tmp);
  176. $filters[$templateName] = $this->readFilters($tmp);
  177. // process blocked content
  178. if (isset($this->blocks[$templateName])) {
  179. foreach ($rawBlocks as $key => $rawBlock) {
  180. if (in_array($key, $this->blocksTouched) && $key !== 'unblocked') {
  181. $tmp = str_replace($rawBlock, $this->blocks[$templateName][$key], $tmp);
  182. $tmp = str_replace('[' . $key . ']', '', $tmp);
  183. $tmp = str_replace('[/' . $key . ']', '', $tmp);
  184. } elseif ($key !== 'unblocked') {
  185. $tmp = str_replace($rawBlock, '', $tmp);
  186. $tmp = str_replace('[' . $key . ']', '', $tmp);
  187. $tmp = str_replace('[/' . $key . ']', '', $tmp);
  188. }
  189. }
  190. }
  191. // process unblocked content
  192. if (isset($this->blocks[$templateName]['unblocked'])) {
  193. $templateContent = $this->checkContent($templateContent);
  194. // @phpstan-ignore-next-line
  195. foreach ($this->blocks[$templateName]['unblocked'] as $tplVar) {
  196. $varName = trim(Strings::preg_replace('/[\{\{\}\}]/', '', $tplVar));
  197. if (isset($templateContent[$varName])) {
  198. $tmp = str_replace($tplVar, $templateContent[$varName], $tmp);
  199. }
  200. }
  201. }
  202. // process filters
  203. if (isset($filters[$templateName])) {
  204. if (count($filters[$templateName])) {
  205. foreach ($filters[$templateName] as $filter) {
  206. $filterMethod = 'render' . ucfirst(key($filter)) . 'Filter';
  207. $filteredVar = $this->tplHelper->$filterMethod(current($filter));
  208. $tmp = str_replace('{{ ' . current($filter) . ' | ' . key($filter) . ' }}', $filteredVar, $tmp);
  209. }
  210. }
  211. }
  212. // add magic variables for each template
  213. $tmp = str_replace('{{ tplSetName }}', self::$tplSetName, $tmp);
  214. if (isset($this->outputs[$templateName])) {
  215. $this->outputs[$templateName] .= $tmp;
  216. } else {
  217. $this->outputs[$templateName] = $tmp;
  218. }
  219. }
  220. /**
  221. * @param string $template
  222. * @return array<int, array<string, string>>
  223. */
  224. private function readFilters(string $template): array
  225. {
  226. $tmpFilter = $tplFilter = [];
  227. Strings::preg_match_all('/\{\{.+?\}\}/', $template, $tmpFilter);
  228. if (isset($tmpFilter)) {
  229. $filterCount = count($tmpFilter[0]);
  230. for ($i = 0; $i < $filterCount; ++$i) {
  231. if (false !== strpos($tmpFilter[0][$i], ' | meta ')) {
  232. $rawFilter = str_replace(['{{', '}}'], '', $tmpFilter[0][$i]);
  233. list($identifier, $filter) = explode('|', $rawFilter);
  234. $tplFilter[] = [trim($filter) => trim($identifier)];
  235. }
  236. }
  237. }
  238. return $tplFilter;
  239. }
  240. /**
  241. * This function checks the content.
  242. *
  243. * @param array<int, string|array<string>> $content Content to check
  244. * @return array<int, string|array<string>>
  245. */
  246. private function checkContent(array $content): array
  247. {
  248. // Security measure: avoid the injection of php/shell-code
  249. $search = ['#<\?php#i', '#\{$\{#', '#<\?#', '#<\%#', '#`#', '#<script[^>]+php#mi'];
  250. $phpPattern1 = '&lt;?php';
  251. $phpPattern2 = '&lt;?';
  252. $replace = [$phpPattern1, '', $phpPattern2, '', ''];
  253. foreach ($content as $var => $val) {
  254. if (is_array($val)) {
  255. foreach ($val as $key => $value) {
  256. $content[$var][$key] = str_replace('`', '&acute;', $value);
  257. $content[$var][$key] = Strings::preg_replace($search, $replace, $value);
  258. }
  259. } else {
  260. $content[$var] = str_replace('`', '&acute;', $content[$var]);
  261. $content[$var] = Strings::preg_replace($search, $replace, $val);
  262. }
  263. }
  264. return $content;
  265. }
  266. /**
  267. * This function renders the whole parsed templates and outputs it.
  268. */
  269. public function render(): void
  270. {
  271. $output = '';
  272. foreach ($this->outputs as $val) {
  273. $output .= str_replace("\n\n", "\n", $val);
  274. }
  275. echo $output;
  276. }
  277. /**
  278. * This function adds two parsed templates.
  279. *
  280. * @param string $from Name of the template to add
  281. * @param string $into Name of the new template
  282. */
  283. public function add(string $from, string $into): void
  284. {
  285. $this->outputs[$into] .= $this->outputs[$from];
  286. $this->outputs[$from] = null;
  287. }
  288. /**
  289. * This function processes the block.
  290. *
  291. * @param string $templateName Name of the template
  292. * @param string $blockName Block name
  293. * @param array<int, array<string>> $blockContent Content of the block
  294. */
  295. public function parseBlock(string $templateName, string $blockName, array $blockContent): void
  296. {
  297. if (isset($this->blocks[$templateName][$blockName])) {
  298. $block = $this->blocks[$templateName][$blockName];
  299. // security check
  300. $blockContent = $this->checkContent($blockContent);
  301. foreach ($blockContent as $var => $val) {
  302. // if array given, multiply block
  303. if (is_array($val)) {
  304. $block = $this->multiplyBlock($this->blocks[$templateName][$blockName], $blockContent);
  305. break;
  306. } else {
  307. $block = str_replace('{{ ' . $var . ' }}', $val, $block);
  308. }
  309. }
  310. $this->blocksTouched[] = $blockName;
  311. $block = str_replace('&acute;', '`', $block);
  312. $this->blocks[$templateName][$blockName] = $block;
  313. }
  314. }
  315. /**
  316. * This function multiplies blocks.
  317. *
  318. * @param string $blockName Block name
  319. * @param array<int, array<string>> $blockContent Content of block
  320. * @return string
  321. */
  322. private function multiplyBlock(string $blockName, array $blockContent): string
  323. {
  324. $replace = $tmpBlock = [];
  325. $multiplyTimes = 0;
  326. // create the replacement array
  327. foreach ($blockContent as $var => $val) {
  328. if (is_array($val) && !$multiplyTimes) {
  329. // the first array in $blockContent defines $multiplyTimes
  330. $multiplyTimes = count($val);
  331. $replace[$var] = $val;
  332. } elseif ((is_array($val) && $multiplyTimes)) {
  333. // check if all further arrays in $blockContent have the same length
  334. if ($multiplyTimes == count($val)) {
  335. $replace[$var] = $val;
  336. } else {
  337. die('Wrong parameter length!');
  338. }
  339. } else {
  340. // multiply strings to $multiplyTimes
  341. for ($i = 0; $i < $multiplyTimes; ++$i) {
  342. $replace[$var][] = $val;
  343. }
  344. }
  345. }
  346. // do the replacement
  347. for ($i = 0; $i < $multiplyTimes; ++$i) {
  348. $tmpBlock[$i] = $blockName;
  349. foreach ($replace as $var => $val) {
  350. $tmpBlock[$i] = str_replace('{{ ' . $var . ' }}', $val[$i], $tmpBlock[$i]);
  351. }
  352. }
  353. return implode('', $tmpBlock);
  354. }
  355. }