StringTemplate.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. <?php
  2. App::uses('PhpReader', 'Configure');
  3. /**
  4. * Provides an interface for registering and inserting
  5. * content into simple logic-less string templates.
  6. *
  7. * Used by several helpers to provide simple flexible templates
  8. * for generating HTML and other content.
  9. *
  10. * Backported from CakePHP3.0
  11. */
  12. class StringTemplate {
  13. /**
  14. * List of attributes that can be made compact.
  15. *
  16. * @var array
  17. */
  18. protected $_compactAttributes = [
  19. 'compact', 'checked', 'declare', 'readonly', 'disabled', 'selected',
  20. 'defer', 'ismap', 'nohref', 'noshade', 'nowrap', 'multiple', 'noresize',
  21. 'autoplay', 'controls', 'loop', 'muted', 'required', 'novalidate', 'formnovalidate'
  22. ];
  23. /**
  24. * The default templates this instance holds.
  25. *
  26. * @var array
  27. */
  28. protected $_defaultConfig = [
  29. 'attribute' => '{{name}}="{{value}}"',
  30. 'compactAttribute' => '{{name}}="{{value}}"',
  31. ];
  32. protected $_config;
  33. /**
  34. * Contains the list of compiled templates
  35. *
  36. * @var array
  37. */
  38. protected $_compiled = [];
  39. /**
  40. * Constructor.
  41. *
  42. * @param array $config A set of templates to add.
  43. */
  44. public function __construct(array $config = []) {
  45. $this->config($config);
  46. }
  47. /**
  48. * StringTemplate::config()
  49. *
  50. * @param string|array|null $key The key to get/set, or a complete array of configs.
  51. * @param mixed|null $value The value to set.
  52. * @param bool $merge Whether to merge or overwrite existing config defaults to true.
  53. * @return mixed Config value being read, or the whole array itself on write operations.
  54. */
  55. public function config($key = null, $value = null, $merge = true) {
  56. if ($key === null) {
  57. return $this->_config;
  58. }
  59. if (is_array($key)) {
  60. if ($merge) {
  61. $this->_config = $key + $this->_defaultConfig;
  62. } else {
  63. $this->_config = $key;
  64. }
  65. return;
  66. }
  67. if (func_num_args() >= 2) {
  68. if ($value === null) {
  69. unset($this->_config[$key]);
  70. } else {
  71. $this->_config[$key] = $value;
  72. }
  73. return $this->_config;
  74. }
  75. if (!isset($this->_config[$key])) {
  76. return null;
  77. }
  78. return $this->_config[$key];
  79. }
  80. /**
  81. * Registers a list of templates by name
  82. *
  83. * ### Example:
  84. *
  85. * {{{
  86. * $templater->add([
  87. * 'link' => '<a href="{{url}}">{{title}}</a>'
  88. * 'button' => '<button>{{text}}</button>'
  89. * ]);
  90. * }}}
  91. *
  92. * @param array an associative list of named templates
  93. * @return \Cake\View\StringTemplate same instance
  94. */
  95. public function add(array $templates) {
  96. $this->config($templates);
  97. $this->_compiled = array_diff_key($this->_compiled, $templates);
  98. return $this;
  99. }
  100. /**
  101. * Load a config file containing templates.
  102. *
  103. * Template files should define a `$config` variable containing
  104. * all the templates to load. Loaded templates will be merged with existing
  105. * templates.
  106. *
  107. * @param string $file The file to load
  108. * @return void
  109. */
  110. public function load($file) {
  111. $loader = new PhpReader();
  112. $templates = $loader->read($file);
  113. $this->add($templates);
  114. }
  115. /**
  116. * Remove the named template.
  117. *
  118. * @param string $name The template to remove.
  119. * @return void
  120. */
  121. public function remove($name) {
  122. $this->config($name, null);
  123. unset($this->_compiled[$name]);
  124. }
  125. /**
  126. * Returns an array containing the compiled template to be used with
  127. * the sprintf function and a list of placeholder names that were found
  128. * in the template in the order that they should be replaced.
  129. *
  130. * @param string $name The compiled template info
  131. * @return array
  132. */
  133. public function compile($name) {
  134. if (isset($this->_compiled[$name])) {
  135. return $this->_compiled[$name];
  136. }
  137. $template = $this->config($name);
  138. if ($template === null) {
  139. return $this->_compiled[$name] = [null, null];
  140. }
  141. preg_match_all('#\{\{(\w+)\}\}#', $template, $matches);
  142. return $this->_compiled[$name] = [
  143. str_replace($matches[0], '%s', $template),
  144. $matches[1]
  145. ];
  146. }
  147. /**
  148. * Format a template string with $data
  149. *
  150. * @param string $name The template name.
  151. * @param array $data The data to insert.
  152. * @return string
  153. */
  154. public function format($name, array $data) {
  155. list($template, $placeholders) = $this->compile($name);
  156. if ($template === null) {
  157. return '';
  158. }
  159. $replace = [];
  160. foreach ($placeholders as $placeholder) {
  161. $replace[] = isset($data[$placeholder]) ? $data[$placeholder] : null;
  162. }
  163. return vsprintf($template, $replace);
  164. }
  165. /**
  166. * Returns a space-delimited string with items of the $options array. If a key
  167. * of $options array happens to be one of those listed
  168. * in `StringTemplate::$_compactAttributes` and its value is one of:
  169. *
  170. * - '1' (string)
  171. * - 1 (integer)
  172. * - true (boolean)
  173. * - 'true' (string)
  174. *
  175. * Then the value will be reset to be identical with key's name.
  176. * If the value is not one of these 4, the parameter is not output.
  177. *
  178. * 'escape' is a special option in that it controls the conversion of
  179. * attributes to their html-entity encoded equivalents. Set to false to disable html-encoding.
  180. *
  181. * If value for any option key is set to `null` or `false`, that option will be excluded from output.
  182. *
  183. * This method uses the 'attribute' and 'compactAttribute' templates. Each of
  184. * these templates uses the `name` and `value` variables. You can modify these
  185. * templates to change how attributes are formatted.
  186. *
  187. * @param array $options Array of options.
  188. * @param null|array $exclude Array of options to be excluded, the options here will not be part of the return.
  189. * @return string Composed attributes.
  190. */
  191. public function formatAttributes($options, $exclude = null) {
  192. $insertBefore = ' ';
  193. $options = (array)$options + ['escape' => true];
  194. if (!is_array($exclude)) {
  195. $exclude = [];
  196. }
  197. $exclude = ['escape' => true, 'idPrefix' => true] + array_flip($exclude);
  198. $escape = $options['escape'];
  199. $attributes = [];
  200. foreach ($options as $key => $value) {
  201. if (!isset($exclude[$key]) && $value !== false && $value !== null) {
  202. $attributes[] = $this->_formatAttribute($key, $value, $escape);
  203. }
  204. }
  205. $out = trim(implode(' ', $attributes));
  206. return $out ? $insertBefore . $out : '';
  207. }
  208. /**
  209. * Formats an individual attribute, and returns the string value of the composed attribute.
  210. * Works with minimized attributes that have the same value as their name such as 'disabled' and 'checked'
  211. *
  212. * @param string $key The name of the attribute to create
  213. * @param string|array $value The value of the attribute to create.
  214. * @param bool $escape Define if the value must be escaped
  215. * @return string The composed attribute.
  216. */
  217. protected function _formatAttribute($key, $value, $escape = true) {
  218. if (is_array($value)) {
  219. $value = implode(' ', $value);
  220. }
  221. if (is_numeric($key)) {
  222. return $this->format('compactAttribute', [
  223. 'name' => $value,
  224. 'value' => $value
  225. ]);
  226. }
  227. $truthy = [1, '1', true, 'true', $key];
  228. $isMinimized = in_array($key, $this->_compactAttributes);
  229. if ($isMinimized && in_array($value, $truthy, true)) {
  230. return $this->format('compactAttribute', [
  231. 'name' => $key,
  232. 'value' => $key
  233. ]);
  234. }
  235. if ($isMinimized) {
  236. return '';
  237. }
  238. return $this->format('attribute', [
  239. 'name' => $key,
  240. 'value' => $escape ? h($value) : $value
  241. ]);
  242. }
  243. }