HtmlHelper.php 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 0.9.1
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\View\Helper;
  16. use Cake\Core\Configure;
  17. use Cake\Network\Response;
  18. use Cake\View\Helper;
  19. use Cake\View\StringTemplateTrait;
  20. use Cake\View\View;
  21. /**
  22. * Html Helper class for easy use of HTML widgets.
  23. *
  24. * HtmlHelper encloses all methods needed while working with HTML pages.
  25. *
  26. * @link http://book.cakephp.org/3.0/en/views/helpers/html.html
  27. */
  28. class HtmlHelper extends Helper {
  29. use StringTemplateTrait;
  30. /**
  31. * List of helpers used by this helper
  32. *
  33. * @var array
  34. */
  35. public $helpers = ['Url'];
  36. /**
  37. * Reference to the Response object
  38. *
  39. * @var \Cake\Network\Response
  40. */
  41. public $response;
  42. /**
  43. * Default config for this class
  44. *
  45. * @var array
  46. */
  47. protected $_defaultConfig = [
  48. 'templates' => [
  49. 'meta' => '<meta{{attrs}}/>',
  50. 'metalink' => '<link href="{{url}}"{{attrs}}/>',
  51. 'link' => '<a href="{{url}}"{{attrs}}>{{content}}</a>',
  52. 'mailto' => '<a href="mailto:{{url}}"{{attrs}}>{{content}}</a>',
  53. 'image' => '<img src="{{url}}"{{attrs}}/>',
  54. 'tableheader' => '<th{{attrs}}>{{content}}</th>',
  55. 'tableheaderrow' => '<tr{{attrs}}>{{content}}</tr>',
  56. 'tablecell' => '<td{{attrs}}>{{content}}</td>',
  57. 'tablerow' => '<tr{{attrs}}>{{content}}</tr>',
  58. 'block' => '<div{{attrs}}>{{content}}</div>',
  59. 'blockstart' => '<div{{attrs}}>',
  60. 'blockend' => '</div>',
  61. 'tag' => '<{{tag}}{{attrs}}>{{content}}</{{tag}}>',
  62. 'tagstart' => '<{{tag}}{{attrs}}>',
  63. 'tagend' => '</{{tag}}>',
  64. 'tagselfclosing' => '<{{tag}}{{attrs}}/>',
  65. 'para' => '<p{{attrs}}>{{content}}</p>',
  66. 'parastart' => '<p{{attrs}}>',
  67. 'css' => '<link rel="{{rel}}" href="{{url}}"{{attrs}}/>',
  68. 'style' => '<style{{attrs}}>{{content}}</style>',
  69. 'charset' => '<meta http-equiv="Content-Type" content="text/html; charset={{charset}}"/>',
  70. 'ul' => '<ul{{attrs}}>{{content}}</ul>',
  71. 'ol' => '<ol{{attrs}}>{{content}}</ol>',
  72. 'li' => '<li{{attrs}}>{{content}}</li>',
  73. 'javascriptblock' => '<script{{attrs}}>{{content}}</script>',
  74. 'javascriptstart' => '<script>',
  75. 'javascriptlink' => '<script src="{{url}}"{{attrs}}></script>',
  76. 'javascriptend' => '</script>'
  77. ]
  78. ];
  79. /**
  80. * Breadcrumbs.
  81. *
  82. * @var array
  83. */
  84. protected $_crumbs = array();
  85. /**
  86. * Names of script & css files that have been included once
  87. *
  88. * @var array
  89. */
  90. protected $_includedAssets = array();
  91. /**
  92. * Options for the currently opened script block buffer if any.
  93. *
  94. * @var array
  95. */
  96. protected $_scriptBlockOptions = array();
  97. /**
  98. * Document type definitions
  99. *
  100. * @var array
  101. */
  102. protected $_docTypes = array(
  103. 'html4-strict' => '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">',
  104. 'html4-trans' => '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
  105. 'html4-frame' => '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">',
  106. 'html5' => '<!DOCTYPE html>',
  107. 'xhtml-strict' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
  108. 'xhtml-trans' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
  109. 'xhtml-frame' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
  110. 'xhtml11' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
  111. );
  112. /**
  113. * Constructor
  114. *
  115. * ### Settings
  116. *
  117. * - `templates` Either a filename to a config containing templates.
  118. * Or an array of templates to load. See Cake\View\StringTemplate for
  119. * template formatting.
  120. *
  121. * ### Customizing tag sets
  122. *
  123. * Using the `templates` option you can redefine the tag HtmlHelper will use.
  124. *
  125. * @param \Cake\View\View $View The View this helper is being attached to.
  126. * @param array $config Configuration settings for the helper.
  127. */
  128. public function __construct(View $View, array $config = array()) {
  129. parent::__construct($View, $config);
  130. $this->response = $this->_View->response ?: new Response();
  131. }
  132. /**
  133. * Adds a link to the breadcrumbs array.
  134. *
  135. * @param string $name Text for link
  136. * @param string|null $link URL for link (if empty it won't be a link)
  137. * @param string|array $options Link attributes e.g. array('id' => 'selected')
  138. * @return $this
  139. * @see HtmlHelper::link() for details on $options that can be used.
  140. * @link http://book.cakephp.org/3.0/en/views/helpers/html.html#creating-breadcrumb-trails-with-htmlhelper
  141. */
  142. public function addCrumb($name, $link = null, array $options = array()) {
  143. $this->_crumbs[] = array($name, $link, $options);
  144. return $this;
  145. }
  146. /**
  147. * Returns a doctype string.
  148. *
  149. * Possible doctypes:
  150. *
  151. * - html4-strict: HTML4 Strict.
  152. * - html4-trans: HTML4 Transitional.
  153. * - html4-frame: HTML4 Frameset.
  154. * - html5: HTML5. Default value.
  155. * - xhtml-strict: XHTML1 Strict.
  156. * - xhtml-trans: XHTML1 Transitional.
  157. * - xhtml-frame: XHTML1 Frameset.
  158. * - xhtml11: XHTML1.1.
  159. *
  160. * @param string $type Doctype to use.
  161. * @return string|null Doctype string
  162. * @link http://book.cakephp.org/3.0/en/views/helpers/html.html#creating-doctype-tags
  163. */
  164. public function docType($type = 'html5') {
  165. if (isset($this->_docTypes[$type])) {
  166. return $this->_docTypes[$type];
  167. }
  168. return null;
  169. }
  170. /**
  171. * Creates a link to an external resource and handles basic meta tags
  172. *
  173. * Create a meta tag that is output inline:
  174. *
  175. * `$this->Html->meta('icon', 'favicon.ico');
  176. *
  177. * Append the meta tag to custom view block "meta":
  178. *
  179. * `$this->Html->meta('description', 'A great page', array('block' => true));`
  180. *
  181. * Append the meta tag to custom view block:
  182. *
  183. * `$this->Html->meta('description', 'A great page', array('block' => 'metaTags'));`
  184. *
  185. * Create a custom meta tag:
  186. *
  187. * `$this->Html->meta(['property' => 'og:site_name', 'content' => 'CakePHP']);`
  188. *
  189. * ### Options
  190. *
  191. * - `block` - Set to true to append output to view block "meta" or provide
  192. * custom block name.
  193. *
  194. * @param string|array $type The title of the external resource
  195. * @param string|array|null $content The address of the external resource or string for content attribute
  196. * @param array $options Other attributes for the generated tag. If the type attribute is html,
  197. * rss, atom, or icon, the mime-type is returned.
  198. * @return string A completed `<link />` element.
  199. * @link http://book.cakephp.org/3.0/en/views/helpers/html.html#creating-meta-tags
  200. */
  201. public function meta($type, $content = null, array $options = array()) {
  202. $options += array('block' => null);
  203. if (!is_array($type)) {
  204. $types = array(
  205. 'rss' => array('type' => 'application/rss+xml', 'rel' => 'alternate', 'title' => $type, 'link' => $content),
  206. 'atom' => array('type' => 'application/atom+xml', 'title' => $type, 'link' => $content),
  207. 'icon' => array('type' => 'image/x-icon', 'rel' => 'icon', 'link' => $content),
  208. 'keywords' => array('name' => 'keywords', 'content' => $content),
  209. 'description' => array('name' => 'description', 'content' => $content),
  210. 'robots' => array('name' => 'robots', 'content' => $content),
  211. 'viewport' => array('name' => 'viewport', 'content' => $content),
  212. );
  213. if ($type === 'icon' && $content === null) {
  214. $types['icon']['link'] = 'favicon.ico';
  215. }
  216. if (isset($types[$type])) {
  217. $type = $types[$type];
  218. } elseif (!isset($options['type']) && $content !== null) {
  219. if (is_array($content) && isset($content['_ext'])) {
  220. $type = $types[$content['_ext']];
  221. } else {
  222. $type = ['name' => $type, 'content' => $content];
  223. }
  224. } elseif (isset($options['type']) && isset($types[$options['type']])) {
  225. $type = $types[$options['type']];
  226. unset($options['type']);
  227. } else {
  228. $type = [];
  229. }
  230. }
  231. $options += $type;
  232. $out = null;
  233. if (isset($options['link'])) {
  234. $options['link'] = $this->Url->assetUrl($options['link']);
  235. if (isset($options['rel']) && $options['rel'] === 'icon') {
  236. $out = $this->formatTemplate('metalink', [
  237. 'url' => $options['link'],
  238. 'attrs' => $this->templater()->formatAttributes($options, ['block', 'link'])
  239. ]);
  240. $options['rel'] = 'shortcut icon';
  241. }
  242. $out .= $this->formatTemplate('metalink', [
  243. 'url' => $options['link'],
  244. 'attrs' => $this->templater()->formatAttributes($options, ['block', 'link'])
  245. ]);
  246. } else {
  247. $out = $this->formatTemplate('meta', [
  248. 'attrs' => $this->templater()->formatAttributes($options, ['block', 'type'])
  249. ]);
  250. }
  251. if (empty($options['block'])) {
  252. return $out;
  253. }
  254. if ($options['block'] === true) {
  255. $options['block'] = __FUNCTION__;
  256. }
  257. $this->_View->append($options['block'], $out);
  258. }
  259. /**
  260. * Returns a charset META-tag.
  261. *
  262. * @param string|null $charset The character set to be used in the meta tag. If empty,
  263. * The App.encoding value will be used. Example: "utf-8".
  264. * @return string A meta tag containing the specified character set.
  265. * @link http://book.cakephp.org/3.0/en/views/helpers/html.html#creating-charset-tags
  266. */
  267. public function charset($charset = null) {
  268. if (empty($charset)) {
  269. $charset = strtolower(Configure::read('App.encoding'));
  270. }
  271. return $this->formatTemplate('charset', [
  272. 'charset' => (!empty($charset) ? $charset : 'utf-8')
  273. ]);
  274. }
  275. /**
  276. * Creates an HTML link.
  277. *
  278. * If $url starts with "http://" this is treated as an external link. Else,
  279. * it is treated as a path to controller/action and parsed with the
  280. * UrlHelper::url() method.
  281. *
  282. * If the $url is empty, $title is used instead.
  283. *
  284. * ### Options
  285. *
  286. * - `escape` Set to false to disable escaping of title and attributes.
  287. * - `escapeTitle` Set to false to disable escaping of title. Takes precedence
  288. * over value of `escape`)
  289. * - `confirm` JavaScript confirmation message.
  290. *
  291. * @param string $title The content to be wrapped by <a> tags.
  292. * @param string|array|null $url Cake-relative URL or array of URL parameters, or
  293. * external URL (starts with http://)
  294. * @param array $options Array of options and HTML attributes.
  295. * @return string An `<a />` element.
  296. * @link http://book.cakephp.org/3.0/en/views/helpers/html.html#creating-links
  297. */
  298. public function link($title, $url = null, array $options = array()) {
  299. $escapeTitle = true;
  300. if ($url !== null) {
  301. $url = $this->Url->build($url);
  302. } else {
  303. $url = $this->Url->build($title);
  304. $title = htmlspecialchars_decode($url, ENT_QUOTES);
  305. $title = h(urldecode($title));
  306. $escapeTitle = false;
  307. }
  308. if (isset($options['escapeTitle'])) {
  309. $escapeTitle = $options['escapeTitle'];
  310. unset($options['escapeTitle']);
  311. } elseif (isset($options['escape'])) {
  312. $escapeTitle = $options['escape'];
  313. }
  314. if ($escapeTitle === true) {
  315. $title = h($title);
  316. } elseif (is_string($escapeTitle)) {
  317. $title = htmlentities($title, ENT_QUOTES, $escapeTitle);
  318. }
  319. $confirmMessage = null;
  320. if (isset($options['confirm'])) {
  321. $confirmMessage = $options['confirm'];
  322. unset($options['confirm']);
  323. }
  324. if ($confirmMessage) {
  325. $options['onclick'] = $this->_confirm($confirmMessage, 'return true;', 'return false;', $options);
  326. }
  327. $templater = $this->templater();
  328. return $templater->format('link', [
  329. 'url' => $url,
  330. 'attrs' => $templater->formatAttributes($options),
  331. 'content' => $title
  332. ]);
  333. }
  334. /**
  335. * Creates a link element for CSS stylesheets.
  336. *
  337. * ### Usage
  338. *
  339. * Include one CSS file:
  340. *
  341. * `echo $this->Html->css('styles.css');`
  342. *
  343. * Include multiple CSS files:
  344. *
  345. * `echo $this->Html->css(array('one.css', 'two.css'));`
  346. *
  347. * Add the stylesheet to view block "css":
  348. *
  349. * `$this->Html->css('styles.css', array('block' => true));`
  350. *
  351. * Add the stylesheet to a custom block:
  352. *
  353. * `$this->Html->css('styles.css', array('block' => 'layoutCss'));`
  354. *
  355. * ### Options
  356. *
  357. * - `block` Set to true to append output to view block "css" or provide
  358. * custom block name.
  359. * - `once` Whether or not the css file should be checked for uniqueness. If true css
  360. * files will only be included once, use false to allow the same
  361. * css to be included more than once per request.
  362. * - `plugin` False value will prevent parsing path as a plugin
  363. * - `rel` Defaults to 'stylesheet'. If equal to 'import' the stylesheet will be imported.
  364. * - `fullBase` If true the URL will get a full address for the css file.
  365. *
  366. * @param string|array $path The name of a CSS style sheet or an array containing names of
  367. * CSS stylesheets. If `$path` is prefixed with '/', the path will be relative to the webroot
  368. * of your application. Otherwise, the path will be relative to your CSS path, usually webroot/css.
  369. * @param array $options Array of options and HTML arguments.
  370. * @return string CSS <link /> or <style /> tag, depending on the type of link.
  371. * @link http://book.cakephp.org/3.0/en/views/helpers/html.html#linking-to-css-files
  372. */
  373. public function css($path, array $options = array()) {
  374. $options += array('once' => true, 'block' => null, 'rel' => 'stylesheet');
  375. if (is_array($path)) {
  376. $out = '';
  377. foreach ($path as $i) {
  378. $out .= "\n\t" . $this->css($i, $options);
  379. }
  380. if (empty($options['block'])) {
  381. return $out . "\n";
  382. }
  383. return;
  384. }
  385. if (strpos($path, '//') !== false) {
  386. $url = $path;
  387. } else {
  388. $url = $this->Url->assetUrl($path, $options + array('pathPrefix' => Configure::read('App.cssBaseUrl'), 'ext' => '.css'));
  389. $options = array_diff_key($options, array('fullBase' => null, 'pathPrefix' => null));
  390. }
  391. if ($options['once'] && isset($this->_includedAssets[__METHOD__][$path])) {
  392. return '';
  393. }
  394. unset($options['once']);
  395. $this->_includedAssets[__METHOD__][$path] = true;
  396. $templater = $this->templater();
  397. if ($options['rel'] === 'import') {
  398. $out = $templater->format('style', [
  399. 'attrs' => $templater->formatAttributes($options, ['rel', 'block']),
  400. 'content' => '@import url(' . $url . ');',
  401. ]);
  402. } else {
  403. $out = $templater->format('css', [
  404. 'rel' => $options['rel'],
  405. 'url' => $url,
  406. 'attrs' => $templater->formatAttributes($options, ['rel', 'block']),
  407. ]);
  408. }
  409. if (empty($options['block'])) {
  410. return $out;
  411. }
  412. if ($options['block'] === true) {
  413. $options['block'] = __FUNCTION__;
  414. }
  415. $this->_View->append($options['block'], $out);
  416. }
  417. /**
  418. * Returns one or many `<script>` tags depending on the number of scripts given.
  419. *
  420. * If the filename is prefixed with "/", the path will be relative to the base path of your
  421. * application. Otherwise, the path will be relative to your JavaScript path, usually webroot/js.
  422. *
  423. * ### Usage
  424. *
  425. * Include one script file:
  426. *
  427. * `echo $this->Html->script('styles.js');`
  428. *
  429. * Include multiple script files:
  430. *
  431. * `echo $this->Html->script(array('one.js', 'two.js'));`
  432. *
  433. * Add the script file to a custom block:
  434. *
  435. * `$this->Html->script('styles.js', array('block' => 'bodyScript'));`
  436. *
  437. * ### Options
  438. *
  439. * - `block` Set to true to append output to view block "script" or provide
  440. * custom block name.
  441. * - `once` Whether or not the script should be checked for uniqueness. If true scripts will only be
  442. * included once, use false to allow the same script to be included more than once per request.
  443. * - `plugin` False value will prevent parsing path as a plugin
  444. * - `fullBase` If true the url will get a full address for the script file.
  445. *
  446. * @param string|array $url String or array of javascript files to include
  447. * @param array $options Array of options, and html attributes see above.
  448. * @return mixed String of `<script />` tags or null if block is specified in options
  449. * or if $once is true and the file has been included before.
  450. * @link http://book.cakephp.org/3.0/en/views/helpers/html.html#linking-to-javascript-files
  451. */
  452. public function script($url, array $options = array()) {
  453. $defaults = array('block' => null, 'once' => true);
  454. $options += $defaults;
  455. if (is_array($url)) {
  456. $out = '';
  457. foreach ($url as $i) {
  458. $out .= "\n\t" . $this->script($i, $options);
  459. }
  460. if (empty($options['block'])) {
  461. return $out . "\n";
  462. }
  463. return null;
  464. }
  465. if (strpos($url, '//') === false) {
  466. $url = $this->Url->assetUrl($url, $options + array('pathPrefix' => Configure::read('App.jsBaseUrl'), 'ext' => '.js'));
  467. $options = array_diff_key($options, array('fullBase' => null, 'pathPrefix' => null));
  468. }
  469. if ($options['once'] && isset($this->_includedAssets[__METHOD__][$url])) {
  470. return null;
  471. }
  472. $this->_includedAssets[__METHOD__][$url] = true;
  473. $out = $this->formatTemplate('javascriptlink', [
  474. 'url' => $url,
  475. 'attrs' => $this->templater()->formatAttributes($options, ['block', 'once']),
  476. ]);
  477. if (empty($options['block'])) {
  478. return $out;
  479. }
  480. if ($options['block'] === true) {
  481. $options['block'] = __FUNCTION__;
  482. }
  483. $this->_View->append($options['block'], $out);
  484. }
  485. /**
  486. * Wrap $script in a script tag.
  487. *
  488. * ### Options
  489. *
  490. * - `safe` (boolean) Whether or not the $script should be wrapped in <![CDATA[ ]]>
  491. * - `block` Set to true to append output to view block "script" or provide
  492. * custom block name.
  493. *
  494. * @param string $script The script to wrap
  495. * @param array $options The options to use. Options not listed above will be
  496. * treated as HTML attributes.
  497. * @return mixed string or null depending on the value of `$options['block']`
  498. * @link http://book.cakephp.org/3.0/en/views/helpers/html.html#creating-inline-javascript-blocks
  499. */
  500. public function scriptBlock($script, array $options = array()) {
  501. $options += array('safe' => true, 'block' => null);
  502. if ($options['safe']) {
  503. $script = "\n" . '//<![CDATA[' . "\n" . $script . "\n" . '//]]>' . "\n";
  504. }
  505. unset($options['safe']);
  506. $out = $this->formatTemplate('javascriptblock', [
  507. 'attrs' => $this->templater()->formatAttributes($options, ['block']),
  508. 'content' => $script
  509. ]);
  510. if (empty($options['block'])) {
  511. return $out;
  512. }
  513. if ($options['block'] === true) {
  514. $options['block'] = 'script';
  515. }
  516. $this->_View->append($options['block'], $out);
  517. }
  518. /**
  519. * Begin a script block that captures output until HtmlHelper::scriptEnd()
  520. * is called. This capturing block will capture all output between the methods
  521. * and create a scriptBlock from it.
  522. *
  523. * ### Options
  524. *
  525. * - `safe` Whether the code block should contain a CDATA
  526. * - `block` Set to true to append output to view block "script" or provide
  527. * custom block name.
  528. *
  529. * @param array $options Options for the code block.
  530. * @return void
  531. * @link http://book.cakephp.org/3.0/en/views/helpers/html.html#creating-javascript-blocks
  532. */
  533. public function scriptStart(array $options = array()) {
  534. $options += array('safe' => true, 'block' => null);
  535. $this->_scriptBlockOptions = $options;
  536. ob_start();
  537. }
  538. /**
  539. * End a Buffered section of JavaScript capturing.
  540. * Generates a script tag inline or appends to specified view block depending on
  541. * the settings used when the scriptBlock was started
  542. *
  543. * @return mixed depending on the settings of scriptStart() either a script tag or null
  544. * @link http://book.cakephp.org/3.0/en/views/helpers/html.html#creating-javascript-blocks
  545. */
  546. public function scriptEnd() {
  547. $buffer = ob_get_clean();
  548. $options = $this->_scriptBlockOptions;
  549. $this->_scriptBlockOptions = array();
  550. return $this->scriptBlock($buffer, $options);
  551. }
  552. /**
  553. * Builds CSS style data from an array of CSS properties
  554. *
  555. * ### Usage:
  556. *
  557. * {{{
  558. * echo $this->Html->style(array('margin' => '10px', 'padding' => '10px'), true);
  559. *
  560. * // creates
  561. * 'margin:10px;padding:10px;'
  562. * }}}
  563. *
  564. * @param array $data Style data array, keys will be used as property names, values as property values.
  565. * @param bool $oneLine Whether or not the style block should be displayed on one line.
  566. * @return string CSS styling data
  567. * @link http://book.cakephp.org/3.0/en/views/helpers/html.html#creating-css-programatically
  568. */
  569. public function style(array $data, $oneLine = true) {
  570. $out = array();
  571. foreach ($data as $key => $value) {
  572. $out[] = $key . ':' . $value . ';';
  573. }
  574. if ($oneLine) {
  575. return implode(' ', $out);
  576. }
  577. return implode("\n", $out);
  578. }
  579. /**
  580. * Returns the breadcrumb trail as a sequence of &raquo;-separated links.
  581. *
  582. * If `$startText` is an array, the accepted keys are:
  583. *
  584. * - `text` Define the text/content for the link.
  585. * - `url` Define the target of the created link.
  586. *
  587. * All other keys will be passed to HtmlHelper::link() as the `$options` parameter.
  588. *
  589. * @param string $separator Text to separate crumbs.
  590. * @param string|array|bool $startText This will be the first crumb, if false it defaults to first crumb in array. Can
  591. * also be an array, see above for details.
  592. * @return string|null Composed bread crumbs
  593. * @link http://book.cakephp.org/3.0/en/views/helpers/html.html#creating-breadcrumb-trails-with-htmlhelper
  594. */
  595. public function getCrumbs($separator = '&raquo;', $startText = false) {
  596. $crumbs = $this->_prepareCrumbs($startText);
  597. if (!empty($crumbs)) {
  598. $out = array();
  599. foreach ($crumbs as $crumb) {
  600. if (!empty($crumb[1])) {
  601. $out[] = $this->link($crumb[0], $crumb[1], $crumb[2]);
  602. } else {
  603. $out[] = $crumb[0];
  604. }
  605. }
  606. return implode($separator, $out);
  607. }
  608. return null;
  609. }
  610. /**
  611. * Returns breadcrumbs as a (x)html list
  612. *
  613. * This method uses HtmlHelper::tag() to generate list and its elements. Works
  614. * similar to HtmlHelper::getCrumbs(), so it uses options which every
  615. * crumb was added with.
  616. *
  617. * ### Options
  618. *
  619. * - `separator` Separator content to insert in between breadcrumbs, defaults to ''
  620. * - `firstClass` Class for wrapper tag on the first breadcrumb, defaults to 'first'
  621. * - `lastClass` Class for wrapper tag on current active page, defaults to 'last'
  622. *
  623. * @param array $options Array of html attributes to apply to the generated list elements.
  624. * @param string|array|bool $startText This will be the first crumb, if false it defaults to first crumb in array. Can
  625. * also be an array, see `HtmlHelper::getCrumbs` for details.
  626. * @return string|null breadcrumbs html list
  627. * @link http://book.cakephp.org/3.0/en/views/helpers/html.html#creating-breadcrumb-trails-with-htmlhelper
  628. */
  629. public function getCrumbList(array $options = array(), $startText = false) {
  630. $defaults = array('firstClass' => 'first', 'lastClass' => 'last', 'separator' => '', 'escape' => true);
  631. $options += $defaults;
  632. $firstClass = $options['firstClass'];
  633. $lastClass = $options['lastClass'];
  634. $separator = $options['separator'];
  635. $escape = $options['escape'];
  636. unset($options['firstClass'], $options['lastClass'], $options['separator'], $options['escape']);
  637. $crumbs = $this->_prepareCrumbs($startText, $escape);
  638. if (empty($crumbs)) {
  639. return null;
  640. }
  641. $result = '';
  642. $crumbCount = count($crumbs);
  643. $ulOptions = $options;
  644. foreach ($crumbs as $which => $crumb) {
  645. $options = array();
  646. if (empty($crumb[1])) {
  647. $elementContent = $crumb[0];
  648. } else {
  649. $elementContent = $this->link($crumb[0], $crumb[1], $crumb[2]);
  650. }
  651. if (!$which && $firstClass !== false) {
  652. $options['class'] = $firstClass;
  653. } elseif ($which == $crumbCount - 1 && $lastClass !== false) {
  654. $options['class'] = $lastClass;
  655. }
  656. if (!empty($separator) && ($crumbCount - $which >= 2)) {
  657. $elementContent .= $separator;
  658. }
  659. $result .= $this->formatTemplate('li', [
  660. 'content' => $elementContent,
  661. 'attrs' => $this->templater()->formatAttributes($options)
  662. ]);
  663. }
  664. return $this->formatTemplate('ul', [
  665. 'content' => $result,
  666. 'attrs' => $this->templater()->formatAttributes($ulOptions)
  667. ]);
  668. }
  669. /**
  670. * Prepends startText to crumbs array if set
  671. *
  672. * @param string|array|bool $startText Text to prepend
  673. * @param bool $escape If the output should be escaped or not
  674. * @return array Crumb list including startText (if provided)
  675. */
  676. protected function _prepareCrumbs($startText, $escape = true) {
  677. $crumbs = $this->_crumbs;
  678. if ($startText) {
  679. if (!is_array($startText)) {
  680. $startText = array(
  681. 'url' => '/',
  682. 'text' => $startText
  683. );
  684. }
  685. $startText += array('url' => '/', 'text' => __d('cake', 'Home'));
  686. list($url, $text) = array($startText['url'], $startText['text']);
  687. unset($startText['url'], $startText['text']);
  688. array_unshift($crumbs, array($text, $url, $startText + array('escape' => $escape)));
  689. }
  690. return $crumbs;
  691. }
  692. /**
  693. * Creates a formatted IMG element.
  694. *
  695. * This method will set an empty alt attribute if one is not supplied.
  696. *
  697. * ### Usage:
  698. *
  699. * Create a regular image:
  700. *
  701. * `echo $this->Html->image('cake_icon.png', array('alt' => 'CakePHP'));`
  702. *
  703. * Create an image link:
  704. *
  705. * `echo $this->Html->image('cake_icon.png', array('alt' => 'CakePHP', 'url' => 'http://cakephp.org'));`
  706. *
  707. * ### Options:
  708. *
  709. * - `url` If provided an image link will be generated and the link will point at
  710. * `$options['url']`.
  711. * - `fullBase` If true the src attribute will get a full address for the image file.
  712. * - `plugin` False value will prevent parsing path as a plugin
  713. *
  714. * @param string $path Path to the image file, relative to the app/webroot/img/ directory.
  715. * @param array $options Array of HTML attributes. See above for special options.
  716. * @return string completed img tag
  717. * @link http://book.cakephp.org/3.0/en/views/helpers/html.html#linking-to-images
  718. */
  719. public function image($path, array $options = array()) {
  720. $path = $this->Url->assetUrl($path, $options + array('pathPrefix' => Configure::read('App.imageBaseUrl')));
  721. $options = array_diff_key($options, array('fullBase' => null, 'pathPrefix' => null));
  722. if (!isset($options['alt'])) {
  723. $options['alt'] = '';
  724. }
  725. $url = false;
  726. if (!empty($options['url'])) {
  727. $url = $options['url'];
  728. unset($options['url']);
  729. }
  730. $templater = $this->templater();
  731. $image = $templater->format('image', [
  732. 'url' => $path,
  733. 'attrs' => $templater->formatAttributes($options),
  734. ]);
  735. if ($url) {
  736. return $templater->format('link', [
  737. 'url' => $this->Url->build($url),
  738. 'attrs' => null,
  739. 'content' => $image
  740. ]);
  741. }
  742. return $image;
  743. }
  744. /**
  745. * Returns a row of formatted and named TABLE headers.
  746. *
  747. * @param array $names Array of tablenames. Each tablename also can be a key that points to an array with a set
  748. * of attributes to its specific tag
  749. * @param array|null $trOptions HTML options for TR elements.
  750. * @param array|null $thOptions HTML options for TH elements.
  751. * @return string Completed table headers
  752. * @link http://book.cakephp.org/3.0/en/views/helpers/html.html#creating-table-headings
  753. */
  754. public function tableHeaders(array $names, array $trOptions = null, array $thOptions = null) {
  755. $out = array();
  756. foreach ($names as $arg) {
  757. if (!is_array($arg)) {
  758. $out[] = $this->formatTemplate('tableheader', [
  759. 'attrs' => $this->templater()->formatAttributes($thOptions),
  760. 'content' => $arg
  761. ]);
  762. } else {
  763. $out[] = $this->formatTemplate('tableheader', [
  764. 'attrs' => $this->templater()->formatAttributes(current($arg)),
  765. 'content' => key($arg)
  766. ]);
  767. }
  768. }
  769. return $this->formatTemplate('tablerow', [
  770. 'attrs' => $this->templater()->formatAttributes($trOptions),
  771. 'content' => implode(' ', $out)
  772. ]);
  773. }
  774. /**
  775. * Returns a formatted string of table rows (TR's with TD's in them).
  776. *
  777. * @param array $data Array of table data
  778. * @param array $oddTrOptions HTML options for odd TR elements if true useCount is used
  779. * @param array $evenTrOptions HTML options for even TR elements
  780. * @param bool $useCount adds class "column-$i"
  781. * @param bool $continueOddEven If false, will use a non-static $count variable,
  782. * so that the odd/even count is reset to zero just for that call.
  783. * @return string Formatted HTML
  784. * @link http://book.cakephp.org/3.0/en/views/helpers/html.html#creating-table-cells
  785. */
  786. public function tableCells($data, $oddTrOptions = null, $evenTrOptions = null, $useCount = false, $continueOddEven = true) {
  787. if (empty($data[0]) || !is_array($data[0])) {
  788. $data = array($data);
  789. }
  790. if ($oddTrOptions === true) {
  791. $useCount = true;
  792. $oddTrOptions = null;
  793. }
  794. if ($evenTrOptions === false) {
  795. $continueOddEven = false;
  796. $evenTrOptions = null;
  797. }
  798. if ($continueOddEven) {
  799. static $count = 0;
  800. } else {
  801. $count = 0;
  802. }
  803. foreach ($data as $line) {
  804. $count++;
  805. $cellsOut = array();
  806. $i = 0;
  807. foreach ($line as $cell) {
  808. $cellOptions = array();
  809. if (is_array($cell)) {
  810. $cellOptions = $cell[1];
  811. $cell = $cell[0];
  812. } elseif ($useCount) {
  813. $cellOptions['class'] = 'column-' . ++$i;
  814. }
  815. $cellsOut[] = $this->formatTemplate('tablecell', [
  816. 'attrs' => $this->templater()->formatAttributes($cellOptions),
  817. 'content' => $cell
  818. ]);
  819. }
  820. $opts = $count % 2 ? $oddTrOptions : $evenTrOptions;
  821. $out[] = $this->formatTemplate('tablerow', [
  822. 'attrs' => $this->templater()->formatAttributes($opts),
  823. 'content' => implode(' ', $cellsOut),
  824. ]);
  825. }
  826. return implode("\n", $out);
  827. }
  828. /**
  829. * Returns a formatted block tag, i.e DIV, SPAN, P.
  830. *
  831. * ### Options
  832. *
  833. * - `escape` Whether or not the contents should be html_entity escaped.
  834. *
  835. * @param string $name Tag name.
  836. * @param string $text String content that will appear inside the div element.
  837. * If null, only a start tag will be printed
  838. * @param array $options Additional HTML attributes of the DIV tag, see above.
  839. * @return string The formatted tag element
  840. */
  841. public function tag($name, $text = null, array $options = array()) {
  842. if (empty($name)) {
  843. return $text;
  844. }
  845. if (isset($options['escape']) && $options['escape']) {
  846. $text = h($text);
  847. unset($options['escape']);
  848. }
  849. if ($text === null) {
  850. $tag = 'tagstart';
  851. } else {
  852. $tag = 'tag';
  853. }
  854. return $this->formatTemplate($tag, [
  855. 'attrs' => $this->templater()->formatAttributes($options),
  856. 'tag' => $name,
  857. 'content' => $text,
  858. ]);
  859. }
  860. /**
  861. * Returns a formatted DIV tag for HTML FORMs.
  862. *
  863. * ### Options
  864. *
  865. * - `escape` Whether or not the contents should be html_entity escaped.
  866. *
  867. * @param string $class CSS class name of the div element.
  868. * @param string $text String content that will appear inside the div element.
  869. * If null, only a start tag will be printed
  870. * @param array $options Additional HTML attributes of the DIV tag
  871. * @return string The formatted DIV element
  872. */
  873. public function div($class = null, $text = null, array $options = array()) {
  874. if (!empty($class)) {
  875. $options['class'] = $class;
  876. }
  877. return $this->tag('div', $text, $options);
  878. }
  879. /**
  880. * Returns a formatted P tag.
  881. *
  882. * ### Options
  883. *
  884. * - `escape` Whether or not the contents should be html_entity escaped.
  885. *
  886. * @param string $class CSS class name of the p element.
  887. * @param string $text String content that will appear inside the p element.
  888. * @param array $options Additional HTML attributes of the P tag
  889. * @return string The formatted P element
  890. */
  891. public function para($class, $text, array $options = array()) {
  892. if (isset($options['escape'])) {
  893. $text = h($text);
  894. }
  895. if ($class && !empty($class)) {
  896. $options['class'] = $class;
  897. }
  898. $tag = 'para';
  899. if ($text === null) {
  900. $tag = 'parastart';
  901. }
  902. return $this->formatTemplate($tag, [
  903. 'attrs' => $this->templater()->formatAttributes($options),
  904. 'content' => $text,
  905. ]);
  906. }
  907. /**
  908. * Returns an audio/video element
  909. *
  910. * ### Usage
  911. *
  912. * Using an audio file:
  913. *
  914. * `echo $this->Html->media('audio.mp3', array('fullBase' => true));`
  915. *
  916. * Outputs:
  917. *
  918. * `<video src="http://www.somehost.com/files/audio.mp3">Fallback text</video>`
  919. *
  920. * Using a video file:
  921. *
  922. * `echo $this->Html->media('video.mp4', array('text' => 'Fallback text'));`
  923. *
  924. * Outputs:
  925. *
  926. * `<video src="/files/video.mp4">Fallback text</video>`
  927. *
  928. * Using multiple video files:
  929. *
  930. * {{{
  931. * echo $this->Html->media(
  932. * array('video.mp4', array('src' => 'video.ogv', 'type' => "video/ogg; codecs='theora, vorbis'")),
  933. * array('tag' => 'video', 'autoplay')
  934. * );
  935. * }}}
  936. *
  937. * Outputs:
  938. *
  939. * {{{
  940. * <video autoplay="autoplay">
  941. * <source src="/files/video.mp4" type="video/mp4"/>
  942. * <source src="/files/video.ogv" type="video/ogv; codecs='theora, vorbis'"/>
  943. * </video>
  944. * }}}
  945. *
  946. * ### Options
  947. *
  948. * - `tag` Type of media element to generate, either "audio" or "video".
  949. * If tag is not provided it's guessed based on file's mime type.
  950. * - `text` Text to include inside the audio/video tag
  951. * - `pathPrefix` Path prefix to use for relative URLs, defaults to 'files/'
  952. * - `fullBase` If provided the src attribute will get a full address including domain name
  953. *
  954. * @param string|array $path Path to the video file, relative to the webroot/{$options['pathPrefix']} directory.
  955. * Or an array where each item itself can be a path string or an associate array containing keys `src` and `type`
  956. * @param array $options Array of HTML attributes, and special options above.
  957. * @return string Generated media element
  958. */
  959. public function media($path, array $options = array()) {
  960. $options += array(
  961. 'tag' => null,
  962. 'pathPrefix' => 'files/',
  963. 'text' => ''
  964. );
  965. if (!empty($options['tag'])) {
  966. $tag = $options['tag'];
  967. } else {
  968. $tag = null;
  969. }
  970. if (is_array($path)) {
  971. $sourceTags = '';
  972. foreach ($path as &$source) {
  973. if (is_string($source)) {
  974. $source = array(
  975. 'src' => $source,
  976. );
  977. }
  978. if (!isset($source['type'])) {
  979. $ext = pathinfo($source['src'], PATHINFO_EXTENSION);
  980. $source['type'] = $this->response->getMimeType($ext);
  981. }
  982. $source['src'] = $this->Url->assetUrl($source['src'], $options);
  983. $sourceTags .= $this->formatTemplate('tagselfclosing', [
  984. 'tag' => 'source',
  985. 'attrs' => $this->templater()->formatAttributes($source)
  986. ]);
  987. }
  988. unset($source);
  989. $options['text'] = $sourceTags . $options['text'];
  990. unset($options['fullBase']);
  991. } else {
  992. if (empty($path) && !empty($options['src'])) {
  993. $path = $options['src'];
  994. }
  995. $options['src'] = $this->Url->assetUrl($path, $options);
  996. }
  997. if ($tag === null) {
  998. if (is_array($path)) {
  999. $mimeType = $path[0]['type'];
  1000. } else {
  1001. $mimeType = $this->response->getMimeType(pathinfo($path, PATHINFO_EXTENSION));
  1002. }
  1003. if (preg_match('#^video/#', $mimeType)) {
  1004. $tag = 'video';
  1005. } else {
  1006. $tag = 'audio';
  1007. }
  1008. }
  1009. if (isset($options['poster'])) {
  1010. $options['poster'] = $this->Url->assetUrl($options['poster'], array('pathPrefix' => Configure::read('App.imageBaseUrl')) + $options);
  1011. }
  1012. $text = $options['text'];
  1013. $options = array_diff_key($options, array(
  1014. 'tag' => null,
  1015. 'fullBase' => null,
  1016. 'pathPrefix' => null,
  1017. 'text' => null
  1018. ));
  1019. return $this->tag($tag, $text, $options);
  1020. }
  1021. /**
  1022. * Build a nested list (UL/OL) out of an associative array.
  1023. *
  1024. * Options for $options:
  1025. *
  1026. * - `tag` - Type of list tag to use (ol/ul)
  1027. *
  1028. * Options for $itemOptions:
  1029. *
  1030. * - `even` - Class to use for even rows.
  1031. * - `odd` - Class to use for odd rows.
  1032. *
  1033. * @param array $list Set of elements to list
  1034. * @param array $options Options and additional HTML attributes of the list (ol/ul) tag.
  1035. * @param array $itemOptions Options and additional HTML attributes of the list item (LI) tag.
  1036. * @return string The nested list
  1037. * @link http://book.cakephp.org/3.0/en/views/helpers/html.html#creating-nested-lists
  1038. */
  1039. public function nestedList(array $list, array $options = [], array $itemOptions = []) {
  1040. $options += array('tag' => 'ul');
  1041. $items = $this->_nestedListItem($list, $options, $itemOptions);
  1042. return $this->formatTemplate($options['tag'], [
  1043. 'attrs' => $this->templater()->formatAttributes($options, ['tag']),
  1044. 'content' => $items
  1045. ]);
  1046. }
  1047. /**
  1048. * Internal function to build a nested list (UL/OL) out of an associative array.
  1049. *
  1050. * @param array $items Set of elements to list.
  1051. * @param array $options Additional HTML attributes of the list (ol/ul) tag.
  1052. * @param array $itemOptions Options and additional HTML attributes of the list item (LI) tag.
  1053. * @return string The nested list element
  1054. * @see HtmlHelper::nestedList()
  1055. */
  1056. protected function _nestedListItem($items, $options, $itemOptions) {
  1057. $out = '';
  1058. $index = 1;
  1059. foreach ($items as $key => $item) {
  1060. if (is_array($item)) {
  1061. $item = $key . $this->nestedList($item, $options, $itemOptions);
  1062. }
  1063. if (isset($itemOptions['even']) && $index % 2 === 0) {
  1064. $itemOptions['class'] = $itemOptions['even'];
  1065. } elseif (isset($itemOptions['odd']) && $index % 2 !== 0) {
  1066. $itemOptions['class'] = $itemOptions['odd'];
  1067. }
  1068. $out .= $this->formatTemplate('li', [
  1069. 'attrs' => $this->templater()->formatAttributes($itemOptions, ['even', 'odd']),
  1070. 'content' => $item
  1071. ]);
  1072. $index++;
  1073. }
  1074. return $out;
  1075. }
  1076. /**
  1077. * Event listeners.
  1078. *
  1079. * @return array
  1080. */
  1081. public function implementedEvents() {
  1082. return [];
  1083. }
  1084. }