PageRenderTime 59ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/library/XenForo/Model/StyleProperty.php

https://github.com/hanguyenhuu/DTUI_201105
PHP | 3183 lines | 2380 code | 287 blank | 516 comment | 295 complexity | a0b35b7e7a56d16fa984fa521a9ea90f MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0, BSD-3-Clause

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * Model for style properties and style property definitions.
  4. * Note that throughout, style ID -1 is the ACP master.
  5. *
  6. * @package XenForo_StyleProperty
  7. */
  8. class XenForo_Model_StyleProperty extends XenForo_Model
  9. {
  10. protected static $_tempProperties = null;
  11. /**
  12. * Gets the specified style property by its ID. Includes
  13. * definition info.
  14. *
  15. * @param integer $id
  16. *
  17. * @return array|false
  18. */
  19. public function getStylePropertyById($id)
  20. {
  21. return $this->_getDb()->fetchRow('
  22. SELECT property_definition.*,
  23. style_property.*
  24. FROM xf_style_property AS style_property
  25. INNER JOIN xf_style_property_definition AS property_definition ON
  26. (property_definition.property_definition_id = style_property.property_definition_id)
  27. WHERE style_property.property_id = ?
  28. ', $id);
  29. }
  30. /**
  31. * Gets the specified style property by its definition ID
  32. * and style ID. Includes definition info.
  33. *
  34. * @param integer $definitionId
  35. * @param integer $styleId
  36. *
  37. * @return array|false
  38. */
  39. public function getStylePropertyByDefinitionAndStyle($definitionId, $styleId)
  40. {
  41. return $this->_getDb()->fetchRow('
  42. SELECT property_definition.*,
  43. style_property.*
  44. FROM xf_style_property AS style_property
  45. INNER JOIN xf_style_property_definition AS property_definition ON
  46. (property_definition.property_definition_id = style_property.property_definition_id)
  47. WHERE style_property.property_definition_id = ?
  48. AND style_property.style_id = ?
  49. ', array($definitionId, $styleId));
  50. }
  51. /**
  52. * Gets all style properties in the specified styles. This only
  53. * includes properties that have been customized or initially defined
  54. * in the specified styles. Includes definition info.
  55. *
  56. * @param array $styleIds
  57. *
  58. * @return array Format: [property id] => info
  59. */
  60. public function getStylePropertiesInStyles(array $styleIds)
  61. {
  62. if (!$styleIds)
  63. {
  64. return array();
  65. }
  66. return $this->fetchAllKeyed('
  67. SELECT property_definition.*,
  68. style_property.*
  69. FROM xf_style_property AS style_property
  70. INNER JOIN xf_style_property_definition AS property_definition ON
  71. (property_definition.property_definition_id = style_property.property_definition_id)
  72. WHERE style_property.style_id IN (' . $this->_getDb()->quote($styleIds) . ')
  73. ORDER BY property_definition.display_order
  74. ', 'property_id');
  75. }
  76. /**
  77. * Gets all style properties in a style with the specified definition IDs
  78. * that have been customized or defined directly in the style.
  79. *
  80. * @param integer $styleId
  81. * @param array $definitionIds
  82. *
  83. * @return array Format: [definition id] => info
  84. */
  85. public function getStylePropertiesInStyleByDefinitions($styleId, array $definitionIds)
  86. {
  87. if (!$definitionIds)
  88. {
  89. return array();
  90. }
  91. return $this->fetchAllKeyed('
  92. SELECT property_definition.*,
  93. style_property.*
  94. FROM xf_style_property AS style_property
  95. INNER JOIN xf_style_property_definition AS property_definition ON
  96. (property_definition.property_definition_id = style_property.property_definition_id)
  97. WHERE style_property.style_id = ?
  98. AND style_property.definition_id IN (' . $this->_getDb()->quote($definitionIds) . ')
  99. ORDER BY property_definition.display_order
  100. ', 'property_definition_id', $styleId);
  101. }
  102. /**
  103. * Gets the effective style properties in a style. This includes properties
  104. * that have been customized/created in a parent style.
  105. *
  106. * Includes effectiveState key in each property (customized, inherited, default).
  107. *
  108. * @param integer $styleId
  109. * @param array|null $path Path from style to root (earlier positions closer to style); if null, determined automatically
  110. * @param array|null $properties List of properties in this style and all parent styles; if null, determined automatically
  111. *
  112. * @return array Format: [definition id] => info
  113. */
  114. public function getEffectiveStylePropertiesInStyle($styleId, array $path = null, array $properties = null)
  115. {
  116. if ($path === null)
  117. {
  118. $path = $this->getParentPathFromStyle($styleId);
  119. }
  120. if ($properties === null)
  121. {
  122. $properties = $this->getStylePropertiesInStyles($path);
  123. }
  124. $effective = array();
  125. $propertyPriorities = array();
  126. foreach ($properties AS $property)
  127. {
  128. $definitionId = $property['property_definition_id'];
  129. $propertyPriority = array_search($property['style_id'], $path);
  130. if (!isset($propertyPriorities[$definitionId]) || $propertyPriority < $propertyPriorities[$definitionId])
  131. {
  132. switch ($property['style_id'])
  133. {
  134. case $property['definition_style_id']:
  135. $property['effectiveState'] = 'default';
  136. break;
  137. case $styleId:
  138. $property['effectiveState'] = 'customized';
  139. break;
  140. default:
  141. $property['effectiveState'] = 'inherited';
  142. break;
  143. }
  144. $effective[$definitionId] = $property;
  145. $propertyPriorities[$definitionId] = $propertyPriority;
  146. }
  147. }
  148. return $effective;
  149. }
  150. /**
  151. * Gets the effective properties and groups in a style. Properties are organized
  152. * within the groups (in properties key).
  153. *
  154. * @param integer $styleId
  155. * @param array|null $path Path from style to root (earlier positions closer to style); if null, determined automatically
  156. * @param array|null $properties List of properties in this style and all parent styles; if null, determined automatically
  157. *
  158. * @return array Format: [group name] => group info, with [properties][definition id] => property info
  159. */
  160. public function getEffectiveStylePropertiesByGroup($styleId, array $path = null, array $properties = null)
  161. {
  162. if ($path === null)
  163. {
  164. $path = $this->getParentPathFromStyle($styleId);
  165. }
  166. if ($properties === null)
  167. {
  168. $properties = $this->getEffectiveStylePropertiesInStyle($styleId, $path, $properties);
  169. }
  170. $groups = $this->getEffectiveStylePropertyGroupsInStyle($styleId, $path);
  171. $invalidGroupings = array();
  172. foreach ($properties AS $definitionId => $property)
  173. {
  174. if (isset($groups[$property['group_name']]))
  175. {
  176. $groups[$property['group_name']]['properties'][$definitionId] = $property;
  177. }
  178. else
  179. {
  180. $invalidGroupings[$definitionId] = $property;
  181. }
  182. }
  183. if ($invalidGroupings)
  184. {
  185. $groups[''] = array(
  186. 'property_group_id' => 0,
  187. 'group_name' => '',
  188. 'group_style_id' => $styleId,
  189. 'title' => '(ungrouped)',
  190. 'description' => '',
  191. 'addon_id' => '',
  192. 'properties' => $invalidGroupings
  193. );
  194. }
  195. return $groups;
  196. }
  197. /**
  198. * Fetches all color palette properties for the specified style.
  199. *
  200. * @param integer $styleId
  201. * @param array|null $path Path from style to root (earlier positions closer to style); if null, determined automatically
  202. * @param array|null $properties List of properties in this style and all parent styles; if null, determined automatically
  203. *
  204. * @return array
  205. */
  206. public function getColorPalettePropertiesInStyle($styleId, array $path = null, array $properties = null)
  207. {
  208. $groups = $this->getEffectiveStylePropertiesByGroup($styleId, $path, $properties);
  209. return $groups['color']['properties'];
  210. }
  211. /**
  212. * Reorganizes a property list to key properties by name. This is only safe
  213. * to do when getting properties (effective or not) for a single style.
  214. *
  215. * @param array $properties
  216. *
  217. * @return array
  218. */
  219. public function keyPropertiesByName(array $properties)
  220. {
  221. $output = array();
  222. foreach ($properties AS $property)
  223. {
  224. $output[$property['property_name']] = $property;
  225. }
  226. return $output;
  227. }
  228. /**
  229. * Filters a list of properties into 2 groups: scalar properties and css properties.
  230. *
  231. * @param array [scalar props, css props]
  232. */
  233. public function filterPropertiesByType(array $properties)
  234. {
  235. $scalar = array();
  236. $css = array();
  237. foreach ($properties AS $key => $property)
  238. {
  239. if ($property['property_type'] == 'scalar')
  240. {
  241. $scalar[$key] = $property;
  242. }
  243. else
  244. {
  245. $css[$key] = $property;
  246. }
  247. }
  248. return array($scalar, $css);
  249. }
  250. /**
  251. * Gets the specified style property group.
  252. *
  253. * @param integer $groupId
  254. *
  255. * @return array|false
  256. */
  257. public function getStylePropertyGroupById($groupId)
  258. {
  259. return $this->_getDb()->fetchRow('
  260. SELECT *
  261. FROM xf_style_property_group
  262. WHERE property_group_id = ?
  263. ', $groupId);
  264. }
  265. /**
  266. * Gets all style property groups defined in the specified styles.
  267. *
  268. * @param array $styleIds
  269. *
  270. * @return array Format: [property group id] => info
  271. */
  272. public function getStylePropertyGroupsInStyles(array $styleIds)
  273. {
  274. if (!$styleIds)
  275. {
  276. return array();
  277. }
  278. return $this->fetchAllKeyed('
  279. SELECT *
  280. FROM xf_style_property_group
  281. WHERE group_style_id IN (' . $this->_getDb()->quote($styleIds) . ')
  282. ORDER BY display_order
  283. ', 'property_group_id');
  284. }
  285. /**
  286. * Gets the effective list of groups that apply to a style.
  287. *
  288. * @param integer $styleId
  289. * @param array|null $path Path from style to root (earlier positions closer to style); if null, determined automatically
  290. *
  291. * @return array Format: [group name] => info
  292. */
  293. public function getEffectiveStylePropertyGroupsInStyle($styleId, array $path = null)
  294. {
  295. if ($path === null)
  296. {
  297. $path = $this->getParentPathFromStyle($styleId);
  298. }
  299. $groups = $this->getStylePropertyGroupsInStyles($path);
  300. $output = array();
  301. foreach ($groups AS $group)
  302. {
  303. $output[$group['group_name']] = $group;
  304. }
  305. return $output;
  306. }
  307. /**
  308. * Gets name-value pairs of style property groups in a style.
  309. *
  310. * @param integer $styleId
  311. *
  312. * @return array Format: [name] => title
  313. */
  314. public function getStylePropertyGroupOptions($styleId)
  315. {
  316. $groups = $this->prepareStylePropertyGroups($this->getEffectiveStylePropertyGroupsInStyle($styleId));
  317. $output = array();
  318. foreach ($groups AS $group)
  319. {
  320. $output[$group['group_name']] = $group['title'];
  321. }
  322. return $output;
  323. }
  324. /**
  325. * Prepares a style property group for display. If properties are found
  326. * within, they will be automatically prepared.
  327. *
  328. * @param array $property
  329. * @param integer|null $displayStyleId The ID of the style the groups/properties are being edited in
  330. *
  331. * @return array Prepared version
  332. */
  333. public function prepareStylePropertyGroup(array $group, $displayStyleId = null)
  334. {
  335. $group['masterTitle'] = $group['title'];
  336. $group['masterDescription'] = $group['description'];
  337. if ($group['addon_id'])
  338. {
  339. $group['title'] = new XenForo_Phrase($this->getStylePropertyGroupTitlePhraseName($group));
  340. $group['description'] = new XenForo_Phrase($this->getStylePropertyGroupDescriptionPhraseName($group));
  341. }
  342. if (!$group['group_name'])
  343. {
  344. $group['canEdit'] = false;
  345. }
  346. else if ($displayStyleId === null)
  347. {
  348. $group['canEdit'] = $this->canEditStylePropertyDefinition($group['group_style_id']);
  349. }
  350. else
  351. {
  352. $group['canEdit'] = (
  353. $this->canEditStylePropertyDefinition($group['group_style_id'])
  354. && $group['group_style_id'] == $displayStyleId
  355. );
  356. }
  357. if (!empty($group['properties']))
  358. {
  359. $group['properties'] = $this->prepareStyleProperties($group['properties'], $displayStyleId);
  360. }
  361. return $group;
  362. }
  363. /**
  364. * Prepares a list of style property groups. If properties are found within,
  365. * they will be automatically prepared.
  366. *
  367. * @param array $groups
  368. * @param integer|null $displayStyleId The ID of the style the groups/properties are being edited in
  369. *
  370. * @return array
  371. */
  372. public function prepareStylePropertyGroups(array $groups, $displayStyleId = null)
  373. {
  374. foreach ($groups AS &$group)
  375. {
  376. $group = $this->prepareStylePropertyGroup($group, $displayStyleId);
  377. }
  378. return $groups;
  379. }
  380. /**
  381. * Gets the default style property group record.
  382. *
  383. * @param integer $styleId
  384. *
  385. * @return array
  386. */
  387. public function getDefaultStylePropertyGroup($styleId)
  388. {
  389. return array(
  390. 'group_name' => '',
  391. 'group_style_id' => $styleId,
  392. 'title' => '',
  393. 'display_order' => 1,
  394. 'sub_group' => '',
  395. 'addon_id' => null,
  396. 'masterTitle' => '',
  397. 'masterDescription' => '',
  398. );
  399. }
  400. /**
  401. * Gets the name of the style property group title phrase.
  402. *
  403. * @param array $group
  404. *
  405. * @return string
  406. */
  407. public function getStylePropertyGroupTitlePhraseName(array $group)
  408. {
  409. switch ($group['group_style_id'])
  410. {
  411. case -1: $suffix = 'admin'; break;
  412. case 0: $suffix = 'master'; break;
  413. default: return '';
  414. }
  415. return "style_property_group_$group[group_name]_$suffix";
  416. }
  417. /**
  418. * Gets the name of the style property group description phrase.
  419. *
  420. * @param array $group
  421. *
  422. * @return string
  423. */
  424. public function getStylePropertyGroupDescriptionPhraseName(array $group)
  425. {
  426. switch ($group['group_style_id'])
  427. {
  428. case -1: $suffix = 'admin'; break;
  429. case 0: $suffix = 'master'; break;
  430. default: return '';
  431. }
  432. return "style_property_group_$group[group_name]_{$suffix}_desc";
  433. }
  434. /**
  435. * Gets the parent path from the specified style. For real styles,
  436. * this is the parent list. However, this function can handle styles
  437. * 0 (master) and -1 (ACP).
  438. *
  439. * @param $styleId
  440. * @return array Parent list; earlier positions are more specific
  441. */
  442. public function getParentPathFromStyle($styleId)
  443. {
  444. switch (intval($styleId))
  445. {
  446. case 0: return array(0);
  447. case -1: return array(-1, 0);
  448. default:
  449. $style = $this->_getStyleModel()->getStyleById($styleId);
  450. if ($style)
  451. {
  452. return explode(',', $style['parent_list']);
  453. }
  454. else
  455. {
  456. return array();
  457. }
  458. }
  459. }
  460. /**
  461. * Gets style info in the style property-specific way.
  462. *
  463. * @param integer $styleId
  464. *
  465. * @return array|false
  466. */
  467. public function getStyle($styleId)
  468. {
  469. if ($styleId >= 0)
  470. {
  471. return $this->getModelFromCache('XenForo_Model_Style')->getStyleById($styleId, true);
  472. }
  473. else
  474. {
  475. return array(
  476. 'style_id' => -1,
  477. 'parent_list' => '-1,0',
  478. 'title' => new XenForo_Phrase('admin_control_panel')
  479. );
  480. }
  481. }
  482. /**
  483. * Group a list of style properties by the style they belong to.
  484. * This uses the customization style (not definition style) for grouping.
  485. *
  486. * @param array $properties
  487. *
  488. * @return array Format: [style id][definition id] => info
  489. */
  490. public function groupStylePropertiesByStyle(array $properties)
  491. {
  492. $newProperties = array();
  493. foreach ($properties AS $property)
  494. {
  495. $newProperties[$property['style_id']][$property['property_definition_id']] = $property;
  496. }
  497. return $newProperties;
  498. }
  499. /**
  500. * Rebuilds the property cache for all styles.
  501. */
  502. public function rebuildPropertyCacheForAllStyles()
  503. {
  504. $this->rebuildPropertyCacheInStyleAndChildren(0);
  505. }
  506. /**
  507. * Rebuild the style property cache in the specified style and all
  508. * child/dependent styles.
  509. *
  510. * @param integer $styleId
  511. *
  512. * @return array The property cache for the requested style
  513. */
  514. public function rebuildPropertyCacheInStyleAndChildren($styleId)
  515. {
  516. if ($styleId == -1)
  517. {
  518. $rebuildStyleIds = array(-1);
  519. $dataStyleIds = array(-1, 0);
  520. $styles = array();
  521. }
  522. else
  523. {
  524. $styleModel = $this->_getStyleModel();
  525. $styles = $styleModel->getAllStyles();
  526. $styleTree = $styleModel->getStyleTreeAssociations($styles);
  527. $rebuildStyleIds = $styleModel->getAllChildStyleIdsFromTree($styleId, $styleTree);
  528. $rebuildStyleIds[] = $styleId;
  529. $dataStyleIds = array_keys($styles);
  530. $dataStyleIds[] = 0;
  531. if ($styleId == 0)
  532. {
  533. $rebuildStyleIds[] = -1;
  534. $dataStyleIds[] = -1;
  535. }
  536. }
  537. $properties = $this->groupStylePropertiesByStyle(
  538. $this->getStylePropertiesInStyles($dataStyleIds)
  539. );
  540. $styleOutput = false;
  541. foreach ($rebuildStyleIds AS $rebuildStyleId)
  542. {
  543. $sourceStyle = (isset($styles[$rebuildStyleId]) ? $styles[$rebuildStyleId] : array());
  544. switch ($rebuildStyleId)
  545. {
  546. case 0:
  547. continue 2;
  548. case -1:
  549. $sourceStyleIds = array(-1, 0);
  550. break;
  551. default:
  552. $sourceStyleIds = explode(',', $sourceStyle['parent_list']);
  553. }
  554. $styleProperties = array();
  555. foreach ($sourceStyleIds AS $sourceStyleId)
  556. {
  557. if (isset($properties[$sourceStyleId]))
  558. {
  559. $styleProperties = array_merge($styleProperties, $properties[$sourceStyleId]);
  560. }
  561. }
  562. $effectiveProperties = $this->getEffectiveStylePropertiesInStyle(
  563. $rebuildStyleId, $sourceStyleIds, $styleProperties
  564. );
  565. $cacheOutput = $this->updatePropertyCacheInStyle($rebuildStyleId, $effectiveProperties, $sourceStyle);
  566. if ($rebuildStyleId == $styleId)
  567. {
  568. $styleOutput = $cacheOutput;
  569. }
  570. }
  571. return $styleOutput;
  572. }
  573. /**
  574. * Updates the property cache in the specified style.
  575. *
  576. * @param integer $styleId
  577. * @param array $effectiveProperties List of effective properties in style.
  578. * @param array|null $style Style information; queried if needed
  579. *
  580. * @return array|false Compiled property cache
  581. */
  582. public function updatePropertyCacheInStyle($styleId, array $effectiveProperties, array $style = null)
  583. {
  584. if ($styleId == 0)
  585. {
  586. return false;
  587. }
  588. $propertyCache = array();
  589. foreach ($effectiveProperties AS $property)
  590. {
  591. if ($property['property_type'] == 'scalar')
  592. {
  593. $propertyCache[$property['property_name']] = $property['property_value'];
  594. }
  595. else
  596. {
  597. $propertyCache[$property['property_name']] = unserialize($property['property_value']);
  598. }
  599. }
  600. foreach ($propertyCache AS &$propertyValue)
  601. {
  602. if (is_array($propertyValue))
  603. {
  604. $propertyValue = $this->compileCssPropertyForCache($propertyValue, $propertyCache);
  605. }
  606. else
  607. {
  608. $propertyValue = $this->compileScalarPropertyForCache($propertyValue, $propertyCache);
  609. }
  610. }
  611. if ($styleId == -1)
  612. {
  613. $this->_getDataRegistryModel()->set('adminStyleModifiedDate', XenForo_Application::$time);
  614. $this->_getDataRegistryModel()->set('adminStyleProperties', $propertyCache);
  615. }
  616. else if ($styleId > 0)
  617. {
  618. $dw = XenForo_DataWriter::create('XenForo_DataWriter_Style');
  619. if ($style)
  620. {
  621. $dw->setExistingData($style, true);
  622. }
  623. else
  624. {
  625. $dw->setExistingData($styleId);
  626. }
  627. $dw->set('properties', $propertyCache);
  628. $dw->save();
  629. }
  630. return $propertyCache;
  631. }
  632. /**
  633. * Compiles a CSS property from it's user-input-based version to the property cache version.
  634. *
  635. * @param array $original Original, input-based CSS rule
  636. * @param array $properties A list of all properties, for resolving variable style references
  637. *
  638. * @return array CSS rule for cache
  639. */
  640. public function compileCssPropertyForCache(array $original, array $properties = array())
  641. {
  642. $output = $original;
  643. $output = $this->compileCssProperty_sanitize($output, $original);
  644. foreach ($output AS &$outputValue)
  645. {
  646. $outputValue = $this->replaceVariablesInStylePropertyValue($outputValue, $properties);
  647. }
  648. $output = $this->compileCssProperty_compileRules($output, $original);
  649. $output = $this->compileCssProperty_cleanUp($output, $original);
  650. return $output;
  651. }
  652. /**
  653. * Sanitizes the values in the CSS property output array.
  654. *
  655. * @param array $output Output CSS property
  656. * @param array $original Original format of CSS property
  657. *
  658. * @return array Updated output CSS property
  659. */
  660. public function compileCssProperty_sanitize(array $output, array $original)
  661. {
  662. // remove empty properties so isset can be used (0 is a valid value in many places)
  663. foreach ($output AS $key => &$value)
  664. {
  665. if (is_array($value))
  666. {
  667. if (count($value) == 0)
  668. {
  669. unset($output[$key]);
  670. }
  671. }
  672. else if (trim($value) === '')
  673. {
  674. unset($output[$key]);
  675. }
  676. else if ($value !== '0' && strval(intval($value)) === $value)
  677. {
  678. // not 0 and looks like an int, add "px" unit
  679. $value = $value . 'px';
  680. }
  681. }
  682. // translate array-based text decoration to css style
  683. if (!empty($output['text-decoration']) && is_array($output['text-decoration']))
  684. {
  685. if (isset($output['text-decoration']['none']))
  686. {
  687. $output['text-decoration'] = 'none';
  688. }
  689. else
  690. {
  691. $output['text-decoration'] = implode(' ', $output['text-decoration']);
  692. }
  693. }
  694. return $output;
  695. }
  696. /**
  697. * Compiles all the rules of a CSS property.
  698. *
  699. * @param array $output Output CSS property
  700. * @param array $original Original format of CSS property
  701. *
  702. * @return array Updated output CSS property
  703. */
  704. public function compileCssProperty_compileRules(array $output, array $original)
  705. {
  706. // handle the font short cut (includes all text-related rules)
  707. if (false && isset($output['font-size'], $output['font-family']))
  708. {
  709. // font shortcut now disabled on account of the line-height issue
  710. $output['font'] = 'font: ' . $this->_getCssValue(
  711. $output, array('font-style', 'font-variant', 'font-weight', 'font-size', 'font-family')
  712. ) . ';';
  713. }
  714. else
  715. {
  716. $output['font'] = $this->_getCssValueRule(
  717. $output, array('font-style', 'font-variant', 'font-weight', 'font-size', 'font-family')
  718. );
  719. }
  720. $output['font'] .= "\n" . $this->_getCssValueRule(
  721. $output, array('color', 'text-decoration')
  722. );
  723. // background shortcut
  724. if (isset($output['background-image']) && $output['background-image'] != 'none')
  725. {
  726. $output['background-image'] = trim($output['background-image']);
  727. if (!preg_match('#^url\s*\(#', $output['background-image']))
  728. {
  729. $output['background-image'] = preg_replace('/^("|\')(.*)\\1$/', '\\2', $output['background-image']);
  730. $output['background-image'] = 'url(\'' . $output['background-image'] . '\')';
  731. }
  732. }
  733. if (!empty($output['background-none']))
  734. {
  735. $output['background'] = 'background: none;';
  736. $output['background-color'] = 'none';
  737. $output['background-image'] = 'none';
  738. }
  739. else if ( // force the background shortcut if a color + image is specified, OR if color = rgba
  740. isset($output['background-color'], $output['background-image'])
  741. ||
  742. (isset($output['background-color']) && substr(strtolower($output['background-color']), 0, 4) == 'rgba')
  743. )
  744. {
  745. $output['background'] = 'background: ' . $this->_getCssValue(
  746. $output, array('background-color', 'background-image', 'background-repeat', 'background-position')
  747. ) . ';';
  748. }
  749. else
  750. {
  751. $output['background'] = $this->_getCssValueRule($output,
  752. array('background-color', 'background-image', 'background-repeat', 'background-position')
  753. );
  754. }
  755. // padding, margin shortcuts
  756. $this->_getPaddingMarginShortCuts('padding', $output);
  757. $this->_getPaddingMarginShortCuts('margin', $output);
  758. // border shortcut
  759. if (isset($output['border-width'], $output['border-style'], $output['border-color']))
  760. {
  761. $output['border'] = 'border: ' . $this->_getCssValue(
  762. $output, array('border-width', 'border-style', 'border-color')
  763. ) . ';';
  764. }
  765. else
  766. {
  767. $output['border'] = $this->_getCssValueRule(
  768. $output, array('border-width', 'border-style', 'border-color')
  769. );
  770. }
  771. foreach (array('top', 'right', 'bottom', 'left') AS $borderSide)
  772. {
  773. $borderSideName = "border-$borderSide";
  774. if (isset($output["$borderSideName-width"], $output["$borderSideName-style"], $output["$borderSideName-color"]))
  775. {
  776. $borderSideCss = $borderSideName . ': ' . $this->_getCssValue(
  777. $output, array("$borderSideName-width", "$borderSideName-style", "$borderSideName-color")
  778. ) . ';';
  779. }
  780. else
  781. {
  782. $borderSideCss = $this->_getCssValueRule(
  783. $output, array("$borderSideName-width", "$borderSideName-style", "$borderSideName-color")
  784. );
  785. }
  786. if ($borderSideCss)
  787. {
  788. $output['border'] .= "\n" . $borderSideCss;
  789. }
  790. }
  791. // border radius shortcut, ties into border
  792. if (isset($output['border-radius']))
  793. {
  794. $output['border'] .= "\nborder-radius: " . $output['border-radius'] . ';';
  795. }
  796. foreach (array('top-left', 'top-right', 'bottom-right', 'bottom-left') AS $radiusCorner)
  797. {
  798. $radiusCornerName = "border-$radiusCorner-radius";
  799. if (isset($output[$radiusCornerName]))
  800. {
  801. $output['border'] .= "\n$radiusCornerName: " . $output[$radiusCornerName] . ';';
  802. }
  803. }
  804. return $output;
  805. }
  806. protected function _getPaddingMarginShortCuts($type, array &$output)
  807. {
  808. $test = $output;
  809. // push all the values into the test array for purposes of determining how to build the short cut
  810. if (isset($output[$type . '-all']))
  811. {
  812. foreach (array('top', 'left', 'bottom', 'right') AS $side)
  813. {
  814. if (!isset($output["{$type}-{$side}"]))
  815. {
  816. $test["{$type}-{$side}"] = $output[$type . '-all'];
  817. }
  818. }
  819. }
  820. if (isset($test[$type . '-top'], $test[$type . '-right'], $test[$type . '-bottom'], $test[$type . '-left']))
  821. {
  822. if ($test[$type . '-top'] == $test[$type . '-right']
  823. && $test[$type . '-top'] == $test[$type . '-bottom']
  824. && $test[$type . '-top'] == $test[$type . '-left'])
  825. {
  826. $output[$type] = $type . ': ' . $test[$type . '-top'] . ';';
  827. }
  828. else if ($test[$type . '-top'] == $test[$type . '-bottom'] && $test[$type . '-right'] == $test[$type . '-left'])
  829. {
  830. $output[$type] = $type . ': ' . $this->_getCssValue(
  831. $test, array($type . '-top', $type . '-right')
  832. ) . ';';
  833. }
  834. else if ($test[$type . '-right'] == $test[$type . '-left'])
  835. {
  836. $output[$type] = $type . ': ' . $this->_getCssValue(
  837. $test, array($type . '-top', $type . '-right', $type . '-bottom')
  838. ) . ';';
  839. }
  840. else
  841. {
  842. $output[$type] = $type . ': ' . $this->_getCssValue(
  843. $test, array($type . '-top', $type . '-right', $type . '-bottom', $type . '-left')
  844. ) . ';';
  845. }
  846. }
  847. else
  848. {
  849. $output[$type] = $this->_getCssValueRule(
  850. $test, array($type . '-top', $type . '-right', $type . '-bottom', $type . '-left')
  851. );
  852. }
  853. }
  854. /**
  855. * Cleans up the CSS property output after compilation.
  856. *
  857. * @param array $output Output CSS property
  858. * @param array $original Original format of CSS property
  859. *
  860. * @return array Updated output CSS property
  861. */
  862. public function compileCssProperty_cleanUp(array $output, array $original)
  863. {
  864. foreach ($output AS $key => &$value)
  865. {
  866. if (preg_match('/^(
  867. background-(none|image|position|repeat)
  868. |font-(variant|weight|style)
  869. |text-decoration
  870. |border-style
  871. |border-(left|right|top|bottom)-style
  872. )/x', $key))
  873. {
  874. unset($output[$key]);
  875. continue;
  876. }
  877. $value = trim($value);
  878. if ($value === '')
  879. {
  880. unset($output[$key]);
  881. continue;
  882. }
  883. }
  884. return $output;
  885. }
  886. /**
  887. * Compiles a scalar property value for the cache.
  888. *
  889. * @param string $original Original property value
  890. * @param array $properties A list of all properties, for resolving variable style references
  891. *
  892. * @return string
  893. */
  894. public function compileScalarPropertyForCache($original, array $properties = array())
  895. {
  896. return $this->replaceVariablesInStylePropertyValue($original, $properties);
  897. }
  898. /**
  899. * Replaces variable references in a style property value.
  900. *
  901. * @param string $value Property value. This is an individual string value.
  902. * @param array $properties List of properites to read from.
  903. *
  904. * @return string
  905. */
  906. public function replaceVariablesInStylePropertyValue($value, array $properties, array $seenProperties = array())
  907. {
  908. if (!$properties)
  909. {
  910. return $value;
  911. }
  912. $outputValue = $this->convertAtPropertiesToTemplateSyntax($value, $properties);
  913. $varProperties = array();
  914. preg_match_all('#\{xen:property\s+("|\'|)([a-z0-9_-]+)(\.([a-z0-9_-]+))?\\1\s*\}#i', $outputValue, $matches, PREG_SET_ORDER);
  915. foreach ($matches AS $match)
  916. {
  917. $varProperties[$match[0]] = (isset($match[4]) ? array($match[2], $match[4]) : array($match[2]));
  918. }
  919. foreach ($varProperties AS $matchSearch => $match)
  920. {
  921. $matchReplace = '';
  922. $matchName = implode('.', $match);
  923. if (!isset($seenProperties[$matchName]))
  924. {
  925. $matchProperty = $match[0];
  926. if (isset($match[1]))
  927. {
  928. $matchSubProperty = $match[1];
  929. if (isset($properties[$matchProperty][$matchSubProperty]) && is_array($properties[$matchProperty]))
  930. {
  931. $matchReplace = $properties[$matchProperty][$matchSubProperty];
  932. }
  933. }
  934. else if (isset($properties[$matchProperty]) && !is_array($properties[$matchProperty]))
  935. {
  936. $matchReplace = $properties[$matchProperty];
  937. }
  938. }
  939. if ($matchReplace)
  940. {
  941. $newSeenProperties = $seenProperties;
  942. $newSeenProperties[$matchName] = true;
  943. $matchReplace = $this->replaceVariablesInStylePropertyValue($matchReplace, $properties, $newSeenProperties);
  944. }
  945. $outputValue = str_replace($matchSearch, $matchReplace, $outputValue);
  946. }
  947. return $outputValue;
  948. }
  949. /**
  950. * Helper for CSS property cache compilation. Gets the value(s) for one or more
  951. * CSS rule keys. Multiple keys will be separated by a space.
  952. *
  953. * @param array $search Array to search for keys in
  954. * @param string|array $key One or more keys to search for
  955. *
  956. * @return string Values for matching keys; space separated
  957. */
  958. protected function _getCssValue(array $search, $key)
  959. {
  960. if (is_array($key))
  961. {
  962. $parts = array();
  963. foreach ($key AS $searchKey)
  964. {
  965. if (isset($search[$searchKey]))
  966. {
  967. $parts[] = $search[$searchKey];
  968. }
  969. }
  970. return implode(' ', $parts);
  971. }
  972. else
  973. {
  974. return (isset($search[$key]) ? $search[$key] : '');
  975. }
  976. }
  977. /**
  978. * Helper for CSS property cache compilation. Gets the full rule(s) for one or more
  979. * CSS rule keys.
  980. *
  981. * @param array $search Array to search for keys in
  982. * @param string|array $key One or more keys to search for
  983. *
  984. * @return string Full CSS rules
  985. */
  986. protected function _getCssValueRule(array $search, $key)
  987. {
  988. if (is_array($key))
  989. {
  990. $parts = array();
  991. foreach ($key AS $searchKey)
  992. {
  993. if (isset($search[$searchKey]))
  994. {
  995. $parts[] = "$searchKey: " . $search[$searchKey] . ";";
  996. }
  997. }
  998. return implode("\n", $parts);
  999. }
  1000. else if (isset($search[$key]))
  1001. {
  1002. return "$key: " . $search[$key] . ";";
  1003. }
  1004. }
  1005. /**
  1006. * Updates the specified style property value.
  1007. *
  1008. * @param array $definition Style property definition
  1009. * @param integer $styleId Style property is being changed in
  1010. * @param mixed $newValue New value (string for scalar; array for css)
  1011. * @param boolean $extraOptions Extra options to pass to the data writer
  1012. * @param mixed $existingProperty If array/false, considered to be the property to be updated; otherwise, determined automatically
  1013. * @param string $existingValue The existing value in the place of this property. This may
  1014. * come from the parent style (unlike $existingProperty). This prevents customization from
  1015. * occurring when a value isn't changed.
  1016. *
  1017. * @param string Returns the property value as it looks going into the DB
  1018. */
  1019. public function updateStylePropertyValue(array $definition, $styleId, $newValue,
  1020. array $extraOptions = array(), $existingProperty = null, $existingValue = null
  1021. )
  1022. {
  1023. $styleId = intval($styleId);
  1024. if ($existingProperty !== false && !is_array($existingProperty))
  1025. {
  1026. $existingProperty = $this->getStylePropertyByDefinitionAndStyle(
  1027. $definition['property_definition_id'], $styleId
  1028. );
  1029. }
  1030. if ($definition['property_type'] == 'scalar')
  1031. {
  1032. $newValue = strval($newValue);
  1033. }
  1034. else if (!is_array($newValue))
  1035. {
  1036. $newValue = array();
  1037. }
  1038. $dw = XenForo_DataWriter::create('XenForo_DataWriter_StyleProperty');
  1039. $dw->setOption(XenForo_DataWriter_StyleProperty::OPTION_VALUE_FORMAT, $definition['property_type']);
  1040. $dw->setOption(XenForo_DataWriter_StyleProperty::OPTION_VALUE_COMPONENTS, unserialize($definition['css_components']));
  1041. $dw->setOption(XenForo_DataWriter_StyleProperty::OPTION_REBUILD_CACHE, true);
  1042. foreach ($extraOptions AS $option => $optionValue)
  1043. {
  1044. $dw->setOption($option, $optionValue);
  1045. }
  1046. $dw->setExtraData(XenForo_DataWriter_StyleProperty::DATA_DEFINITION, $definition);
  1047. if ($existingProperty)
  1048. {
  1049. $dw->setExistingData($existingProperty, true);
  1050. }
  1051. else
  1052. {
  1053. $dw->set('property_definition_id', $definition['property_definition_id']);
  1054. $dw->set('style_id', $styleId);
  1055. }
  1056. $dw->set('property_value', $newValue);
  1057. $dw->preSave();
  1058. if ($dw->get('property_value') === $existingValue)
  1059. {
  1060. return $dw->get('property_value');
  1061. }
  1062. $dw->save();
  1063. return $dw->get('property_value');
  1064. }
  1065. /**
  1066. * Saves a set of style property changes from the input format.
  1067. *
  1068. * @param integer $styleId Style to change properties in
  1069. * @param array $properties Properties from input; keyed by definition ID
  1070. * @param array $reset List of properties to reset if customized; keyed by definition ID
  1071. */
  1072. public function saveStylePropertiesInStyleFromInput($styleId, array $properties, array $reset = array())
  1073. {
  1074. $existingProperties = $this->getEffectiveStylePropertiesInStyle($styleId);
  1075. XenForo_Db::beginTransaction($this->_getDb());
  1076. foreach ($properties AS $definitionId => $propertyValue)
  1077. {
  1078. if (!isset($existingProperties[$definitionId]))
  1079. {
  1080. continue;
  1081. }
  1082. $propertyDefinition = $existingProperties[$definitionId];
  1083. if ($propertyDefinition['style_id'] == $styleId)
  1084. {
  1085. $existingProperty = $propertyDefinition;
  1086. if (!empty($reset[$definitionId]) && $propertyDefinition['definition_style_id'] != $styleId)
  1087. {
  1088. $dw = XenForo_DataWriter::create('XenForo_DataWriter_StyleProperty');
  1089. $dw->setOption(XenForo_DataWriter_StyleProperty::OPTION_REBUILD_CACHE, false);
  1090. $dw->setExtraData(XenForo_DataWriter_StyleProperty::DATA_DEFINITION, $propertyDefinition);
  1091. $dw->setExistingData($existingProperty, true);
  1092. $dw->delete();
  1093. continue;
  1094. }
  1095. }
  1096. else
  1097. {
  1098. $existingProperty = false;
  1099. }
  1100. $this->updateStylePropertyValue(
  1101. $propertyDefinition, $styleId, $propertyValue,
  1102. array(XenForo_DataWriter_StyleProperty::OPTION_REBUILD_CACHE => false),
  1103. $existingProperty, $propertyDefinition['property_value'] // this is the effective value
  1104. );
  1105. }
  1106. $this->rebuildPropertyCacheInStyleAndChildren($styleId);
  1107. XenForo_Db::commit($this->_getDb());
  1108. }
  1109. /**
  1110. * Get the specified style property definition by ID. Includes default
  1111. * property value.
  1112. *
  1113. * @param integer $propertyDefinitionId
  1114. *
  1115. * @return array|false
  1116. */
  1117. public function getStylePropertyDefinitionById($propertyDefinitionId)
  1118. {
  1119. return $this->_getDb()->fetchRow('
  1120. SELECT property_definition.*,
  1121. property.property_value
  1122. FROM xf_style_property_definition AS property_definition
  1123. LEFT JOIN xf_style_property AS property ON
  1124. (property.property_definition_id = property_definition.property_definition_id
  1125. AND property.style_id = property_definition.definition_style_id)
  1126. WHERE property_definition.property_definition_id = ?
  1127. ', $propertyDefinitionId);
  1128. }
  1129. /**
  1130. * Gets the specified style property definition by its name and definition
  1131. * style ID. Includes default property value.
  1132. *
  1133. * @param string $name
  1134. * @param integer $styleId
  1135. *
  1136. * @return array|false
  1137. */
  1138. public function getStylePropertyDefinitionByNameAndStyle($name, $styleId)
  1139. {
  1140. return $this->_getDb()->fetchRow('
  1141. SELECT property_definition.*,
  1142. property.property_value
  1143. FROM xf_style_property_definition AS property_definition
  1144. LEFT JOIN xf_style_property AS property ON
  1145. (property.property_definition_id = property_definition.property_definition_id
  1146. AND property.style_id = property_definition.definition_style_id)
  1147. WHERE property_definition.property_name = ?
  1148. AND property_definition.definition_style_id = ?
  1149. ', array($name, $styleId));
  1150. }
  1151. /**
  1152. * Get the specified style property definitions by their IDs.
  1153. * Includes default property value.
  1154. *
  1155. * @param array $propertyDefinitionIds
  1156. *
  1157. * @return array Format: [property definition id] => info
  1158. */
  1159. public function getStylePropertyDefinitionsByIds(array $propertyDefinitionIds)
  1160. {
  1161. if (!$propertyDefinitionIds)
  1162. {
  1163. return array();
  1164. }
  1165. return $this->fetchAllKeyed('
  1166. SELECT property_definition.*,
  1167. property.property_value
  1168. FROM xf_style_property_definition AS property_definition
  1169. LEFT JOIN xf_style_property AS property ON
  1170. (property.property_definition_id = property_definition.property_definition_id
  1171. AND property.style_id = property_definition.definition_style_id)
  1172. WHERE property_definition.property_definition_id IN (' . $this->_getDb()->quote($propertyDefinitionIds) . ')
  1173. ', 'property_definition_id');
  1174. }
  1175. /**
  1176. * Returns an array of all style property definitions in the specified group
  1177. *
  1178. * @param string $groupId
  1179. * @param integer|null $styleId If specified, limits to definitions in a specified style
  1180. *
  1181. * @return array
  1182. */
  1183. public function getStylePropertyDefinitionsByGroup($groupName, $styleId = null)
  1184. {
  1185. $properties = $this->fetchAllKeyed('
  1186. SELECT *
  1187. FROM xf_style_property_definition
  1188. WHERE group_name = ?
  1189. ' . ($styleId !== null ? 'AND definition_style_id = ' . $this->_getDb()->quote($styleId) : '') . '
  1190. ORDER BY display_order
  1191. ', 'property_name', $groupName);
  1192. return $properties;
  1193. }
  1194. /**
  1195. * Create of update a style property definition. Input data
  1196. * is named after fields in the style property definition, as well as
  1197. * property_value_scalar and property_value_css.
  1198. *
  1199. * @param integer $definitionId Definition to update; if 0, creates a new one
  1200. * @param array $input List of data from input to change in definition
  1201. *
  1202. * @return array Definition info after saving
  1203. */
  1204. public function createOrUpdateStylePropertyDefinition($definitionId, array $input)
  1205. {
  1206. XenForo_Db::beginTransaction($this->_getDb());
  1207. $dw = XenForo_DataWriter::create('XenForo_DataWriter_StylePropertyDefinition');
  1208. if ($definitionId)
  1209. {
  1210. $dw->setExistingData($definitionId);
  1211. }
  1212. else
  1213. {
  1214. $dw->set('definition_style_id', $input['definition_style_id']);
  1215. }
  1216. $dw->set('group_name', $input['group_name']);
  1217. $dw->set('property_name', $input['property_name']);
  1218. $dw->set('title', $input['title']);
  1219. $dw->set('description', $input['description']);
  1220. $dw->set('property_type', $input['property_type']);
  1221. $dw->set('css_components', $input['css_components']);
  1222. $dw->set('scalar_type', $input['scalar_type']);
  1223. $dw->set('scalar_parameters', $input['scalar_parameters']);
  1224. $dw->set('display_order', $input['display_order']);
  1225. $dw->set('sub_group', $input['sub_group']);
  1226. $dw->set('addon_id', $input['addon_id']);
  1227. $dw->save();
  1228. $definition = $dw->getMergedData();
  1229. if ($input['property_type'] == 'scalar')
  1230. {
  1231. $propertyValue = $input['property_value_scalar'];
  1232. $newPropertyValue = $this->updateStylePropertyValue(
  1233. $definition, $definition['definition_style_id'], $propertyValue
  1234. );
  1235. }
  1236. else
  1237. {
  1238. $propertyValue = $input['property_value_css'];
  1239. if ($definitionId && !$dw->isChanged('property_type'))
  1240. {
  1241. // TODO: update value when possible
  1242. }
  1243. else
  1244. {
  1245. $newPropertyValue = $this->updateStylePropertyValue(
  1246. $definition, $definition['definition_style_id'], array()
  1247. );
  1248. }
  1249. }
  1250. XenForo_Db::commit($this->_getDb());
  1251. return $definition;
  1252. }
  1253. /**
  1254. * Prepares a style property (or definition) for display.
  1255. *
  1256. * @param array $property
  1257. * @param integer|null $displayStyleId The ID of the style the properties are being edited in
  1258. *
  1259. * @return array Prepared version
  1260. */
  1261. public function prepareStyleProperty(array $property, $displayStyleId = null)
  1262. {
  1263. $property['cssComponents'] = unserialize($property['css_components']);
  1264. if ($property['property_type'] == 'scalar')
  1265. {
  1266. $property['propertyValueScalar'] = $property['property_value'];
  1267. $property['propertyValueCss'] = array();
  1268. }
  1269. else
  1270. {
  1271. $property['propertyValueScalar'] = '';
  1272. $property['propertyValueCss'] = unserialize($property['property_value']);
  1273. }
  1274. $property['masterTitle'] = $property['title'];
  1275. if ($property['addon_id'])
  1276. {
  1277. $property['title'] = new XenForo_Phrase($this->getStylePropertyTitlePhraseName($property));
  1278. }
  1279. $property['masterDescription'] = $property['description'];
  1280. if ($property['addon_id'])
  1281. {
  1282. $property['description'] = new XenForo_Phrase($this->getStylePropertyDescriptionPhraseName($property));
  1283. }
  1284. if ($displayStyleId === null)
  1285. {
  1286. $property['canEditDefinition'] = $this->canEditStylePropertyDefinition($property['definition_style_id']);
  1287. }
  1288. else
  1289. {
  1290. $property['canEditDefinition'] = (
  1291. $this->canEditStylePropertyDefinition($property['definition_style_id'])
  1292. && $property['definition_style_id'] == $displayStyleId
  1293. );
  1294. }
  1295. $property['canReset'] = (
  1296. isset($property['effectiveState'])
  1297. && $property['effectiveState'] == 'customized'
  1298. );
  1299. return $property;
  1300. }
  1301. /**
  1302. * Prepares a list of style properties.
  1303. *
  1304. * @param array $properties
  1305. * @param integer|null $displayStyleId The ID of the style the properties are being edited in
  1306. *
  1307. * @return array
  1308. */
  1309. public function prepareStyleProperties(array $properties, $displayStyleId = null)
  1310. {
  1311. foreach ($properties AS &$property)
  1312. {
  1313. $property = $this->prepareStyleProperty($property, $displayStyleId);
  1314. }
  1315. return $properties;
  1316. }
  1317. /**
  1318. * Gets the name of the style property title phrase.
  1319. *
  1320. * @param array $property
  1321. *
  1322. * @return string
  1323. */
  1324. public function getStylePropertyTitlePhraseName(array $property)
  1325. {
  1326. switch ($property['definition_style_id'])
  1327. {
  1328. case -1: $suffix = 'admin'; break;
  1329. case 0: $suffix = 'master'; break;
  1330. default: return '';
  1331. }
  1332. return "style_property_$property[property_name]_$suffix";
  1333. }
  1334. /**
  1335. * Gets the name of the style property description phrase.
  1336. *
  1337. * @param array $property
  1338. *
  1339. * @return string
  1340. */
  1341. public function getStylePropertyDescriptionPhraseName(array $property)
  1342. {
  1343. switch ($property['definition_style_id'])
  1344. {
  1345. case -1: $suffix = 'admin'; break;
  1346. case 0: $suffix = 'master'; break;
  1347. default: return '';
  1348. }
  1349. return "style_property_$property[property_name]_description_$suffix";
  1350. }
  1351. /**
  1352. * Gets the default style property definition.
  1353. *
  1354. * @param integer $styleId
  1355. * @param string $groupName
  1356. *
  1357. * @return array
  1358. */
  1359. public function getDefaultStylePropertyDefinition($styleId, $groupName = '')
  1360. {
  1361. $components = array(
  1362. 'text' => true,
  1363. 'background' => true,
  1364. 'border' => true,
  1365. 'layout' => true,
  1366. 'extra' => true
  1367. );
  1368. return array(
  1369. 'definition_style_id' => $styleId,
  1370. 'group_name' => $groupName,
  1371. 'title' => '',
  1372. 'property_name' => '',
  1373. 'property_type' => 'css',
  1374. 'property_value' => '',
  1375. 'css_components' => serialize($components),
  1376. 'display_order' => 1,
  1377. 'sub_group' => '',
  1378. 'addon_id' => null,
  1379. 'cssComponents' => $components,
  1380. 'propertyValueScalar' => '',
  1381. 'propertyValueCss' => array(),
  1382. 'masterTitle' => ''
  1383. );
  1384. }
  1385. /**
  1386. * Determines if a style property definition can be edited in
  1387. * the specified style.
  1388. *
  1389. * @param integer $styleId
  1390. *
  1391. * @return boolean
  1392. */
  1393. public function canEditStylePropertyDefinition($styleId)
  1394. {
  1395. return XenForo_Application::debugMode();
  1396. }
  1397. /**
  1398. * Determines if a style property can be edited in the specified style.
  1399. *
  1400. * @param integer $styleId
  1401. *
  1402. * @return boolean
  1403. */
  1404. public function canEditStyleProperty($styleId)
  1405. {
  1406. if ($styleId > 0)
  1407. {
  1408. return true;
  1409. }
  1410. else
  1411. {
  1412. return XenForo_Application::debugMode();
  1413. }
  1414. }
  1415. /**
  1416. * Get the style property development directory based on the style a property/definition
  1417. * is defined in. Returns an empty string for non-master properties.
  1418. *
  1419. * @param integer $styleId
  1420. *
  1421. * @return string
  1422. */
  1423. public function getStylePropertyDevelopmentDirectory($styleId)
  1424. {
  1425. if ($styleId > 0)
  1426. {
  1427. return '';
  1428. }
  1429. $config = XenForo_Application::get('config');
  1430. if (!$config->debug || !$config->development->directory)
  1431. {
  1432. return '';
  1433. }
  1434. if ($styleId == 0)
  1435. {
  1436. return XenForo_Application::getInstance()->getRootDir()
  1437. . '/' . $config->development->directory . '/file_output/style_properties';
  1438. }
  1439. else
  1440. {
  1441. return XenForo_Application::getInstance()->getRootDir()
  1442. . '/' . $config->development->directory . '/file_output/admin_style_properties';
  1443. }
  1444. }
  1445. /**
  1446. * Gets the full path to a specific style property development file.
  1447. * Ensures directory is writable.
  1448. *
  1449. * @param string $propertyName
  1450. * @param integer $styleId
  1451. *
  1452. * @return string
  1453. */
  1454. public function getStylePropertyDevelopmentFileName($propertyName, $styleId)
  1455. {
  1456. $dir = $this->getStylePropertyDevelopmentDirectory($styleId);
  1457. if (!$dir)
  1458. {
  1459. throw new XenForo_Exception('Tried to write non-master/admin style property value to development directory, or debug mode is not enabled');
  1460. }
  1461. if (!is_dir($dir) || !is_writable($dir))
  1462. {
  1463. throw new XenForo_Exception("Style property development directory $dir is not writable");
  1464. }
  1465. return ($dir . '/' . $propertyName . '.xml');
  1466. }
  1467. public function writeAllStylePropertyDevelopmentFiles()
  1468. {
  1469. $definitions = $this->getDefaultStylePropertyDefinition(0);
  1470. Zend_Debug::dump($definitions);
  1471. }
  1472. /**
  1473. * Writes out a style property development file.
  1474. *
  1475. * @param array $definition Property definition
  1476. * @param array $property Property value
  1477. */
  1478. public function writeStylePropertyDevelopmentFile(array $definition, array $property)
  1479. {
  1480. if ($definition['addon_id'] != 'XenForo' && $property['style_id'] > 0)
  1481. {
  1482. return;
  1483. }
  1484. $fileName = $this->getStylePropertyDevelopmentFileName($definition['property_name'], $property['style_id']);
  1485. // TODO: in the future, the writing system could be split into writing out definition and values in separate functions.
  1486. // it would make a clearer code path.
  1487. $document = new DOMDocument('1.0', 'utf-8');
  1488. $document->formatOutput = true;
  1489. $node = $document->createElement('property');
  1490. $document->appendChild($node);
  1491. $includeDefinition = ($definition['definition_style_id'] == $property['style_id']);
  1492. if ($includeDefinition)
  1493. {
  1494. $node->setAttribute('definition', 1);
  1495. $node->setAttribute('group_name', $definition['group_name']);
  1496. $node->setAttribute('property_type', $definition['property_type']);
  1497. $node->setAttribute('scalar_type', $definition['scalar_type']);
  1498. $node->setAttribute('scalar_parameters', $definition['scalar_parameters']);
  1499. $components = unserialize($definition['css_components']);
  1500. $node->setAttribute('css_components', implode(',', array_keys($components)));
  1501. $node->setAttribute('display_order', $definition['display_order']);
  1502. $node->setAttribute('sub_group', $definition['sub_group']);
  1503. XenForo_Helper_DevelopmentXml::createDomElements($node, array(
  1504. 'title' => isset($definition['masterTitle']) ? $definition['masterTitle'] : strval($definition['title']),
  1505. 'description' => isset($definition['masterDescription']) ? $definition['masterDescription'] : strval($definition['description'])
  1506. ));
  1507. }
  1508. else
  1509. {
  1510. $node->setAttribute('property_type', $definition['property_type']);
  1511. }
  1512. $valueNode = $document->createElement('value');
  1513. if ($definition['property_type'] == 'scalar')
  1514. {
  1515. $valueNode->appendChild($document->createCDATASection($property['property_value']));
  1516. }
  1517. else
  1518. {
  1519. $jsonValue = json_encode(unserialize($property['property_value']));
  1520. // format one value per line
  1521. $jsonValue = preg_replace('/(?<!:|\\\\)","/', '",' . "\n" . '"', $jsonValue);
  1522. $valueNode->appendChild($document->createCDATASection($jsonValue));
  1523. }
  1524. $node->appendChild($valueNode);
  1525. $document->save($fileName);
  1526. }
  1527. /**
  1528. * Moves a style property development file, for renames.
  1529. *
  1530. * @param array $oldDefinition
  1531. * @param array $newDefinition
  1532. */
  1533. public function moveStylePropertyDevelopmentFile(array $oldDefinition, array $newDefinition)
  1534. {
  1535. if ($oldDefinition['addon_id'] != 'XenForo' || $oldDefinition['definition_style_id'] > 0)
  1536. {
  1537. return;
  1538. }
  1539. if ($newDefinition['definition_style_id'] > 0)
  1540. {
  1541. $this->deleteStylePropertyDevelopmentFile($oldDefinition['property_name'], $oldDefinition['definition_style_id']);
  1542. return;
  1543. }
  1544. $oldFile = $this->getStylePropertyDevelopmentFileName($oldDefinition['property_name'], $oldDefinition['definition_style_id']);
  1545. $newFile = $this->getStylePropertyDevelopmentFileName($newDefinition['property_name'], $newDefinition['definition_style_id']);
  1546. if (file_exists($oldFile))
  1547. {
  1548. rename($oldFile, $newFile);
  1549. }
  1550. }
  1551. /**
  1552. * Updates the definition-related elements of the style property development file
  1553. * without touching the property value.
  1554. *
  1555. * @param array $definition
  1556. */
  1557. public function updateStylePropertyDevelopmentFile(array $definition)
  1558. {
  1559. if ($definition['addon_id'] != 'XenForo' || $definition['definition_style_id'] > 0)
  1560. {
  1561. return;
  1562. }
  1563. $fileName = $this->getStylePropertyDevelopmentFileName($definition['property_name'], $definition['definition_style_id']);
  1564. if (file_exists($fileName))
  1565. {
  1566. $document = new SimpleXMLElement($fileName, 0, true);
  1567. if ((string)$document['definition'])
  1568. {
  1569. $value = (string)$document->value;
  1570. if ((string)$document['property_type'] == 'css')
  1571. {
  1572. $value = serialize(json_decode($value, true));
  1573. }
  1574. $property = $definition + array(
  1575. 'style_id' => $definition['definition_style_id'],
  1576. 'property_value' => $value
  1577. );
  1578. $this->writeStylePropertyDevelopmentFile($definition, $property);
  1579. }
  1580. }
  1581. }
  1582. /**
  1583. * Deletes a style property development file.
  1584. *
  1585. * @param string $name
  1586. * @param integer $styleId Definition style ID.
  1587. */
  1588. public function deleteStylePropertyDevelopmentFile($name, $styleId)
  1589. {
  1590. if ($styleId > 0)
  1591. {
  1592. return;
  1593. }
  1594. $fileName = $this->getStylePropertyDevelopmentFileName($name, $styleId);
  1595. if (file_exists($fileName))
  1596. {
  1597. unlink($fileName);
  1598. }
  1599. }
  1600. /**
  1601. * Deletes the style property file if needed. This is used when reverting a
  1602. * customized property; the file is only deleted if we're deleting the property
  1603. * from a style other than the one it was created in.
  1604. *
  1605. * @param array $definition
  1606. * @param array $property
  1607. */
  1608. public function deleteStylePropertyDevelopmentFileIfNeeded(array $definition, array $property)
  1609. {
  1610. if ($property['style_id'] > 0)
  1611. {
  1612. return;
  1613. }
  1614. if ($property['style_id'] == $definition['definition_style_id'])
  1615. {
  1616. // assume this will be deleted by the definition
  1617. return;
  1618. }
  1619. $this->deleteStylePropertyDevelopmentFile($definition['property_name'], $property['style_id']);
  1620. }
  1621. /**
  1622. * Imports style properties/groups from the development location. This only imports
  1623. * one style's worth of properties at a time.
  1624. *
  1625. * @param integer $styleId Style to import for
  1626. */
  1627. public function importStylePropertiesFromDevelopment($styleId)
  1628. {
  1629. if ($styleId > 0)
  1630. {
  1631. return;

Large files files are truncated, but you can click here to view the full file