Html.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2013, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\template\helper;
  9. /**
  10. * A template helper that assists in generating HTML content. Accessible in templates via
  11. * `$this->html`, which will auto-load this helper into the rendering context. For examples of how
  12. * to use this helper, see the documentation for a specific method. For a list of the
  13. * template strings this helper uses, see the `$_strings` property.
  14. */
  15. class Html extends \lithium\template\Helper {
  16. /**
  17. * String templates used by this helper.
  18. *
  19. * @var array
  20. */
  21. protected $_strings = array(
  22. 'block' => '<div{:options}>{:content}</div>',
  23. 'block-end' => '</div>',
  24. 'block-start' => '<div{:options}>',
  25. 'charset' => '<meta charset="{:encoding}" />',
  26. 'image' => '<img src="{:path}"{:options} />',
  27. 'js-block' => '<script type="text/javascript"{:options}>{:content}</script>',
  28. 'js-end' => '</script>',
  29. 'js-start' => '<script type="text/javascript"{:options}>',
  30. 'link' => '<a href="{:url}"{:options}>{:title}</a>',
  31. 'list' => '<ul{:options}>{:content}</ul>',
  32. 'list-item' => '<li{:options}>{:content}</li>',
  33. 'meta' => '<meta{:options}/>',
  34. 'meta-link' => '<link href="{:url}"{:options} />',
  35. 'para' => '<p{:options}>{:content}</p>',
  36. 'para-start' => '<p{:options}>',
  37. 'script' => '<script type="text/javascript" src="{:path}"{:options}></script>',
  38. 'style' => '<style type="text/css"{:options}>{:content}</style>',
  39. 'style-import' => '<style type="text/css"{:options}>@import url({:url});</style>',
  40. 'style-link' => '<link rel="{:type}" type="text/css" href="{:path}"{:options} />',
  41. 'table-header' => '<th{:options}>{:content}</th>',
  42. 'table-header-row' => '<tr{:options}>{:content}</tr>',
  43. 'table-cell' => '<td{:options}>{:content}</td>',
  44. 'table-row' => '<tr{:options}>{:content}</tr>',
  45. 'tag' => '<{:name}{:options}>{:content}</{:name}>',
  46. 'tag-end' => '</{:name}>',
  47. 'tag-start' => '<{:name}{:options}>'
  48. );
  49. /**
  50. * Data used for custom <meta /> links.
  51. *
  52. * @var array
  53. */
  54. protected $_metaLinks = array(
  55. 'atom' => array('type' => 'application/atom+xml', 'rel' => 'alternate'),
  56. 'rss' => array('type' => 'application/rss+xml', 'rel' => 'alternate'),
  57. 'icon' => array('type' => 'image/x-icon', 'rel' => 'icon')
  58. );
  59. /**
  60. * List of meta tags to cache and to output.
  61. *
  62. * @var array
  63. * @see lithium\template\helper\Html::meta()
  64. */
  65. protected $_metaList = array();
  66. /**
  67. * Used by output handlers to calculate asset paths in conjunction with the `Media` class.
  68. *
  69. * @var array
  70. * @see lithium\net\http\Media
  71. */
  72. public $contentMap = array(
  73. 'script' => 'js',
  74. 'style' => 'css',
  75. 'image' => 'image',
  76. '_metaLink' => 'generic'
  77. );
  78. /**
  79. * Returns a charset meta-tag for declaring the encoding of the document.
  80. *
  81. * The terms character set (here: charset) and character encoding (here:
  82. * encoding) were historically synonymous. The terms now have related but
  83. * distinct meanings. Whenever possible Lithium tries to use precise
  84. * terminology. Since HTML uses the term `charset` we expose this method
  85. * under the exact same name. This caters to the expectation towards a HTML
  86. * helper. However the rest of the framework will use the term `encoding`
  87. * when talking about character encoding.
  88. *
  89. * It is suggested that uppercase letters should be used when specifying
  90. * the encoding. HTML specs don't require it to be uppercase and sites in
  91. * the wild most often use the lowercase variant. On the other hand must
  92. * XML parsers (those may not be relevant in this context anyway) not
  93. * support lowercase encodings. This and the fact that IANA lists only
  94. * encodings with uppercase characters led to the above suggestion.
  95. *
  96. * @see lithium\net\http\Response::$encoding
  97. * @link http://www.iana.org/assignments/character-sets
  98. * @param string $encoding The character encoding to be used in the meta tag.
  99. * Defaults to the encoding of the `Response` object attached to the
  100. * current context. The default encoding of that object is `UTF-8`.
  101. * The string given here is not manipulated in any way, so that
  102. * values are rendered literally. Also see above note about casing.
  103. * @return string A meta tag containing the specified encoding (literally).
  104. */
  105. public function charset($encoding = null) {
  106. if ($response = $this->_context->response()) {
  107. $encoding = $encoding ?: $response->encoding;
  108. }
  109. return $this->_render(__METHOD__, 'charset', compact('encoding'));
  110. }
  111. /**
  112. * Creates an HTML link (`<a />`) or a document meta-link (`<link />`).
  113. *
  114. * If `$url` starts with `'http://'` or `'https://'`, this is treated as an external link.
  115. * Otherwise, it is treated as a path to controller/action and parsed using
  116. * the `Router::match()` method (where `Router` is the routing class dependency specified by
  117. * the rendering context, i.e. `lithium\template\view\Renderer::$_classes`).
  118. *
  119. * If `$url` is empty, `$title` is used in its place.
  120. *
  121. * @param string $title The content to be wrapped by an `<a />` tag,
  122. * or the `title` attribute of a meta-link `<link />`.
  123. * @param mixed $url Can be a string representing a URL relative to the base of your Lithium
  124. * application, an external URL (starts with `'http://'` or `'https://'`), an
  125. * anchor name starting with `'#'` (i.e. `'#top'`), or an array defining a set
  126. * of request parameters that should be matched against a route in `Router`.
  127. * @param array $options The available options are:
  128. * - `'escape'` _boolean_: Whether or not the title content should be escaped.
  129. * Defaults to `true`.
  130. * - `'type'` _string_: The meta-link type, which is looked up in
  131. * `Html::$_metaLinks`. By default it accepts `atom`, `rss` and `icon`. If a `type`
  132. * is specified, this method will render a document meta-link (`<link />`),
  133. * instead of an HTML link (`<a />`).
  134. * - any other options specified are rendered as HTML attributes of the element.
  135. * @return string Returns an `<a />` or `<link />` element.
  136. */
  137. public function link($title, $url = null, array $options = array()) {
  138. $defaults = array('escape' => true, 'type' => null);
  139. list($scope, $options) = $this->_options($defaults, $options);
  140. if (isset($scope['type']) && $type = $scope['type']) {
  141. $options += compact('title');
  142. return $this->_metaLink($type, $url, $options);
  143. }
  144. $url = is_null($url) ? $title : $url;
  145. return $this->_render(__METHOD__, 'link', compact('title', 'url', 'options'), $scope);
  146. }
  147. /**
  148. * Returns a JavaScript include tag (`<script />` element). If the filename is prefixed with
  149. * `'/'`, the path will be relative to the base path of your application. Otherwise, the path
  150. * will be relative to your JavaScript path, usually `webroot/js`.
  151. *
  152. * @link http://lithify.me/docs/manual/handling-http-requests/views.wiki
  153. * @param mixed $path String The name of a JavaScript file, or an array of names.
  154. * @param array $options Available options are:
  155. * - `'inline'` _boolean_: Whether or not the `<script />` element should be output
  156. * inline. When set to false, the `scripts()` handler prints out the script, and
  157. * other specified scripts to be included in the layout. Defaults to `true`.
  158. * This is useful when page-specific scripts are created inline in the page, and
  159. * you'd like to place them in the `<head />` along with your other scripts.
  160. * - any other options specified are rendered as HTML attributes of the element.
  161. * @return string
  162. * @filter This method can be filtered.
  163. */
  164. public function script($path, array $options = array()) {
  165. $defaults = array('inline' => true);
  166. list($scope, $options) = $this->_options($defaults, $options);
  167. if (is_array($path)) {
  168. foreach ($path as $i => $item) {
  169. $path[$i] = $this->script($item, $scope);
  170. }
  171. return ($scope['inline']) ? join("\n\t", $path) . "\n" : null;
  172. }
  173. $m = __METHOD__;
  174. $params = compact('path', 'options');
  175. $script = $this->_filter(__METHOD__, $params, function($self, $params, $chain) use ($m) {
  176. return $self->invokeMethod('_render', array($m, 'script', $params));
  177. });
  178. if ($scope['inline']) {
  179. return $script;
  180. }
  181. if ($this->_context) {
  182. $this->_context->scripts($script);
  183. }
  184. }
  185. /**
  186. * Creates a `<link />` element for CSS stylesheets or a `<style />` tag. If the filename is
  187. * prefixed with `'/'`, the path will be relative to the base path of your application.
  188. * Otherwise, the path will be relative to your stylesheets path, usually `webroot/css`.
  189. *
  190. * @param mixed $path The name of a CSS stylesheet in `/app/webroot/css`, or an array
  191. * containing names of CSS stylesheets in that directory.
  192. * @param array $options Available options are:
  193. * - `'inline'` _boolean_: Whether or not the `<style />` element should be output
  194. * inline. When set to `false`, the `styles()` handler prints out the styles,
  195. * and other specified styles to be included in the layout. Defaults to `true`.
  196. * This is useful when page-specific styles are created inline in the page, and
  197. * you'd like to place them in
  198. * the `<head />` along with your other styles.
  199. * - `'type'` _string_: By default, accepts `stylesheet` or `import`, which
  200. * respectively correspond to `style-link` and `style-import` strings templates
  201. * defined in `Html::$_strings`.
  202. * - any other options specified are rendered as HTML attributes of the element.
  203. * @return string CSS <link /> or <style /> tag, depending on the type of link.
  204. * @filter This method can be filtered.
  205. */
  206. public function style($path, array $options = array()) {
  207. $defaults = array('type' => 'stylesheet', 'inline' => true);
  208. list($scope, $options) = $this->_options($defaults, $options);
  209. if (is_array($path)) {
  210. foreach ($path as $i => $item) {
  211. $path[$i] = $this->style($item, $scope);
  212. }
  213. return ($scope['inline']) ? join("\n\t", $path) . "\n" : null;
  214. }
  215. $method = __METHOD__;
  216. $type = $scope['type'];
  217. $params = compact('type', 'path', 'options');
  218. $filter = function($self, $params, $chain) use ($defaults, $method) {
  219. $template = ($params['type'] === 'import') ? 'style-import' : 'style-link';
  220. return $self->invokeMethod('_render', array($method, $template, $params));
  221. };
  222. $style = $this->_filter($method, $params, $filter);
  223. if ($scope['inline']) {
  224. return $style;
  225. }
  226. if ($this->_context) {
  227. $this->_context->styles($style);
  228. }
  229. }
  230. /**
  231. * Creates a tag for the `<head>` section of your document.
  232. *
  233. * If there is a rendering context, then it also pushes the resulting tag to it.
  234. *
  235. * The `$options` must match the named parameters from `$_strings` for the
  236. * given `$tag`.
  237. *
  238. * @param string $tag the name of a key in `$_strings`
  239. * @param array $options the options required by `$_strings[$tag]`
  240. * @return mixed a string if successful, otherwise `null`
  241. * @filter This method can be filtered.
  242. */
  243. public function head($tag, array $options) {
  244. if (!isset($this->_strings[$tag])) {
  245. return null;
  246. }
  247. $method = __METHOD__;
  248. $filter = function($self, $options, $chain) use ($method, $tag) {
  249. return $self->invokeMethod('_render', array($method, $tag, $options));
  250. };
  251. $head = $this->_filter($method, $options, $filter);
  252. if ($this->_context) {
  253. $this->_context->head($head);
  254. }
  255. return $head;
  256. }
  257. /**
  258. * Creates a formatted `<img />` element.
  259. *
  260. * @param string $path Path to the image file. If the filename is prefixed with
  261. * `'/'`, the path will be relative to the base path of your application.
  262. * Otherwise the path will be relative to the images directory, usually
  263. * `app/webroot/img/`. If the name starts with `'http://'`, this is treated
  264. * as an external url used as the `src` attribute.
  265. * @param array $options Array of HTML attributes.
  266. * @return string Returns a formatted `<img />` tag.
  267. * @filter This method can be filtered.
  268. */
  269. public function image($path, array $options = array()) {
  270. $defaults = array('alt' => '');
  271. $options += $defaults;
  272. $path = is_array($path) ? $this->_context->url($path) : $path;
  273. $params = compact('path', 'options');
  274. $method = __METHOD__;
  275. return $this->_filter($method, $params, function($self, $params, $chain) use ($method) {
  276. return $self->invokeMethod('_render', array($method, 'image', $params));
  277. });
  278. }
  279. /**
  280. * Creates a link to an external resource.
  281. *
  282. * @param string $type The title of the external resource
  283. * @param mixed $url The address of the external resource or string for content attribute
  284. * @param array $options Other attributes for the generated tag. If the type attribute
  285. * is 'html', 'rss', 'atom', or 'icon', the mime-type is returned.
  286. * @return string
  287. */
  288. protected function _metaLink($type, $url = null, array $options = array()) {
  289. $options += isset($this->_metaLinks[$type]) ? $this->_metaLinks[$type] : array();
  290. if ($type === 'icon') {
  291. $url = $url ?: 'favicon.ico';
  292. $standard = $this->_render(__METHOD__, 'meta-link', compact('url', 'options'), array(
  293. 'handlers' => array('url' => 'path')
  294. ));
  295. $options['rel'] = 'shortcut icon';
  296. $ieFix = $this->_render(__METHOD__, 'meta-link', compact('url', 'options'), array(
  297. 'handlers' => array('url' => 'path')
  298. ));
  299. return "{$standard}\n\t{$ieFix}";
  300. }
  301. return $this->_render(__METHOD__, 'meta-link', compact('url', 'options'), array(
  302. 'handlers' => array()
  303. ));
  304. }
  305. }
  306. ?>