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