PageRenderTime 56ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/library/XenForo/Model/Template.php

https://github.com/hanguyenhuu/DTUI_201105
PHP | 1384 lines | 892 code | 145 blank | 347 comment | 61 complexity | 7f8b50db84998692cd77f16f6709039b MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0, BSD-3-Clause
  1. <?php
  2. /**
  3. * Model for templates
  4. *
  5. * @package XenForo_Templates
  6. */
  7. class XenForo_Model_Template extends XenForo_Model
  8. {
  9. /**
  10. * Returns all templates customized in a style in alphabetical title order
  11. *
  12. * @param integer $styleId Style ID
  13. * @param boolean $basicData If true, gets basic data only
  14. *
  15. * @return array Format: [] => (array) template
  16. */
  17. public function getAllTemplatesInStyle($styleId, $basicData = false)
  18. {
  19. return $this->_getDb()->fetchAll('
  20. SELECT ' . ($basicData ? 'template_id, title, style_id, addon_id' : '*') . '
  21. FROM xf_template
  22. WHERE style_id = ?
  23. ORDER BY title
  24. ', $styleId);
  25. }
  26. /**
  27. * Get the effective template list for a style. "Effective" means a merged/flattened
  28. * system where every valid template has a record.
  29. *
  30. * This only returns data appropriate for a list view (map id, template id, title).
  31. * Template_state is also calculated based on whether this template has been customized.
  32. * State options: default, custom, inherited.
  33. *
  34. * @param integer $styleId
  35. *
  36. * @return array Format: [] => (array) template list info
  37. */
  38. public function getEffectiveTemplateListForStyle($styleId, array $conditions = array(), $fetchOptions = array())
  39. {
  40. $whereClause = $this->prepareTemplateConditions($conditions, $fetchOptions);
  41. return $this->_getDb()->fetchAll('
  42. SELECT template_map.template_map_id,
  43. template_map.style_id AS map_style_id,
  44. template.template_id,
  45. template.title,
  46. addon.addon_id, addon.title AS addonTitle,
  47. IF(template.style_id = 0, \'default\', IF(template.style_id = template_map.style_id, \'custom\', \'inherited\')) AS template_state,
  48. IF(template.style_id = template_map.style_id, 1, 0) AS canDelete
  49. FROM xf_template_map AS template_map
  50. INNER JOIN xf_template AS template ON
  51. (template_map.template_id = template.template_id)
  52. LEFT JOIN xf_addon AS addon ON
  53. (addon.addon_id = template.addon_id)
  54. WHERE template_map.style_id = ?
  55. AND ' . $whereClause . '
  56. ORDER BY template_map.title
  57. ', $styleId);
  58. }
  59. /**
  60. * Prepares conditions for searching templates. Often, this search will
  61. * be done on an effective template set (using the map). Some conditions
  62. * may require this.
  63. *
  64. * @param array $conditions
  65. * @param array $fetchOptions
  66. *
  67. * @return string SQL conditions
  68. */
  69. public function prepareTemplateConditions(array $conditions, array &$fetchOptions)
  70. {
  71. $db = $this->_getDb();
  72. $sqlConditions = array();
  73. if (!empty($conditions['title']))
  74. {
  75. if (is_array($conditions['title']))
  76. {
  77. $sqlConditions[] = 'template.title LIKE ' . XenForo_Db::quoteLike($conditions['title'][0], $conditions['title'][1], $db);
  78. }
  79. else
  80. {
  81. $sqlConditions[] = 'template.title LIKE ' . XenForo_Db::quoteLike($conditions['title'], 'lr', $db);
  82. }
  83. }
  84. if (!empty($conditions['template']))
  85. {
  86. if (is_array($conditions['template']))
  87. {
  88. $sqlConditions[] = 'template.template LIKE ' . XenForo_Db::quoteLike($conditions['template'][0], $conditions['phrase_text'][1], $db);
  89. }
  90. else
  91. {
  92. $sqlConditions[] = 'template.template LIKE ' . XenForo_Db::quoteLike($conditions['template'], 'lr', $db);
  93. }
  94. }
  95. if (!empty($conditions['template_state']))
  96. {
  97. $stateIf = 'IF(template.style_id = 0, \'default\', IF(template.style_id = template_map.style_id, \'custom\', \'inherited\'))';
  98. if (is_array($conditions['template_state']))
  99. {
  100. $sqlConditions[] = $stateIf . ' IN (' . $db->quote($conditions['template_state']) . ')';
  101. }
  102. else
  103. {
  104. $sqlConditions[] = $stateIf . ' = ' . $db->quote($conditions['template_state']);
  105. }
  106. }
  107. return $this->getConditionsForClause($sqlConditions);
  108. }
  109. /**
  110. * Gets all effective templates in a style. "Effective" means a merged/flattened system
  111. * where every valid template has a record.
  112. *
  113. * @param integer $styleId
  114. *
  115. * @return array Format: [] => (array) effective template info
  116. */
  117. public function getAllEffectiveTemplatesInStyle($styleId)
  118. {
  119. return $this->_getDb()->fetchAll('
  120. SELECT template_map.template_map_id,
  121. template_map.style_id AS map_style_id,
  122. template.*
  123. FROM xf_template_map AS template_map
  124. INNER JOIN xf_template AS template ON
  125. (template_map.template_id = template.template_id)
  126. WHERE template_map.style_id = ?
  127. ORDER BY template_map.title
  128. ', $styleId);
  129. }
  130. /**
  131. * Gets style ID/template ID pairs for all styles where the named template
  132. * is modified.
  133. *
  134. * @param string $templateTitle
  135. *
  136. * @return array Format: [style_id] => template_id
  137. */
  138. public function getTemplateIdInStylesByTitle($templateTitle)
  139. {
  140. return $this->_getDb()->fetchPairs('
  141. SELECT style_id, template_id
  142. FROM xf_template
  143. WHERE title = ?
  144. ', $templateTitle);
  145. }
  146. /**
  147. * Gets the effective template in a style by its title. This includes all
  148. * template information and the map ID.
  149. *
  150. * @param string $title
  151. * @param integer $styleId
  152. *
  153. * @return array|false Effective template info.
  154. */
  155. public function getEffectiveTemplateByTitle($title, $styleId)
  156. {
  157. return $this->_getDb()->fetchRow('
  158. SELECT template_map.template_map_id,
  159. template_map.style_id AS map_style_id,
  160. template.*
  161. FROM xf_template_map AS template_map
  162. INNER JOIN xf_template AS template ON
  163. (template.template_id = template_map.template_id)
  164. WHERE template_map.title = ? AND template_map.style_id = ?
  165. ', array($title, $styleId));
  166. }
  167. /**
  168. * Gets effective templates in a style by their titles
  169. *
  170. * @param array $titles
  171. * @param integer $styleId
  172. *
  173. * @return array|false Effective template info
  174. */
  175. public function getEffectiveTemplatesByTitles(array $titles, $styleId)
  176. {
  177. if (empty($titles))
  178. {
  179. return array();
  180. }
  181. return $this->_getDb()->fetchAll('
  182. SELECT template.*
  183. FROM xf_template_map AS template_map
  184. INNER JOIN xf_template AS template ON
  185. (template.template_id = template_map.template_id)
  186. WHERE template_map.title IN(' . $this->_getDb()->quote($titles) . ') AND template_map.style_id = ?
  187. ', array($styleId));
  188. }
  189. /**
  190. * Gets the effective template based on a known map idea. Returns all template
  191. * information and the map ID.
  192. *
  193. * @param integer $templateMapId
  194. *
  195. * @return array|false Effective template info.
  196. */
  197. public function getEffectiveTemplateByMapId($templateMapId)
  198. {
  199. return $this->_getDb()->fetchRow('
  200. SELECT template_map.template_map_id,
  201. template_map.style_id AS map_style_id,
  202. template.*
  203. FROM xf_template_map AS template_map
  204. INNER JOIN xf_template AS template ON
  205. (template.template_id = template_map.template_id)
  206. WHERE template_map.template_map_id = ?
  207. ', $templateMapId);
  208. }
  209. /**
  210. * Gets multiple effective templates based on 1 or more map IDs. Returns all template
  211. * information and the map ID.
  212. *
  213. * @param integery|array $templateMapIds Either one map ID as a scalar or any array of map IDs
  214. *
  215. * @return array Format: [] => (array) effective template info
  216. */
  217. public function getEffectiveTemplatesByMapIds($templateMapIds)
  218. {
  219. if (!is_array($templateMapIds))
  220. {
  221. $templateMapIds = array($templateMapIds);
  222. }
  223. if (!$templateMapIds)
  224. {
  225. return array();
  226. }
  227. $db = $this->_getDb();
  228. return $db->fetchAll('
  229. SELECT template_map.template_map_id,
  230. template_map.style_id AS map_style_id,
  231. template.*
  232. FROM xf_template_map AS template_map
  233. INNER JOIN xf_template AS template ON
  234. (template.template_id = template_map.template_id)
  235. WHERE template_map.template_map_id IN (' . $db->quote($templateMapIds) . ')
  236. ');
  237. }
  238. /**
  239. * Returns the template specified by template_id
  240. *
  241. * @param integer $templateId Template ID
  242. *
  243. * @return array|false Template
  244. */
  245. public function getTemplateById($templateId)
  246. {
  247. return $this->_getDb()->fetchRow('
  248. SELECT *
  249. FROM xf_template
  250. WHERE template_id = ?
  251. ', $templateId);
  252. }
  253. /**
  254. * Fetches a template from a particular style based on its title.
  255. * Note that if a version of the requested template does not exist
  256. * in the specified style, nothing will be returned.
  257. *
  258. * @param string Title
  259. * @param integer Style ID (defaults to master style)
  260. *
  261. * @return array
  262. */
  263. public function getTemplateInStyleByTitle($title, $styleId = 0)
  264. {
  265. return $this->_getDb()->fetchRow('
  266. SELECT *
  267. FROM xf_template
  268. WHERE title = ?
  269. AND style_id = ?
  270. ', array($title, $styleId));
  271. }
  272. /**
  273. * Fetches templates from a particular style based on their titles.
  274. * Note that if a version of the requested template does not exist
  275. * in the specified style, nothing will be returned for it.
  276. *
  277. * @param array $titles List of titles
  278. * @param integer $styleId Style ID (defaults to master style)
  279. *
  280. * @return array Format: [title] => info
  281. */
  282. public function getTemplatesInStyleByTitles(array $titles, $styleId = 0)
  283. {
  284. if (!$titles)
  285. {
  286. return array();
  287. }
  288. return $this->fetchAllKeyed('
  289. SELECT *
  290. FROM xf_template
  291. WHERE title IN (' . $this->_getDb()->quote($titles) . ')
  292. AND style_id = ?
  293. ', 'title', $styleId);
  294. }
  295. /**
  296. * Gets all templates that are outdated (master version edited more recently).
  297. * Does not include contents of template.
  298. *
  299. * @return array [template id] => template info, including master_version_string
  300. */
  301. public function getOutdatedTemplates()
  302. {
  303. return $this->fetchAllKeyed('
  304. SELECT template.template_id, template.title, template.style_id,
  305. template.addon_id, template.version_id, template.version_string,
  306. master.version_string AS master_version_string
  307. FROM xf_template AS template
  308. INNER JOIN xf_template AS master ON (master.title = template.title AND master.style_id = 0)
  309. INNER JOIN xf_style AS style ON (style.style_id = template.style_id)
  310. WHERE template.style_id > 0
  311. AND master.version_id > template.version_id
  312. ', 'template_id');
  313. }
  314. /**
  315. * Returns all the templates that belong to the specified add-on.
  316. *
  317. * @param string $addOnId
  318. *
  319. * @return array Format: [title] => info
  320. */
  321. public function getMasterTemplatesInAddOn($addOnId)
  322. {
  323. return $this->fetchAllKeyed('
  324. SELECT *
  325. FROM xf_template
  326. WHERE addon_id = ?
  327. AND style_id = 0
  328. ORDER BY title ASC
  329. ', 'title', $addOnId);
  330. }
  331. /**
  332. * Gets the template map IDs of any templates that include the source
  333. * map IDs. For example, this would pass in the map ID of _header
  334. * and get the map ID of the PAGE_CONTAINER.
  335. *
  336. * @param integer|array $mapIds One map ID as a scalar or an array of many.
  337. *
  338. * @return array Array of map IDs
  339. */
  340. public function getIncludingTemplateMapIds($mapIds)
  341. {
  342. if (!is_array($mapIds))
  343. {
  344. $mapIds = array($mapIds);
  345. }
  346. if (!$mapIds)
  347. {
  348. return array();
  349. }
  350. $db = $this->_getDb();
  351. return $db->fetchCol('
  352. SELECT source_map_id
  353. FROM xf_template_include
  354. WHERE target_map_id IN (' . $db->quote($mapIds) . ')
  355. ');
  356. }
  357. /**
  358. * Gets the template map information for all templates that are mapped
  359. * to the specified template ID.
  360. *
  361. * @param integer $templateId
  362. *
  363. * @return array Format: [] => (array) template map info
  364. */
  365. public function getMappedTemplatesByTemplateId($templateId)
  366. {
  367. return $this->_getDb()->fetchAll('
  368. SELECT *
  369. FROM xf_template_map
  370. WHERE template_id = ?
  371. ', $templateId);
  372. }
  373. /**
  374. * Gets mapped template information from the parent style of the named
  375. * template. If the named style is 0 (or invalid), returns false.
  376. *
  377. * @param string $title
  378. * @param integer $styleId
  379. *
  380. * @return array|false
  381. */
  382. public function getParentMappedTemplateByTitle($title, $styleId)
  383. {
  384. if ($styleId == 0)
  385. {
  386. return false;
  387. }
  388. return $this->_getDb()->fetchRow('
  389. SELECT parent_template_map.*
  390. FROM xf_template_map AS template_map
  391. INNER JOIN xf_style AS style ON
  392. (template_map.style_id = style.style_id)
  393. INNER JOIN xf_template_map AS parent_template_map ON
  394. (parent_template_map.style_id = style.parent_id AND parent_template_map.title = template_map.title)
  395. WHERE template_map.title = ? AND template_map.style_id = ?
  396. ', array($title, $styleId));
  397. }
  398. /**
  399. * Gets the list of all template map IDs that include the named phrase.
  400. *
  401. * @param string $phraseTitle
  402. *
  403. * @return array List of template map IDs
  404. */
  405. public function getTemplateMapIdsThatIncludePhrase($phraseTitle)
  406. {
  407. return $this->_getDb()->fetchCol('
  408. SELECT template_map_id
  409. FROM xf_template_phrase
  410. WHERE phrase_title = ?
  411. ', $phraseTitle);
  412. }
  413. /**
  414. * Returns the path to the template development directory, if it has been configured and exists
  415. *
  416. * @return string Path to templates directory
  417. */
  418. public function getTemplateDevelopmentDirectory()
  419. {
  420. $config = XenForo_Application::get('config');
  421. if (!$config->debug || !$config->development->directory)
  422. {
  423. return '';
  424. }
  425. return XenForo_Application::getInstance()->getRootDir()
  426. . '/' . $config->development->directory . '/file_output/templates';
  427. }
  428. /**
  429. * Checks that the templates directory has been configured and exists
  430. *
  431. * @return boolean
  432. */
  433. public function canImportTemplatesFromDevelopment()
  434. {
  435. $dir = $this->getTemplateDevelopmentDirectory();
  436. return ($dir && is_dir($dir));
  437. }
  438. /**
  439. * Deletes the templates that belong to the specified add-on.
  440. *
  441. * @param string $addOnId
  442. */
  443. public function deleteTemplatesForAddOn($addOnId)
  444. {
  445. $db = $this->_getDb();
  446. $db->query('
  447. DELETE FROM xf_template_include
  448. WHERE source_map_id IN (
  449. SELECT template_map_id
  450. FROM xf_template AS template
  451. INNER JOIN xf_template_map AS template_map ON
  452. (template.template_id = template_map.template_id)
  453. WHERE template.style_id = 0
  454. AND template.addon_id = ?
  455. )
  456. ', $addOnId);
  457. $db->query('
  458. DELETE FROM xf_template_phrase
  459. WHERE template_map_id IN (
  460. SELECT template_map_id
  461. FROM xf_template AS template
  462. INNER JOIN xf_template_map AS template_map ON
  463. (template.template_id = template_map.template_id)
  464. WHERE template.style_id = 0
  465. AND template.addon_id = ?
  466. )
  467. ', $addOnId);
  468. $db->query('
  469. DELETE FROM xf_template_map
  470. WHERE template_id IN (
  471. SELECT template_id
  472. FROM xf_template
  473. WHERE style_id = 0
  474. AND addon_id = ?
  475. )
  476. ', $addOnId);
  477. $db->query('
  478. DELETE FROM xf_template_compiled
  479. WHERE style_id = 0
  480. AND title IN (
  481. SELECT title
  482. FROM xf_template
  483. WHERE style_id = 0
  484. AND addon_id = ?
  485. )
  486. ', $addOnId);
  487. $db->delete('xf_template', 'style_id = 0 AND addon_id = ' . $db->quote($addOnId));
  488. XenForo_Template_Compiler::resetTemplateCache();
  489. }
  490. public function deleteTemplatesInStyle($styleId)
  491. {
  492. $db = $this->_getDb();
  493. $db->query('
  494. DELETE FROM xf_template_include
  495. WHERE source_map_id IN (
  496. SELECT template_map_id
  497. FROM xf_template AS template
  498. INNER JOIN xf_template_map AS template_map ON
  499. (template.template_id = template_map.template_id)
  500. WHERE template.style_id = ?
  501. )
  502. ', $styleId);
  503. $db->query('
  504. DELETE FROM xf_template_phrase
  505. WHERE template_map_id IN (
  506. SELECT template_map_id
  507. FROM xf_template AS template
  508. INNER JOIN xf_template_map AS template_map ON
  509. (template.template_id = template_map.template_id)
  510. WHERE template.style_id = ?
  511. )
  512. ', $styleId);
  513. $db->query('
  514. DELETE FROM xf_template_map
  515. WHERE template_id IN (
  516. SELECT template_id
  517. FROM xf_template
  518. WHERE style_id = ?
  519. )
  520. ', $styleId);
  521. $db->query('
  522. DELETE FROM xf_template_compiled
  523. WHERE style_id = 0
  524. AND title IN (
  525. SELECT title
  526. FROM xf_template
  527. WHERE style_id = ?
  528. )
  529. ', $styleId);
  530. $db->delete('xf_template', 'style_id = ' . $db->quote($styleId));
  531. XenForo_Template_Compiler::resetTemplateCache();
  532. }
  533. /**
  534. * Imports all templates from the templates directory into the database
  535. */
  536. public function importTemplatesFromDevelopment()
  537. {
  538. $db = $this->_getDb();
  539. $templateDir = $this->getTemplateDevelopmentDirectory();
  540. if (!$templateDir && !is_dir($templateDir))
  541. {
  542. throw new XenForo_Exception("Template development directory not enabled or doesn't exist");
  543. }
  544. $files = glob("$templateDir/*.html");
  545. if (!$files)
  546. {
  547. throw new XenForo_Exception("Template development directory does not have any templates");
  548. }
  549. $metaData = XenForo_Helper_DevelopmentXml::readMetaDataFile($templateDir . '/_metadata.xml');
  550. XenForo_Db::beginTransaction($db);
  551. $this->deleteTemplatesForAddOn('XenForo');
  552. $titles = array();
  553. foreach ($files AS $templateFile)
  554. {
  555. $filename = basename($templateFile);
  556. if (preg_match('/^(.+)\.html$/', $filename, $match))
  557. {
  558. $titles[] = $match[1];
  559. }
  560. }
  561. $existingTemplates = $this->getTemplatesInStyleByTitles($titles, 0);
  562. foreach ($files AS $templateFile)
  563. {
  564. if (!is_readable($templateFile))
  565. {
  566. throw new XenForo_Exception("Template file '$templateFile' not readable");
  567. }
  568. $filename = basename($templateFile);
  569. if (preg_match('/^(.+)\.html$/', $filename, $match))
  570. {
  571. $templateName = $match[1];
  572. $data = file_get_contents($templateFile);
  573. $dw = XenForo_DataWriter::create('XenForo_DataWriter_Template');
  574. if (isset($existingTemplates[$templateName]))
  575. {
  576. $dw->setExistingData($existingTemplates[$templateName], true);
  577. }
  578. $dw->setOption(XenForo_DataWriter_Template::OPTION_DEV_OUTPUT_DIR, '');
  579. $dw->setOption(XenForo_DataWriter_Template::OPTION_FULL_COMPILE, false);
  580. $dw->setOption(XenForo_DataWriter_Template::OPTION_TEST_COMPILE, false);
  581. $dw->setOption(XenForo_DataWriter_Template::OPTION_CHECK_DUPLICATE, false);
  582. $dw->setOption(XenForo_DataWriter_Template::OPTION_REBUILD_TEMPLATE_MAP, false);
  583. $dw->bulkSet(array(
  584. 'style_id' => 0,
  585. 'title' => $templateName,
  586. 'template' => $data,
  587. 'addon_id' => 'XenForo'
  588. ));
  589. if (isset($metaData[$templateName]))
  590. {
  591. $dw->bulkSet($metaData[$templateName]);
  592. }
  593. $dw->save();
  594. }
  595. }
  596. XenForo_Db::commit($db);
  597. }
  598. /**
  599. * Imports the add-on templates XML.
  600. *
  601. * @param SimpleXMLElement $xml XML element pointing to the root of the data
  602. * @param string $addOnId Add-on to import for
  603. * @param integer $maxExecution Maximum run time in seconds
  604. * @param integer $offset Number of elements to skip
  605. *
  606. * @return boolean|integer True on completion; false if the XML isn't correct; integer otherwise with new offset value
  607. */
  608. public function importTemplatesAddOnXml(SimpleXMLElement $xml, $addOnId, $maxExecution = 0, $offset = 0)
  609. {
  610. $db = $this->_getDb();
  611. XenForo_Db::beginTransaction($db);
  612. $startTime = microtime(true);
  613. if ($offset == 0)
  614. {
  615. $this->deleteTemplatesForAddOn($addOnId);
  616. }
  617. $templates = XenForo_Helper_DevelopmentXml::fixPhpBug50670($xml->template);
  618. $titles = array();
  619. $current = 0;
  620. foreach ($templates AS $template)
  621. {
  622. $current++;
  623. if ($current <= $offset)
  624. {
  625. continue;
  626. }
  627. $titles[] = (string)$template['title'];
  628. }
  629. $existingTemplates = $this->getTemplatesInStyleByTitles($titles, 0);
  630. $current = 0;
  631. $restartOffset = false;
  632. foreach ($templates AS $template)
  633. {
  634. $current++;
  635. if ($current <= $offset)
  636. {
  637. continue;
  638. }
  639. $templateName = (string)$template['title'];
  640. $dw = XenForo_DataWriter::create('XenForo_DataWriter_Template');
  641. if (isset($existingTemplates[$templateName]))
  642. {
  643. $dw->setExistingData($existingTemplates[$templateName], true);
  644. }
  645. $dw->setOption(XenForo_DataWriter_Template::OPTION_DEV_OUTPUT_DIR, '');
  646. $dw->setOption(XenForo_DataWriter_Template::OPTION_FULL_COMPILE, false);
  647. $dw->setOption(XenForo_DataWriter_Template::OPTION_TEST_COMPILE, false);
  648. $dw->setOption(XenForo_DataWriter_Template::OPTION_CHECK_DUPLICATE, false);
  649. $dw->setOption(XenForo_DataWriter_Template::OPTION_REBUILD_TEMPLATE_MAP, false);
  650. $dw->bulkSet(array(
  651. 'style_id' => 0,
  652. 'title' => $templateName,
  653. 'template' => (string)$template,
  654. 'addon_id' => $addOnId,
  655. 'version_id' => (int)$template['version_id'],
  656. 'version_string' => (string)$template['version_string']
  657. ));
  658. $dw->save();
  659. if ($maxExecution && (microtime(true) - $startTime) > $maxExecution)
  660. {
  661. $restartOffset = $current;
  662. break;
  663. }
  664. }
  665. XenForo_Db::commit($db);
  666. return ($restartOffset ? $restartOffset : true);
  667. }
  668. /**
  669. * Imports templates into a given style. Note that this assumes the style is already empty.
  670. * It does not check for conflicts.
  671. *
  672. * @param SimpleXMLElement $xml
  673. * @param integer $styleId
  674. */
  675. public function importTemplatesStyleXml(SimpleXMLElement $xml, $styleId)
  676. {
  677. $db = $this->_getDb();
  678. if ($xml->template === null)
  679. {
  680. return;
  681. }
  682. XenForo_Db::beginTransaction($db);
  683. foreach ($xml->template AS $template)
  684. {
  685. $templateName = (string)$template['title'];
  686. $dw = XenForo_DataWriter::create('XenForo_DataWriter_Template');
  687. $dw->setOption(XenForo_DataWriter_Template::OPTION_DEV_OUTPUT_DIR, '');
  688. $dw->setOption(XenForo_DataWriter_Template::OPTION_FULL_COMPILE, false);
  689. $dw->setOption(XenForo_DataWriter_Template::OPTION_TEST_COMPILE, false);
  690. $dw->setOption(XenForo_DataWriter_Template::OPTION_CHECK_DUPLICATE, false);
  691. $dw->setOption(XenForo_DataWriter_Template::OPTION_REBUILD_TEMPLATE_MAP, false);
  692. $dw->bulkSet(array(
  693. 'style_id' => $styleId,
  694. 'title' => (string)$template['title'],
  695. 'template' => (string)$template,
  696. 'addon_id' => (string)$template['addon_id'],
  697. 'version_id' => (int)$template['version_id'],
  698. 'version_string' => (string)$template['version_string']
  699. ));
  700. $dw->save();
  701. }
  702. XenForo_Db::commit($db);
  703. }
  704. /**
  705. * Appends the add-on template XML to a given DOM element.
  706. *
  707. * @param DOMElement $rootNode Node to append all elements to
  708. * @param string $addOnId Add-on ID to be exported
  709. */
  710. public function appendTemplatesAddOnXml(DOMElement $rootNode, $addOnId)
  711. {
  712. $document = $rootNode->ownerDocument;
  713. $templates = $this->getMasterTemplatesInAddOn($addOnId);
  714. foreach ($templates AS $template)
  715. {
  716. $templateNode = $document->createElement('template');
  717. $templateNode->setAttribute('title', $template['title']);
  718. $templateNode->setAttribute('version_id', $template['version_id']);
  719. $templateNode->setAttribute('version_string', $template['version_string']);
  720. $templateNode->appendChild($document->createCDATASection($template['template']));
  721. $rootNode->appendChild($templateNode);
  722. }
  723. }
  724. /**
  725. * Appends the template XML for templates in the specified style.
  726. *
  727. * @param DOMElement $rootNode
  728. * @param integer $styleId
  729. */
  730. public function appendTemplatesStyleXml(DOMElement $rootNode, $styleId)
  731. {
  732. $document = $rootNode->ownerDocument;
  733. $templates = $this->getAllTemplatesInStyle($styleId);
  734. foreach ($templates AS $template)
  735. {
  736. $templateNode = $document->createElement('template');
  737. $templateNode->setAttribute('title', $template['title']);
  738. $templateNode->setAttribute('addon_id', $template['addon_id']);
  739. $templateNode->setAttribute('version_id', $template['version_id']);
  740. $templateNode->setAttribute('version_string', $template['version_string']);
  741. $templateNode->appendChild($document->createCDATASection($template['template']));
  742. $rootNode->appendChild($templateNode);
  743. }
  744. }
  745. /**
  746. * Gets the templates development XML.
  747. *
  748. * @return DOMDocument
  749. */
  750. public function getTemplatesDevelopmentXml()
  751. {
  752. $document = new DOMDocument('1.0', 'utf-8');
  753. $document->formatOutput = true;
  754. $rootNode = $document->createElement('templates');
  755. $document->appendChild($rootNode);
  756. $this->appendTemplatesAddOnXml($rootNode, 'XenForo');
  757. return $document;
  758. }
  759. /**
  760. * Recompiles all templates.
  761. *
  762. * @param integer $maxExecution The approx maximum length of time this function will run for
  763. * @param integer $startStyle The ID of the style to start with
  764. * @param integer $startTemplate The number of the template to start with in that style (not ID, just counter)
  765. *
  766. * @return boolean|array True if completed successfull, otherwise array of where to restart (values start style ID, start template counter)
  767. */
  768. public function compileAllTemplates($maxExecution = 0, $startStyle = 0, $startTemplate = 0)
  769. {
  770. $db = $this->_getDb();
  771. $styles = $this->getModelFromCache('XenForo_Model_Style')->getAllStyles();
  772. $styleIds = array_merge(array(0), array_keys($styles));
  773. sort($styleIds);
  774. $lastStyle = 0;
  775. $startTime = microtime(true);
  776. $complete = true;
  777. XenForo_Db::beginTransaction($db);
  778. if ($startStyle == 0 && $startTemplate == 0)
  779. {
  780. $db->query('DELETE FROM xf_template_compiled');
  781. }
  782. foreach ($styleIds AS $styleId)
  783. {
  784. if ($styleId < $startStyle)
  785. {
  786. continue;
  787. }
  788. $lastStyle = $styleId;
  789. $lastTemplate = 0;
  790. $templates = $this->getAllTemplatesInStyle($styleId, true);
  791. foreach ($templates AS $key => $template)
  792. {
  793. $lastTemplate++;
  794. if ($styleId == $startStyle && $lastTemplate < $startTemplate)
  795. {
  796. continue;
  797. }
  798. $this->compileNamedTemplateInStyleTree($template['title'], $template['style_id']);
  799. if ($maxExecution && (microtime(true) - $startTime) > $maxExecution)
  800. {
  801. $complete = false;
  802. break 2;
  803. }
  804. }
  805. }
  806. if ($complete)
  807. {
  808. $this->getModelFromCache('XenForo_Model_Style')->updateAllStylesLastModifiedDate();
  809. }
  810. XenForo_Db::commit($db);
  811. if ($complete)
  812. {
  813. return true;
  814. }
  815. else
  816. {
  817. return array($lastStyle, $lastTemplate + 1);
  818. }
  819. }
  820. /**
  821. * Compiles the named template in the style tree. Any child templates that
  822. * use this template will be recompiled as well.
  823. *
  824. * @param string $title
  825. * @param integer $styleId
  826. *
  827. * @return array A list of template map IDs that were compiled
  828. */
  829. public function compileNamedTemplateInStyleTree($title, $styleId)
  830. {
  831. $parsedRecord = $this->getEffectiveTemplateByTitle($title, $styleId);
  832. if (!$parsedRecord)
  833. {
  834. return array();
  835. }
  836. return $this->compileTemplateInStyleTree($parsedRecord);
  837. }
  838. /**
  839. * Compiles the list of template map IDs and any child templates that are using
  840. * the same core template.
  841. *
  842. * @param integer|array $templateMapIds One map ID as a scalar or many as an array
  843. *
  844. * @return array A list of template map IDs that were compiled
  845. */
  846. public function compileMappedTemplatesInStyleTree($templateMapIds)
  847. {
  848. $templates = $this->getEffectiveTemplatesByMapIds($templateMapIds);
  849. $mapIds = array();
  850. foreach ($templates AS $template)
  851. {
  852. $mapIds = array_merge($mapIds, $this->compileTemplateInStyleTree($template));
  853. }
  854. return $mapIds;
  855. }
  856. /**
  857. * Compiles the specified template data in the style tree. This compiles this template
  858. * in any style that is actually using this template.
  859. *
  860. * @param array $parsedRecord Full template information
  861. *
  862. * @return array List of template map IDs that were compiled
  863. */
  864. public function compileTemplateInStyleTree(array $parsedRecord)
  865. {
  866. $parsedTemplate = unserialize($parsedRecord['template_parsed']);
  867. $dependentTemplates = array();
  868. $styles = $this->getMappedTemplatesByTemplateId($parsedRecord['template_id']);
  869. foreach ($styles AS $compileStyle)
  870. {
  871. $this->compileAndInsertParsedTemplate(
  872. $compileStyle['template_map_id'],
  873. $parsedTemplate,
  874. $parsedRecord['title'],
  875. $compileStyle['style_id']
  876. );
  877. $dependentTemplates[] = $compileStyle['template_map_id'];
  878. }
  879. return $dependentTemplates;
  880. }
  881. /**
  882. * Compiles and inserts the specified effective templates.
  883. *
  884. * @param array $templates Array of effective template info
  885. */
  886. public function compileAndInsertEffectiveTemplates(array $templates)
  887. {
  888. foreach ($templates AS $template)
  889. {
  890. $this->compileAndInsertParsedTemplate(
  891. $template['template_map_id'],
  892. unserialize($template['template_parsed']),
  893. $template['title'],
  894. isset($template['map_style_id']) ? $template['map_style_id'] : $template['style_id']
  895. );
  896. }
  897. }
  898. /**
  899. * Recompiles all templates that include the named phrase.
  900. *
  901. * @param string $phraseTitle
  902. *
  903. * @return array List of template map IDs that were compiled
  904. */
  905. public function compileTemplatesThatIncludePhrase($phraseTitle)
  906. {
  907. $mapIds = $this->getTemplateMapIdsThatIncludePhrase($phraseTitle);
  908. return $this->compileMappedTemplatesInStyleTree($mapIds);
  909. }
  910. /**
  911. * Compiles the specified parsed template and updates the compiled table
  912. * and included templates list.
  913. *
  914. * @param integer $templateMapId The map ID of the template being compiled (for includes)
  915. * @param string|array $parsedTemplate Parsed form of the template
  916. * @param string $title Title of the template
  917. * @param integer $compileStyleId Style ID of the template
  918. */
  919. public function compileAndInsertParsedTemplate($templateMapId, $parsedTemplate, $title, $compileStyleId)
  920. {
  921. $isCss = (substr($title, -4) == '.css');
  922. if (!$compileStyleId && !$isCss)
  923. {
  924. return; // skip compiling master templates, but compile css we need it
  925. }
  926. $compiler = new XenForo_Template_Compiler('');
  927. $languages = $this->getModelFromCache('XenForo_Model_Language')->getAllLanguages();
  928. $db = $this->_getDb();
  929. if ($isCss)
  930. {
  931. $compiledTemplate = $compiler->compileParsed($parsedTemplate, $title, $compileStyleId, 0);
  932. $db->query('
  933. INSERT INTO xf_template_compiled
  934. (style_id, language_id, title, template_compiled)
  935. VALUES
  936. (?, ?, ?, ?)
  937. ON DUPLICATE KEY UPDATE template_compiled = VALUES(template_compiled)
  938. ', array($compileStyleId, 0, $title, $compiledTemplate));
  939. }
  940. else
  941. {
  942. foreach ($languages AS $language)
  943. {
  944. $compiledTemplate = $compiler->compileParsed($parsedTemplate, $title, $compileStyleId, $language['language_id']);
  945. $db->query('
  946. INSERT INTO xf_template_compiled
  947. (style_id, language_id, title, template_compiled)
  948. VALUES
  949. (?, ?, ?, ?)
  950. ON DUPLICATE KEY UPDATE template_compiled = VALUES(template_compiled)
  951. ', array($compileStyleId, $language['language_id'], $title, $compiledTemplate));
  952. }
  953. }
  954. $db->delete('xf_template_include', 'source_map_id = ' . $db->quote($templateMapId));
  955. foreach ($compiler->getIncludedTemplates() AS $includedMapId)
  956. {
  957. $db->insert('xf_template_include', array(
  958. 'source_map_id' => $templateMapId,
  959. 'target_map_id' => $includedMapId
  960. ));
  961. //TODO: this system doesn't handle includes for templates that don't exist yet
  962. }
  963. $db->delete('xf_template_phrase', 'template_map_id = ' . $db->quote($templateMapId));
  964. foreach ($compiler->getIncludedPhrases() AS $includedPhrase)
  965. {
  966. $db->insert('xf_template_phrase', array(
  967. 'template_map_id' => $templateMapId,
  968. 'phrase_title' => $includedPhrase
  969. ));
  970. }
  971. }
  972. /**
  973. * Determines if the visiting user can modify a template in the specified style.
  974. * If debug mode is not enabled, users can't modify templates in the master style.
  975. *
  976. * @param integer $styleId
  977. *
  978. * @return boolean
  979. */
  980. public function canModifyTemplateInStyle($styleId)
  981. {
  982. return ($styleId != 0 || XenForo_Application::debugMode());
  983. }
  984. /**
  985. * Builds (and inserts) the template map for a specified template, from
  986. * the root of the style tree.
  987. *
  988. * @param string $title Title of the template being build
  989. * @param array $data Injectable data. Supports styleTree and styleTemplateMap.
  990. */
  991. public function buildTemplateMap($title, array $data = array())
  992. {
  993. if (!isset($data['styleTree']))
  994. {
  995. /* @var $styleModel XenForo_Model_Style */
  996. $styleModel = $this->getModelFromCache('XenForo_Model_Style');
  997. $data['styleTree'] = $styleModel->getStyleTreeAssociations($styleModel->getAllStyles());
  998. }
  999. if (!isset($data['styleTemplateMap']))
  1000. {
  1001. $data['styleTemplateMap'] = $this->getTemplateIdInStylesByTitle($title);
  1002. }
  1003. $mapUpdates = $this->findTemplateMapUpdates(0, $data['styleTree'], $data['styleTemplateMap']);
  1004. if ($mapUpdates)
  1005. {
  1006. $db = $this->_getDb();
  1007. $toDeleteInStyleIds = array();
  1008. foreach ($mapUpdates AS $styleId => $newTemplateId)
  1009. {
  1010. if ($newTemplateId == 0)
  1011. {
  1012. $toDeleteInStyleIds[] = $styleId;
  1013. continue;
  1014. }
  1015. $db->query('
  1016. INSERT INTO xf_template_map
  1017. (style_id, title, template_id)
  1018. VALUES
  1019. (?, ?, ?)
  1020. ON DUPLICATE KEY UPDATE
  1021. template_id = ?
  1022. ', array($styleId, $title, $newTemplateId, $newTemplateId));
  1023. }
  1024. if ($toDeleteInStyleIds)
  1025. {
  1026. $db->delete('xf_template_map',
  1027. 'title = ' . $db->quote($title) . ' AND style_id IN (' . $db->quote($toDeleteInStyleIds) . ')'
  1028. );
  1029. $db->delete('xf_template_compiled',
  1030. 'title = ' . $db->quote($title) . ' AND style_id IN (' . $db->quote($toDeleteInStyleIds) . ')'
  1031. );
  1032. }
  1033. }
  1034. }
  1035. /**
  1036. * Finds the necessary template map updates for the specified template within the
  1037. * sub-tree.
  1038. *
  1039. * If {$defaultTemplateId} is non-0, a return entry will be inserted for {$parentId}.
  1040. *
  1041. * @param integer $parentId Parent of the style sub-tree to search.
  1042. * @param array $styleTree Tree of styles
  1043. * @param array $styleTemplateMap List of styleId => templateId pairs for the places where this template has been customized.
  1044. * @param integer $defaultTemplateId The default template ID that non-customized template in the sub-tree should get.
  1045. *
  1046. * @return array Format: [style id] => [effective template id]
  1047. */
  1048. public function findTemplateMapUpdates($parentId, array $styleTree, array $styleTemplateMap, $defaultTemplateId = 0)
  1049. {
  1050. $output = array();
  1051. if (isset($styleTemplateMap[$parentId]))
  1052. {
  1053. $defaultTemplateId = $styleTemplateMap[$parentId];
  1054. }
  1055. $output[$parentId] = $defaultTemplateId;
  1056. if (!isset($styleTree[$parentId]))
  1057. {
  1058. return $output;
  1059. }
  1060. foreach ($styleTree[$parentId] AS $styleId)
  1061. {
  1062. $output += $this->findTemplateMapUpdates($styleId, $styleTree, $styleTemplateMap, $defaultTemplateId);
  1063. }
  1064. return $output;
  1065. }
  1066. /**
  1067. * Inserts the template map records for all elements of various styles.
  1068. *
  1069. * @param array $styleMapList Format: [style id][title] => template id
  1070. * @param bolean $truncate If true, all map data is truncated (quicker that way)
  1071. */
  1072. public function insertTemplateMapForStyles(array $styleMapList, $truncate = false)
  1073. {
  1074. $db = $this->_getDb();
  1075. XenForo_Db::beginTransaction($db);
  1076. if ($truncate)
  1077. {
  1078. $db->query('TRUNCATE TABLE xf_template_map');
  1079. }
  1080. foreach ($styleMapList AS $builtStyleId => $map)
  1081. {
  1082. if (!$truncate)
  1083. {
  1084. $db->delete('xf_template_map', 'style_id = ' . $db->quote($builtStyleId));
  1085. }
  1086. foreach ($map AS $title => $templateId)
  1087. {
  1088. $db->insert('xf_template_map', array(
  1089. 'style_id' => $builtStyleId,
  1090. 'title' => $title,
  1091. 'template_id' => $templateId
  1092. ));
  1093. }
  1094. }
  1095. XenForo_Db::commit($db);
  1096. }
  1097. /**
  1098. * Builds the full template map data for an entire style sub-tree.
  1099. *
  1100. * @param integer $styleId Starting style. This style and all children will be built.
  1101. *
  1102. * @return array Format: [style id][title] => template id
  1103. */
  1104. public function buildTemplateMapForStyleTree($styleId)
  1105. {
  1106. /* @var $styleModel XenForo_Model_Style */
  1107. $styleModel = $this->getModelFromCache('XenForo_Model_Style');
  1108. $styles = $styleModel->getAllStyles();
  1109. $styleTree = $styleModel->getStyleTreeAssociations($styles);
  1110. $styles[0] = true;
  1111. if ($styleId && !isset($styles[$styleId]))
  1112. {
  1113. return array();
  1114. }
  1115. $map = array();
  1116. if ($styleId)
  1117. {
  1118. $style = $styles[$styleId];
  1119. $templates = $this->getEffectiveTemplateListForStyle($style['parent_id']);
  1120. foreach ($templates AS $template)
  1121. {
  1122. $map[$template['title']] = $template['template_id'];
  1123. }
  1124. }
  1125. return $this->_buildTemplateMapForStyleTree($styleId, $map, $styles, $styleTree);
  1126. }
  1127. /**
  1128. * Internal handler to build the template map data for a style sub-tree.
  1129. * Calls itself recursively.
  1130. *
  1131. * @param integer $styleId Style to build (builds children automatically)
  1132. * @param array $map Base template map data. Format: [title] => template id
  1133. * @param array $styles List of styles
  1134. * @param array $styleTree Style tree
  1135. *
  1136. * @return array Format: [style id][title] => template id
  1137. */
  1138. protected function _buildTemplateMapForStyleTree($styleId, array $map, array $styles, array $styleTree)
  1139. {
  1140. if (!isset($styles[$styleId]))
  1141. {
  1142. return array();
  1143. }
  1144. $customTemplates = $this->getAllTemplatesInStyle($styleId);
  1145. foreach ($customTemplates AS $template)
  1146. {
  1147. $map[$template['title']] = $template['template_id'];
  1148. }
  1149. $output = array($styleId => $map);
  1150. if (isset($styleTree[$styleId]))
  1151. {
  1152. foreach ($styleTree[$styleId] AS $childStyleId)
  1153. {
  1154. $output += $this->_buildTemplateMapForStyleTree($childStyleId, $map, $styles, $styleTree);
  1155. }
  1156. }
  1157. return $output;
  1158. }
  1159. /**
  1160. * Replaces <xen:require/include/edithint with <link rel="xenforo_x"
  1161. * for the purposes of easy WebDAV editing.
  1162. *
  1163. * @param string $templateText
  1164. *
  1165. * @return string
  1166. */
  1167. public static function replaceIncludesWithLinkRel($templateText)
  1168. {
  1169. $search = array(
  1170. '#<xen:require\s+css="([^"]+)"\s*/>#siU'
  1171. => '<link rel="xenforo_stylesheet" type="text/css" href="\1" />',
  1172. '#<xen:edithint\s+template="([^"]+\.css)"\s*/>#siU'
  1173. => '<link rel="xenforo_stylesheet_hint" type="text/css" href="\1" />',
  1174. '#<xen:edithint\s+template="([^"]+)"\s*/>#siU'
  1175. => '<link rel="xenforo_template_hint" type="text/html" href="\1.html" />',
  1176. '#<xen:include\s+template="([^"]+)"(\s*/)?>#siU'
  1177. => '<link rel="xenforo_template" type="text/html" href="\1.html"\2>',
  1178. '#</xen:include>#siU'
  1179. => '</link>',
  1180. );
  1181. return preg_replace(array_keys($search), $search, $templateText);
  1182. }
  1183. /**
  1184. * Replaces <link rel="xenforo_x" with <xen:require/include/edithint
  1185. * for the purposes of easy WebDAV editing.
  1186. *
  1187. * @param string $templateText
  1188. *
  1189. * @return string
  1190. */
  1191. public static function replaceLinkRelWithIncludes($templateText)
  1192. {
  1193. $search = array(
  1194. '#</link>#siU'
  1195. => '</xen:include>',
  1196. '#<link rel="xenforo_template" type="text/html" href="([^"]+)(\.html)?"(\s*/)?>#siU'
  1197. => '<xen:include template="\1"\3>',
  1198. '#<link rel="xenforo_template_hint" type="text/html" href="([^"]+)(\.html)?"\s/>#siU'
  1199. => '<xen:edithint template="\1" />',
  1200. '#<link rel="xenforo_stylesheet_hint" type="text/css" href="([^"]+)"\s*/>#siU'
  1201. => '<xen:edithint template="\1" />',
  1202. '#<link rel="xenforo_stylesheet" type="text/css" href="([^"]+)"\s*/>#siU'
  1203. => '<xen:require css="\1" />'
  1204. );
  1205. return preg_replace(array_keys($search), $search, $templateText);
  1206. }
  1207. /* //TODO:...
  1208. const CSS_MIN = 'minified_css_format';
  1209. const CSS_STANDARD = 'standard_css_format';
  1210. const CSS_EXTENDED = 'extended_xenforo_css_format';
  1211. public static function formatCss($css, $mode = self::CSS_EXTENDED)
  1212. {
  1213. return self::_parseCss($css);
  1214. switch ($mode)
  1215. {
  1216. case self::CSS_MIN:
  1217. break;
  1218. case self::CSS_STANDARD:
  1219. break;
  1220. case self::CSS_EXTENDED:
  1221. default:
  1222. break;
  1223. }
  1224. }*/
  1225. }