1 <?php
2 /**
3 * @package Joomla.Libraries
4 * @subpackage HTML
5 *
6 * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE.txt
8 */
9
10 defined('JPATH_PLATFORM') or die;
11
12 use Joomla\Utilities\ArrayHelper;
13
14 jimport('joomla.environment.browser');
15 jimport('joomla.filesystem.file');
16 jimport('joomla.filesystem.path');
17
18 /**
19 * Utility class for all HTML drawing classes
20 *
21 * @since 1.5
22 */
23 abstract class JHtml
24 {
25 /**
26 * Option values related to the generation of HTML output. Recognized
27 * options are:
28 * fmtDepth, integer. The current indent depth.
29 * fmtEol, string. The end of line string, default is linefeed.
30 * fmtIndent, string. The string to use for indentation, default is
31 * tab.
32 *
33 * @var array
34 * @since 1.5
35 */
36 public static $formatOptions = array('format.depth' => 0, 'format.eol' => "\n", 'format.indent' => "\t");
37
38 /**
39 * An array to hold included paths
40 *
41 * @var string[]
42 * @since 1.5
43 */
44 protected static $includePaths = array();
45
46 /**
47 * An array to hold method references
48 *
49 * @var callable[]
50 * @since 1.6
51 */
52 protected static $registry = array();
53
54 /**
55 * Method to extract a key
56 *
57 * @param string $key The name of helper method to load, (prefix).(class).function
58 * prefix and class are optional and can be used to load custom html helpers.
59 *
60 * @return array Contains lowercase key, prefix, file, function.
61 *
62 * @since 1.6
63 */
64 protected static function extract($key)
65 {
66 $key = preg_replace('#[^A-Z0-9_\.]#i', '', $key);
67
68 // Check to see whether we need to load a helper file
69 $parts = explode('.', $key);
70
71 $prefix = count($parts) === 3 ? array_shift($parts) : 'JHtml';
72 $file = count($parts) === 2 ? array_shift($parts) : '';
73 $func = array_shift($parts);
74
75 return array(strtolower($prefix . '.' . $file . '.' . $func), $prefix, $file, $func);
76 }
77
78 /**
79 * Class loader method
80 *
81 * Additional arguments may be supplied and are passed to the sub-class.
82 * Additional include paths are also able to be specified for third-party use
83 *
84 * @param string $key The name of helper method to load, (prefix).(class).function
85 * prefix and class are optional and can be used to load custom
86 * html helpers.
87 *
88 * @return mixed Result of JHtml::call($function, $args)
89 *
90 * @since 1.5
91 * @throws InvalidArgumentException
92 */
93 public static function _($key)
94 {
95 list($key, $prefix, $file, $func) = static::extract($key);
96
97 if (array_key_exists($key, static::$registry))
98 {
99 $function = static::$registry[$key];
100 $args = func_get_args();
101
102 // Remove function name from arguments
103 array_shift($args);
104
105 return static::call($function, $args);
106 }
107
108 $className = $prefix . ucfirst($file);
109
110 if (!class_exists($className))
111 {
112 $path = JPath::find(static::$includePaths, strtolower($file) . '.php');
113
114 if (!$path)
115 {
116 throw new InvalidArgumentException(sprintf('%s %s not found.', $prefix, $file), 500);
117 }
118
119 JLoader::register($className, $path);
120
121 if (!class_exists($className))
122 {
123 throw new InvalidArgumentException(sprintf('%s not found.', $className), 500);
124 }
125 }
126
127 $toCall = array($className, $func);
128
129 if (!is_callable($toCall))
130 {
131 throw new InvalidArgumentException(sprintf('%s::%s not found.', $className, $func), 500);
132 }
133
134 static::register($key, $toCall);
135 $args = func_get_args();
136
137 // Remove function name from arguments
138 array_shift($args);
139
140 return static::call($toCall, $args);
141 }
142
143 /**
144 * Registers a function to be called with a specific key
145 *
146 * @param string $key The name of the key
147 * @param string $function Function or method
148 *
149 * @return boolean True if the function is callable
150 *
151 * @since 1.6
152 */
153 public static function register($key, $function)
154 {
155 list($key) = static::extract($key);
156
157 if (is_callable($function))
158 {
159 static::$registry[$key] = $function;
160
161 return true;
162 }
163
164 return false;
165 }
166
167 /**
168 * Removes a key for a method from registry.
169 *
170 * @param string $key The name of the key
171 *
172 * @return boolean True if a set key is unset
173 *
174 * @since 1.6
175 */
176 public static function unregister($key)
177 {
178 list($key) = static::extract($key);
179
180 if (isset(static::$registry[$key]))
181 {
182 unset(static::$registry[$key]);
183
184 return true;
185 }
186
187 return false;
188 }
189
190 /**
191 * Test if the key is registered.
192 *
193 * @param string $key The name of the key
194 *
195 * @return boolean True if the key is registered.
196 *
197 * @since 1.6
198 */
199 public static function isRegistered($key)
200 {
201 list($key) = static::extract($key);
202
203 return isset(static::$registry[$key]);
204 }
205
206 /**
207 * Function caller method
208 *
209 * @param callable $function Function or method to call
210 * @param array $args Arguments to be passed to function
211 *
212 * @return mixed Function result or false on error.
213 *
214 * @link https://secure.php.net/manual/en/function.call-user-func-array.php
215 * @since 1.6
216 * @throws InvalidArgumentException
217 */
218 protected static function call($function, $args)
219 {
220 if (!is_callable($function))
221 {
222 throw new InvalidArgumentException('Function not supported', 500);
223 }
224
225 // PHP 5.3 workaround
226 $temp = array();
227
228 foreach ($args as &$arg)
229 {
230 $temp[] = &$arg;
231 }
232
233 return call_user_func_array($function, $temp);
234 }
235
236 /**
237 * Write a `<a>` element
238 *
239 * @param string $url The relative URL to use for the href attribute
240 * @param string $text The target attribute to use
241 * @param array|string $attribs Attributes to be added to the `<a>` element
242 *
243 * @return string
244 *
245 * @since 1.5
246 */
247 public static function link($url, $text, $attribs = null)
248 {
249 if (is_array($attribs))
250 {
251 $attribs = ArrayHelper::toString($attribs);
252 }
253
254 return '<a href="' . $url . '" ' . $attribs . '>' . $text . '</a>';
255 }
256
257 /**
258 * Write a `<iframe>` element
259 *
260 * @param string $url The relative URL to use for the src attribute.
261 * @param string $name The target attribute to use.
262 * @param array|string $attribs Attributes to be added to the `<iframe>` element
263 * @param string $noFrames The message to display if the iframe tag is not supported.
264 *
265 * @return string
266 *
267 * @since 1.5
268 */
269 public static function iframe($url, $name, $attribs = null, $noFrames = '')
270 {
271 if (is_array($attribs))
272 {
273 $attribs = ArrayHelper::toString($attribs);
274 }
275
276 return '<iframe src="' . $url . '" ' . $attribs . ' name="' . $name . '">' . $noFrames . '</iframe>';
277 }
278
279 /**
280 * Include version with MD5SUM file in path.
281 *
282 * @param string $path Folder name to search into (images, css, js, ...).
283 *
284 * @return string Query string to add.
285 *
286 * @since 3.7.0
287 *
288 * @deprecated 4.0 Usage of MD5SUM files is deprecated, use version instead.
289 */
290 protected static function getMd5Version($path)
291 {
292 $md5 = dirname($path) . '/MD5SUM';
293
294 if (file_exists($md5))
295 {
296 JLog::add('Usage of MD5SUM files is deprecated, use version instead.', JLog::WARNING, 'deprecated');
297
298 return '?' . file_get_contents($md5);
299 }
300
301 return '';
302 }
303
304 /**
305 * Compute the files to be included
306 *
307 * @param string $folder Folder name to search in (i.e. images, css, js).
308 * @param string $file Path to file.
309 * @param boolean $relative Flag if the path to the file is relative to the /media folder (and searches in template).
310 * @param boolean $detect_browser Flag if the browser should be detected to include specific browser files.
311 * @param boolean $detect_debug Flag if debug mode is enabled to include uncompressed files if debug is on.
312 *
313 * @return array files to be included.
314 *
315 * @see JBrowser
316 * @since 1.6
317 */
318 protected static function includeRelativeFiles($folder, $file, $relative, $detect_browser, $detect_debug)
319 {
320 // If http is present in filename just return it as an array
321 if (strpos($file, 'http') === 0 || strpos($file, '//') === 0)
322 {
323 return array($file);
324 }
325
326 // Extract extension and strip the file
327 $strip = JFile::stripExt($file);
328 $ext = JFile::getExt($file);
329
330 // Prepare array of files
331 $includes = array();
332
333 // Detect browser and compute potential files
334 if ($detect_browser)
335 {
336 $navigator = JBrowser::getInstance();
337 $browser = $navigator->getBrowser();
338 $major = $navigator->getMajor();
339 $minor = $navigator->getMinor();
340
341 // Try to include files named filename.ext, filename_browser.ext, filename_browser_major.ext, filename_browser_major_minor.ext
342 // where major and minor are the browser version names
343 $potential = array(
344 $strip,
345 $strip . '_' . $browser,
346 $strip . '_' . $browser . '_' . $major,
347 $strip . '_' . $browser . '_' . $major . '_' . $minor,
348 );
349 }
350 else
351 {
352 $potential = array($strip);
353 }
354
355 // If relative search in template directory or media directory
356 if ($relative)
357 {
358 // Get the template
359 $template = JFactory::getApplication()->getTemplate();
360
361 // For each potential files
362 foreach ($potential as $strip)
363 {
364 $files = array();
365
366 // Detect debug mode
367 if ($detect_debug && JFactory::getConfig()->get('debug'))
368 {
369 /*
370 * Detect if we received a file in the format name.min.ext
371 * If so, strip the .min part out, otherwise append -uncompressed
372 */
373 if (strlen($strip) > 4 && preg_match('#\.min$#', $strip))
374 {
375 $files[] = preg_replace('#\.min$#', '.', $strip) . $ext;
376 }
377 else
378 {
379 $files[] = $strip . '-uncompressed.' . $ext;
380 }
381 }
382
383 $files[] = $strip . '.' . $ext;
384
385 /*
386 * Loop on 1 or 2 files and break on first found.
387 * Add the content of the MD5SUM file located in the same folder to URL to ensure cache browser refresh
388 * This MD5SUM file must represent the signature of the folder content
389 */
390 foreach ($files as $file)
391 {
392 // If the file is in the template folder
393 $path = JPATH_THEMES . "/$template/$folder/$file";
394
395 if (file_exists($path))
396 {
397 $includes[] = JUri::base(true) . "/templates/$template/$folder/$file" . static::getMd5Version($path);
398
399 break;
400 }
401 else
402 {
403 // If the file contains any /: it can be in an media extension subfolder
404 if (strpos($file, '/'))
405 {
406 // Divide the file extracting the extension as the first part before /
407 list($extension, $file) = explode('/', $file, 2);
408
409 // If the file yet contains any /: it can be a plugin
410 if (strpos($file, '/'))
411 {
412 // Divide the file extracting the element as the first part before /
413 list($element, $file) = explode('/', $file, 2);
414
415 // Try to deal with plugins group in the media folder
416 $path = JPATH_ROOT . "/media/$extension/$element/$folder/$file";
417
418 if (file_exists($path))
419 {
420 $includes[] = JUri::root(true) . "/media/$extension/$element/$folder/$file" . static::getMd5Version($path);
421
422 break;
423 }
424
425 // Try to deal with classical file in a media subfolder called element
426 $path = JPATH_ROOT . "/media/$extension/$folder/$element/$file";
427
428 if (file_exists($path))
429 {
430 $includes[] = JUri::root(true) . "/media/$extension/$folder/$element/$file" . static::getMd5Version($path);
431
432 break;
433 }
434
435 // Try to deal with system files in the template folder
436 $path = JPATH_THEMES . "/$template/$folder/system/$element/$file";
437
438 if (file_exists($path))
439 {
440 $includes[] = JUri::root(true) . "/templates/$template/$folder/system/$element/$file" . static::getMd5Version($path);
441
442 break;
443 }
444
445 // Try to deal with system files in the media folder
446 $path = JPATH_ROOT . "/media/system/$folder/$element/$file";
447
448 if (file_exists($path))
449 {
450 $includes[] = JUri::root(true) . "/media/system/$folder/$element/$file" . static::getMd5Version($path);
451
452 break;
453 }
454 }
455 else
456 {
457 // Try to deals in the extension media folder
458 $path = JPATH_ROOT . "/media/$extension/$folder/$file";
459
460 if (file_exists($path))
461 {
462 $includes[] = JUri::root(true) . "/media/$extension/$folder/$file" . static::getMd5Version($path);
463
464 break;
465 }
466
467 // Try to deal with system files in the template folder
468 $path = JPATH_THEMES . "/$template/$folder/system/$file";
469
470 if (file_exists($path))
471 {
472 $includes[] = JUri::root(true) . "/templates/$template/$folder/system/$file" . static::getMd5Version($path);
473
474 break;
475 }
476
477 // Try to deal with system files in the media folder
478 $path = JPATH_ROOT . "/media/system/$folder/$file";
479
480 if (file_exists($path))
481 {
482 $includes[] = JUri::root(true) . "/media/system/$folder/$file" . static::getMd5Version($path);
483
484 break;
485 }
486 }
487 }
488 // Try to deal with system files in the media folder
489 else
490 {
491 $path = JPATH_ROOT . "/media/system/$folder/$file";
492
493 if (file_exists($path))
494 {
495 $includes[] = JUri::root(true) . "/media/system/$folder/$file" . static::getMd5Version($path);
496
497 break;
498 }
499 }
500 }
501 }
502 }
503 }
504 // If not relative and http is not present in filename
505 else
506 {
507 foreach ($potential as $strip)
508 {
509 $files = array();
510
511 // Detect debug mode
512 if ($detect_debug && JFactory::getConfig()->get('debug'))
513 {
514 /*
515 * Detect if we received a file in the format name.min.ext
516 * If so, strip the .min part out, otherwise append -uncompressed
517 */
518 if (strlen($strip) > 4 && preg_match('#\.min$#', $strip))
519 {
520 $files[] = preg_replace('#\.min$#', '.', $strip) . $ext;
521 }
522 else
523 {
524 $files[] = $strip . '-uncompressed.' . $ext;
525 }
526 }
527
528 $files[] = $strip . '.' . $ext;
529
530 /*
531 * Loop on 1 or 2 files and break on first found.
532 * Add the content of the MD5SUM file located in the same folder to URL to ensure cache browser refresh
533 * This MD5SUM file must represent the signature of the folder content
534 */
535 foreach ($files as $file)
536 {
537 $path = JPATH_ROOT . "/$file";
538
539 if (file_exists($path))
540 {
541 $includes[] = JUri::root(true) . "/$file" . static::getMd5Version($path);
542
543 break;
544 }
545 }
546 }
547 }
548
549 return $includes;
550 }
551
552 /**
553 * Write a `<img>` element
554 *
555 * @param string $file The relative or absolute URL to use for the src attribute.
556 * @param string $alt The alt text.
557 * @param array|string $attribs Attributes to be added to the `<img>` element
558 * @param boolean $relative Flag if the path to the file is relative to the /media folder (and searches in template).
559 * @param integer $returnPath Defines the return value for the method:
560 * -1: Returns a `<img>` tag without looking for relative files
561 * 0: Returns a `<img>` tag while searching for relative files
562 * 1: Returns the file path to the image while searching for relative files
563 *
564 * @return string
565 *
566 * @since 1.5
567 */
568 public static function image($file, $alt, $attribs = null, $relative = false, $returnPath = 0)
569 {
570 $returnPath = (int) $returnPath;
571
572 if ($returnPath !== -1)
573 {
574 $includes = static::includeRelativeFiles('images', $file, $relative, false, false);
575 $file = count($includes) ? $includes[0] : null;
576 }
577
578 // If only path is required
579 if ($returnPath)
580 {
581 return $file;
582 }
583
584 return '<img src="' . $file . '" alt="' . $alt . '" ' . trim((is_array($attribs) ? ArrayHelper::toString($attribs) : $attribs) . ' /') . '>';
585 }
586
587 /**
588 * Write a `<link>` element to load a CSS file
589 *
590 * @param string $file Path to file
591 * @param array $options Array of options. Example: array('version' => 'auto', 'conditional' => 'lt IE 9')
592 * @param array $attribs Array of attributes. Example: array('id' => 'scriptid', 'async' => 'async', 'data-test' => 1)
593 *
594 * @return array|string|null nothing if $returnPath is false, null, path or array of path if specific CSS browser files were detected
595 *
596 * @see JBrowser
597 * @since 1.5
598 * @deprecated 4.0 The (file, attribs, relative, pathOnly, detectBrowser, detectDebug) method signature is deprecated,
599 * use (file, options, attributes) instead.
600 */
601 public static function stylesheet($file, $options = array(), $attribs = array())
602 {
603 // B/C before 3.7.0
604 if (!is_array($attribs))
605 {
606 JLog::add('The stylesheet method signature used has changed, use (file, options, attributes) instead.', JLog::WARNING, 'deprecated');
607
608 $argList = func_get_args();
609 $options = array();
610
611 // Old parameters.
612 $attribs = isset($argList[1]) ? $argList[1] : array();
613 $options['relative'] = isset($argList[2]) ? $argList[2] : false;
614 $options['pathOnly'] = isset($argList[3]) ? $argList[3] : false;
615 $options['detectBrowser'] = isset($argList[4]) ? $argList[4] : true;
616 $options['detectDebug'] = isset($argList[5]) ? $argList[5] : true;
617 }
618 else
619 {
620 $options['relative'] = isset($options['relative']) ? $options['relative'] : false;
621 $options['pathOnly'] = isset($options['pathOnly']) ? $options['pathOnly'] : false;
622 $options['detectBrowser'] = isset($options['detectBrowser']) ? $options['detectBrowser'] : true;
623 $options['detectDebug'] = isset($options['detectDebug']) ? $options['detectDebug'] : true;
624 }
625
626 $includes = static::includeRelativeFiles('css', $file, $options['relative'], $options['detectBrowser'], $options['detectDebug']);
627
628 // If only path is required
629 if ($options['pathOnly'])
630 {
631 if (count($includes) === 0)
632 {
633 return;
634 }
635
636 if (count($includes) === 1)
637 {
638 return $includes[0];
639 }
640
641 return $includes;
642 }
643
644 // If inclusion is required
645 $document = JFactory::getDocument();
646
647 foreach ($includes as $include)
648 {
649 // If there is already a version hash in the script reference (by using deprecated MD5SUM).
650 if ($pos = strpos($include, '?') !== false)
651 {
652 $options['version'] = substr($include, $pos + 1);
653 }
654
655 $document->addStyleSheet($include, $options, $attribs);
656 }
657 }
658
659 /**
660 * Write a `<script>` element to load a JavaScript file
661 *
662 * @param string $file Path to file.
663 * @param array $options Array of options. Example: array('version' => 'auto', 'conditional' => 'lt IE 9')
664 * @param array $attribs Array of attributes. Example: array('id' => 'scriptid', 'async' => 'async', 'data-test' => 1)
665 *
666 * @return array|string|null Nothing if $returnPath is false, null, path or array of path if specific JavaScript browser files were detected
667 *
668 * @see JHtml::stylesheet()
669 * @since 1.5
670 * @deprecated 4.0 The (file, framework, relative, pathOnly, detectBrowser, detectDebug) method signature is deprecated,
671 * use (file, options, attributes) instead.
672 */
673 public static function script($file, $options = array(), $attribs = array())
674 {
675 // B/C before 3.7.0
676 if (!is_array($options))
677 {
678 JLog::add('The script method signature used has changed, use (file, options, attributes) instead.', JLog::WARNING, 'deprecated');
679
680 $argList = func_get_args();
681 $options = array();
682 $attribs = array();
683
684 // Old parameters.
685 $options['framework'] = isset($argList[1]) ? $argList[1] : false;
686 $options['relative'] = isset($argList[2]) ? $argList[2] : false;
687 $options['pathOnly'] = isset($argList[3]) ? $argList[3] : false;
688 $options['detectBrowser'] = isset($argList[4]) ? $argList[4] : true;
689 $options['detectDebug'] = isset($argList[5]) ? $argList[5] : true;
690 }
691 else
692 {
693 $options['framework'] = isset($options['framework']) ? $options['framework'] : false;
694 $options['relative'] = isset($options['relative']) ? $options['relative'] : false;
695 $options['pathOnly'] = isset($options['pathOnly']) ? $options['pathOnly'] : false;
696 $options['detectBrowser'] = isset($options['detectBrowser']) ? $options['detectBrowser'] : true;
697 $options['detectDebug'] = isset($options['detectDebug']) ? $options['detectDebug'] : true;
698 }
699
700 // Include MooTools framework
701 if ($options['framework'])
702 {
703 static::_('behavior.framework');
704 }
705
706 $includes = static::includeRelativeFiles('js', $file, $options['relative'], $options['detectBrowser'], $options['detectDebug']);
707
708 // If only path is required
709 if ($options['pathOnly'])
710 {
711 if (count($includes) === 0)
712 {
713 return;
714 }
715
716 if (count($includes) === 1)
717 {
718 return $includes[0];
719 }
720
721 return $includes;
722 }
723
724 // If inclusion is required
725 $document = JFactory::getDocument();
726
727 foreach ($includes as $include)
728 {
729 // If there is already a version hash in the script reference (by using deprecated MD5SUM).
730 if ($pos = strpos($include, '?') !== false)
731 {
732 $options['version'] = substr($include, $pos + 1);
733 }
734
735 $document->addScript($include, $options, $attribs);
736 }
737 }
738
739 /**
740 * Set format related options.
741 *
742 * Updates the formatOptions array with all valid values in the passed array.
743 *
744 * @param array $options Option key/value pairs.
745 *
746 * @return void
747 *
748 * @see JHtml::$formatOptions
749 * @since 1.5
750 */
751 public static function setFormatOptions($options)
752 {
753 foreach ($options as $key => $val)
754 {
755 if (isset(static::$formatOptions[$key]))
756 {
757 static::$formatOptions[$key] = $val;
758 }
759 }
760 }
761
762 /**
763 * Returns formated date according to a given format and time zone.
764 *
765 * @param string $input String in a format accepted by date(), defaults to "now".
766 * @param string $format The date format specification string (see {@link PHP_MANUAL#date}).
767 * @param mixed $tz Time zone to be used for the date. Special cases: boolean true for user
768 * setting, boolean false for server setting.
769 * @param boolean $gregorian True to use Gregorian calendar.
770 *
771 * @return string A date translated by the given format and time zone.
772 *
773 * @see strftime
774 * @since 1.5
775 */
776 public static function date($input = 'now', $format = null, $tz = true, $gregorian = false)
777 {
778 // Get some system objects.
779 $config = JFactory::getConfig();
780 $user = JFactory::getUser();
781
782 // UTC date converted to user time zone.
783 if ($tz === true)
784 {
785 // Get a date object based on UTC.
786 $date = JFactory::getDate($input, 'UTC');
787
788 // Set the correct time zone based on the user configuration.
789 $date->setTimezone($user->getTimezone());
790 }
791 // UTC date converted to server time zone.
792 elseif ($tz === false)
793 {
794 // Get a date object based on UTC.
795 $date = JFactory::getDate($input, 'UTC');
796
797 // Set the correct time zone based on the server configuration.
798 $date->setTimezone(new DateTimeZone($config->get('offset')));
799 }
800 // No date conversion.
801 elseif ($tz === null)
802 {
803 $date = JFactory::getDate($input);
804 }
805 // UTC date converted to given time zone.
806 else
807 {
808 // Get a date object based on UTC.
809 $date = JFactory::getDate($input, 'UTC');
810
811 // Set the correct time zone based on the server configuration.
812 $date->setTimezone(new DateTimeZone($tz));
813 }
814
815 // If no format is given use the default locale based format.
816 if (!$format)
817 {
818 $format = JText::_('DATE_FORMAT_LC1');
819 }
820 // $format is an existing language key
821 elseif (JFactory::getLanguage()->hasKey($format))
822 {
823 $format = JText::_($format);
824 }
825
826 if ($gregorian)
827 {
828 return $date->format($format, true);
829 }
830
831 return $date->calendar($format, true);
832 }
833
834 /**
835 * Creates a tooltip with an image as button
836 *
837 * @param string $tooltip The tip string.
838 * @param mixed $title The title of the tooltip or an associative array with keys contained in
839 * {'title','image','text','href','alt'} and values corresponding to parameters of the same name.
840 * @param string $image The image for the tip, if no text is provided.
841 * @param string $text The text for the tip.
842 * @param string $href A URL that will be used to create the link.
843 * @param string $alt The alt attribute for img tag.
844 * @param string $class CSS class for the tool tip.
845 *
846 * @return string
847 *
848 * @since 1.5
849 */
850 public static function tooltip($tooltip, $title = '', $image = 'tooltip.png', $text = '', $href = '', $alt = 'Tooltip', $class = 'hasTooltip')
851 {
852 if (is_array($title))
853 {
854 foreach (array('image', 'text', 'href', 'alt', 'class') as $param)
855 {
856 if (isset($title[$param]))
857 {
858 $$param = $title[$param];
859 }
860 }
861
862 if (isset($title['title']))
863 {
864 $title = $title['title'];
865 }
866 else
867 {
868 $title = '';
869 }
870 }
871
872 if (!$text)
873 {
874 $alt = htmlspecialchars($alt, ENT_COMPAT, 'UTF-8');
875 $text = static::image($image, $alt, null, true);
876 }
877
878 if ($href)
879 {
880 $tip = '<a href="' . $href . '">' . $text . '</a>';
881 }
882 else
883 {
884 $tip = $text;
885 }
886
887 if ($class === 'hasTip')
888 {
889 // Still using MooTools tooltips!
890 $tooltip = htmlspecialchars($tooltip, ENT_COMPAT, 'UTF-8');
891
892 if ($title)
893 {
894 $title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8');
895 $tooltip = $title . '::' . $tooltip;
896 }
897 }
898 else
899 {
900 $tooltip = self::tooltipText($title, $tooltip, 0);
901 }
902
903 return '<span class="' . $class . '" title="' . $tooltip . '">' . $tip . '</span>';
904 }
905
906 /**
907 * Converts a double colon separated string or 2 separate strings to a string ready for bootstrap tooltips
908 *
909 * @param string $title The title of the tooltip (or combined '::' separated string).
910 * @param string $content The content to tooltip.
911 * @param boolean $translate If true will pass texts through JText.
912 * @param boolean $escape If true will pass texts through htmlspecialchars.
913 *
914 * @return string The tooltip string
915 *
916 * @since 3.1.2
917 */
918 public static function tooltipText($title = '', $content = '', $translate = true, $escape = true)
919 {
920 // Initialise return value.
921 $result = '';
922
923 // Don't process empty strings
924 if ($content !== '' || $title !== '')
925 {
926 // Split title into title and content if the title contains '::' (old Mootools format).
927 if ($content === '' && !(strpos($title, '::') === false))
928 {
929 list($title, $content) = explode('::', $title, 2);
930 }
931
932 // Pass texts through JText if required.
933 if ($translate)
934 {
935 $title = JText::_($title);
936 $content = JText::_($content);
937 }
938
939 // Use only the content if no title is given.
940 if ($title === '')
941 {
942 $result = $content;
943 }
944 // Use only the title, if title and text are the same.
945 elseif ($title === $content)
946 {
947 $result = '<strong>' . $title . '</strong>';
948 }
949 // Use a formatted string combining the title and content.
950 elseif ($content !== '')
951 {
952 $result = '<strong>' . $title . '</strong><br />' . $content;
953 }
954 else
955 {
956 $result = $title;
957 }
958
959 // Escape everything, if required.
960 if ($escape)
961 {
962 $result = htmlspecialchars($result);
963 }
964 }
965
966 return $result;
967 }
968
969 /**
970 * Displays a calendar control field
971 *
972 * @param string $value The date value
973 * @param string $name The name of the text field
974 * @param string $id The id of the text field
975 * @param string $format The date format
976 * @param mixed $attribs Additional HTML attributes
977 * The array can have the following keys:
978 * readonly Sets the readonly parameter for the input tag
979 * disabled Sets the disabled parameter for the input tag
980 * autofocus Sets the autofocus parameter for the input tag
981 * autocomplete Sets the autocomplete parameter for the input tag
982 * filter Sets the filter for the input tag
983 *
984 * @return string HTML markup for a calendar field
985 *
986 * @since 1.5
987 *
988 */
989 public static function calendar($value, $name, $id, $format = '%Y-%m-%d', $attribs = array())
990 {
991 $tag = JFactory::getLanguage()->getTag();
992 $calendar = JFactory::getLanguage()->getCalendar();
993 $direction = strtolower(JFactory::getDocument()->getDirection());
994
995 // Get the appropriate file for the current language date helper
996 $helperPath = 'system/fields/calendar-locales/date/gregorian/date-helper.min.js';
997
998 if (!empty($calendar) && is_dir(JPATH_ROOT . '/media/system/js/fields/calendar-locales/date/' . strtolower($calendar)))
999 {
1000 $helperPath = 'system/fields/calendar-locales/date/' . strtolower($calendar) . '/date-helper.min.js';
1001 }
1002
1003 // Get the appropriate locale file for the current language
1004 $localesPath = 'system/fields/calendar-locales/en.js';
1005
1006 if (is_file(JPATH_ROOT . '/media/system/js/fields/calendar-locales/' . strtolower($tag) . '.js'))
1007 {
1008 $localesPath = 'system/fields/calendar-locales/' . strtolower($tag) . '.js';
1009 }
1010 elseif (is_file(JPATH_ROOT . '/media/system/js/fields/calendar-locales/' . strtolower(substr($tag, 0, -3)) . '.js'))
1011 {
1012 $localesPath = 'system/fields/calendar-locales/' . strtolower(substr($tag, 0, -3)) . '.js';
1013 }
1014
1015 $readonly = isset($attribs['readonly']) && $attribs['readonly'] === 'readonly';
1016 $disabled = isset($attribs['disabled']) && $attribs['disabled'] === 'disabled';
1017 $autocomplete = isset($attribs['autocomplete']) && $attribs['autocomplete'] === '';
1018 $autofocus = isset($attribs['autofocus']) && $attribs['autofocus'] === '';
1019 $required = isset($attribs['required']) && $attribs['required'] === '';
1020 $filter = isset($attribs['filter']) && $attribs['filter'] === '';
1021 $todayBtn = isset($attribs['todayBtn']) ? $attribs['todayBtn'] : true;
1022 $weekNumbers = isset($attribs['weekNumbers']) ? $attribs['weekNumbers'] : true;
1023 $showTime = isset($attribs['showTime']) ? $attribs['showTime'] : false;
1024 $fillTable = isset($attribs['fillTable']) ? $attribs['fillTable'] : true;
1025 $timeFormat = isset($attribs['timeFormat']) ? $attribs['timeFormat'] : 24;
1026 $singleHeader = isset($attribs['singleHeader']) ? $attribs['singleHeader'] : false;
1027 $hint = isset($attribs['placeholder']) ? $attribs['placeholder'] : '';
1028 $class = isset($attribs['class']) ? $attribs['class'] : '';
1029 $onchange = isset($attribs['onChange']) ? $attribs['onChange'] : '';
1030
1031 $showTime = ($showTime) ? "1" : "0";
1032 $todayBtn = ($todayBtn) ? "1" : "0";
1033 $weekNumbers = ($weekNumbers) ? "1" : "0";
1034 $fillTable = ($fillTable) ? "1" : "0";
1035 $singleHeader = ($singleHeader) ? "1" : "0";
1036
1037 // Format value when not nulldate ('0000-00-00 00:00:00'), otherwise blank it as it would result in 1970-01-01.
1038 if ($value && $value !== JFactory::getDbo()->getNullDate() && strtotime($value) !== false)
1039 {
1040 $tz = date_default_timezone_get();
1041 date_default_timezone_set('UTC');
1042 $inputvalue = strftime($format, strtotime($value));
1043 date_default_timezone_set($tz);
1044 }
1045 else
1046 {
1047 $inputvalue = '';
1048 }
1049
1050 $data = array(
1051 'id' => $id,
1052 'name' => $name,
1053 'class' => $class,
1054 'value' => $inputvalue,
1055 'format' => $format,
1056 'filter' => $filter,
1057 'required' => $required,
1058 'readonly' => $readonly,
1059 'disabled' => $disabled,
1060 'hint' => $hint,
1061 'autofocus' => $autofocus,
1062 'autocomplete' => $autocomplete,
1063 'todaybutton' => $todayBtn,
1064 'weeknumbers' => $weekNumbers,
1065 'showtime' => $showTime,
1066 'filltable' => $fillTable,
1067 'timeformat' => $timeFormat,
1068 'singleheader' => $singleHeader,
1069 'tag' => $tag,
1070 'helperPath' => $helperPath,
1071 'localesPath' => $localesPath,
1072 'direction' => $direction,
1073 'onchange' => $onchange,
1074 );
1075
1076 return JLayoutHelper::render('joomla.form.field.calendar', $data, null, null);
1077 }
1078
1079 /**
1080 * Add a directory where JHtml should search for helpers. You may
1081 * either pass a string or an array of directories.
1082 *
1083 * @param string $path A path to search.
1084 *
1085 * @return array An array with directory elements
1086 *
1087 * @since 1.5
1088 */
1089 public static function addIncludePath($path = '')
1090 {
1091 // Loop through the path directories
1092 foreach ((array) $path as $dir)
1093 {
1094 if (!empty($dir) && !in_array($dir, static::$includePaths))
1095 {
1096 array_unshift(static::$includePaths, JPath::clean($dir));
1097 }
1098 }
1099
1100 return static::$includePaths;
1101 }
1102
1103 /**
1104 * Internal method to get a JavaScript object notation string from an array
1105 *
1106 * @param array $array The array to convert to JavaScript object notation
1107 *
1108 * @return string JavaScript object notation representation of the array
1109 *
1110 * @since 3.0
1111 * @deprecated 4.0 Use `json_encode()` or `Joomla\Registry\Registry::toString('json')` instead
1112 */
1113 public static function getJSObject(array $array = array())
1114 {
1115 JLog::add(
1116 __METHOD__ . " is deprecated. Use json_encode() or \\Joomla\\Registry\\Registry::toString('json') instead.",
1117 JLog::WARNING,
1118 'deprecated'
1119 );
1120
1121 $elements = array();
1122
1123 foreach ($array as $k => $v)
1124 {
1125 // Don't encode either of these types
1126 if ($v === null || is_resource($v))
1127 {
1128 continue;
1129 }
1130
1131 // Safely encode as a Javascript string
1132 $key = json_encode((string) $k);
1133
1134 if (is_bool($v))
1135 {
1136 $elements[] = $key . ': ' . ($v ? 'true' : 'false');
1137 }
1138 elseif (is_numeric($v))
1139 {
1140 $elements[] = $key . ': ' . ($v + 0);
1141 }
1142 elseif (is_string($v))
1143 {
1144 if (strpos($v, '\\') === 0)
1145 {
1146 // Items such as functions and JSON objects are prefixed with \, strip the prefix and don't encode them
1147 $elements[] = $key . ': ' . substr($v, 1);
1148 }
1149 else
1150 {
1151 // The safest way to insert a string
1152 $elements[] = $key . ': ' . json_encode((string) $v);
1153 }
1154 }
1155 else
1156 {
1157 $elements[] = $key . ': ' . static::getJSObject(is_object($v) ? get_object_vars($v) : $v);
1158 }
1159 }
1160
1161 return '{' . implode(',', $elements) . '}';
1162 }
1163 }
1164