492 lines
14 KiB
PHP
492 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* @package awf
|
|
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
|
|
* @license GNU GPL version 3 or later
|
|
*/
|
|
|
|
namespace Awf\Html;
|
|
|
|
|
|
use Awf\Application\Application;
|
|
use Awf\Date\Date;
|
|
use Awf\Text\Text;
|
|
use Awf\Uri\Uri;
|
|
use Awf\Utils\ArrayHelper;
|
|
|
|
/**
|
|
* Generic HTML output abstraction class
|
|
*
|
|
* This class is based on the JHtml package of Joomla! 3 but heavily modified
|
|
*/
|
|
abstract class Html
|
|
{
|
|
/**
|
|
* Option values related to the generation of HTML output. Recognized
|
|
* options are:
|
|
* fmtDepth, integer. The current indent depth.
|
|
* fmtEol, string. The end of line string, default is linefeed.
|
|
* fmtIndent, string. The string to use for indentation, default is
|
|
* tab.
|
|
*
|
|
* @var array
|
|
*/
|
|
public static $formatOptions = array('format.depth' => 0, 'format.eol' => "\n", 'format.indent' => "\t");
|
|
|
|
/**
|
|
* Set format related options.
|
|
*
|
|
* Updates the formatOptions array with all valid values in the passed array.
|
|
*
|
|
* @param array $options Option key/value pairs.
|
|
*
|
|
* @return void
|
|
*
|
|
* @see Html::$formatOptions
|
|
*/
|
|
public static function setFormatOptions($options)
|
|
{
|
|
foreach ($options as $key => $val)
|
|
{
|
|
if (isset(static::$formatOptions[$key]))
|
|
{
|
|
static::$formatOptions[$key] = $val;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function caller. Call me line Html::_('HtmlClass.function', $param1, $param2)
|
|
*
|
|
* @param string $key The name of helper method to load, (prefix).(class).function
|
|
* prefix and class are optional and can be used to load custom
|
|
* html helpers.
|
|
*
|
|
* @return mixed
|
|
*
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
public static function _($key)
|
|
{
|
|
list($prefix, $file, $func) = static::extract($key);
|
|
|
|
$className = $prefix . ucfirst($file);
|
|
|
|
if (!class_exists($className))
|
|
{
|
|
$className = __NAMESPACE__ . '\\' . $prefix . '\\' . ucfirst($file);
|
|
}
|
|
|
|
if (!class_exists($className))
|
|
{
|
|
throw new \InvalidArgumentException(sprintf('%s %s not found.', $prefix, $file), 500);
|
|
}
|
|
|
|
$toCall = array($className, $func);
|
|
|
|
if (!is_callable($toCall))
|
|
{
|
|
throw new \InvalidArgumentException(sprintf('%s::%s not found.', $className, $func), 500);
|
|
}
|
|
|
|
$args = func_get_args();
|
|
|
|
// Remove function name from arguments
|
|
array_shift($args);
|
|
|
|
// PHP 5.3 workaround
|
|
$temp = array();
|
|
|
|
foreach ($args as &$arg)
|
|
{
|
|
$temp[] = &$arg;
|
|
}
|
|
|
|
return call_user_func_array($toCall, $temp);
|
|
}
|
|
|
|
/**
|
|
* Method to extract a key
|
|
*
|
|
* @param string $key The name of helper method to load, (prefix).(class).function
|
|
* prefix and class are optional and can be used to load custom html helpers.
|
|
*
|
|
* @return array Contains lowercase key, prefix, file, function.
|
|
*
|
|
* @since 1.6
|
|
*/
|
|
protected static function extract($key)
|
|
{
|
|
$key = preg_replace('#[^A-Z0-9_\.]#i', '', $key);
|
|
|
|
// Check to see whether we need to load a helper file
|
|
$parts = explode('.', $key);
|
|
|
|
$prefix = count($parts) === 3 ? array_shift($parts) : '\\Awf\\Html\\';
|
|
$file = count($parts) === 2 ? array_shift($parts) : '';
|
|
$func = array_shift($parts);
|
|
|
|
return array($prefix, $file, $func);
|
|
}
|
|
|
|
|
|
/**
|
|
* Write a <a></a> element
|
|
*
|
|
* @param string $url The relative URL to use for the href attribute
|
|
* @param string $text The target attribute to use
|
|
* @param array $attribs An associative array of attributes to add
|
|
*
|
|
* @return string <a></a> string
|
|
*/
|
|
public static function link($url, $text, $attribs = null)
|
|
{
|
|
if (is_array($attribs))
|
|
{
|
|
$attribs = ArrayHelper::toString($attribs);
|
|
}
|
|
|
|
return '<a href="' . $url . '" ' . $attribs . '>' . $text . '</a>';
|
|
}
|
|
|
|
/**
|
|
* Write a <iframe></iframe> element
|
|
*
|
|
* @param string $url The relative URL to use for the src attribute.
|
|
* @param string $name The target attribute to use.
|
|
* @param array $attribs An associative array of attributes to add.
|
|
* @param string $noFrames The message to display if the iframe tag is not supported.
|
|
*
|
|
* @return string <iframe></iframe> element or message if not supported.
|
|
*/
|
|
public static function iframe($url, $name, $attribs = null, $noFrames = '')
|
|
{
|
|
if (is_array($attribs))
|
|
{
|
|
$attribs = ArrayHelper::toString($attribs);
|
|
}
|
|
|
|
return '<iframe src="' . $url . '" ' . $attribs . ' name="' . $name . '">' . $noFrames . '</iframe>';
|
|
}
|
|
|
|
/**
|
|
* Returns formatted date according to a given format and time zone.
|
|
*
|
|
* @param string $input String in a format accepted by date(), defaults to "now".
|
|
* @param string $format The date format specification string (see {@link PHP_MANUAL#date}).
|
|
* @param mixed $tz Time zone to be used for the date. Special cases: boolean true for user
|
|
* setting, boolean false for server setting.
|
|
* @param Application $app The application from which we'll retrieve settings, null to use the default app
|
|
*
|
|
* @return string A date translated by the given format and time zone.
|
|
*
|
|
* @see strftime
|
|
*/
|
|
public static function date($input = 'now', $format = null, $tz = true, Application $app = null)
|
|
{
|
|
if (!is_object($app))
|
|
{
|
|
$app = Application::getInstance();
|
|
}
|
|
|
|
// Get some system objects.
|
|
$config = $app->getContainer()->appConfig;
|
|
$userManager = $app->getContainer()->userManager;
|
|
$user = $userManager->getUser();
|
|
|
|
// UTC date converted to user time zone.
|
|
if ($tz === true)
|
|
{
|
|
// Get a date object based on UTC.
|
|
$date = new Date($input, 'UTC');
|
|
|
|
// Set the correct time zone based on the user configuration.
|
|
$date->setTimeZone(new \DateTimeZone($user->getParameters()->get('timezone', $config->get('timezone'))));
|
|
}
|
|
// UTC date converted to server time zone.
|
|
elseif ($tz === false)
|
|
{
|
|
// Get a date object based on UTC.
|
|
$date = new Date($input, 'UTC');
|
|
|
|
// Set the correct time zone based on the server configuration.
|
|
$date->setTimeZone(new \DateTimeZone($config->get('timezone', 'UTC')));
|
|
}
|
|
// No date conversion.
|
|
elseif ($tz === null)
|
|
{
|
|
$date = new Date($input);
|
|
}
|
|
// UTC date converted to given time zone.
|
|
else
|
|
{
|
|
// Get a date object based on UTC.
|
|
$date = new Date($input, 'UTC');
|
|
|
|
// Set the correct time zone based on the server configuration.
|
|
$date->setTimeZone(new \DateTimeZone($tz));
|
|
}
|
|
|
|
// If no format is given use the default locale based format.
|
|
if (!$format)
|
|
{
|
|
$format = Text::_('DATE_FORMAT_LC1');
|
|
}
|
|
// $format is an existing language key
|
|
elseif (Text::hasKey($format))
|
|
{
|
|
$format = Text::_($format);
|
|
}
|
|
|
|
return $date->format($format, true);
|
|
}
|
|
|
|
/**
|
|
* Write a <img></img> element
|
|
*
|
|
* @param string $file The relative or absolute URL to use for the src attribute.
|
|
* @param string $alt The alt text.
|
|
* @param mixed $attribs String or associative array of attribute(s) to use.
|
|
* @param boolean $relative Path to file is relative to /media folder
|
|
* @param Application $app The application to get configuration from
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function image($file, $alt, $attribs = null, $relative = false, Application $app = null)
|
|
{
|
|
if ($relative)
|
|
{
|
|
if (!is_object($app))
|
|
{
|
|
$app = Application::getInstance();
|
|
}
|
|
|
|
$file = Uri::base(false, $app->getContainer()) . 'media/' . ltrim($file, '/');
|
|
}
|
|
|
|
return '<img src="' . $file . '" alt="' . $alt . '" '
|
|
. trim((is_array($attribs) ? ArrayHelper::toString($attribs) : $attribs) . ' /')
|
|
. '>';
|
|
}
|
|
|
|
/**
|
|
* Creates a tooltip with an image as button
|
|
*
|
|
* @param string $tooltip The tip string.
|
|
* @param mixed $title The title of the tooltip or an associative array with keys contained in
|
|
* {'title','image','text','href','alt'} and values corresponding to parameters of the same name.
|
|
* @param string $image The image for the tip, if no text is provided.
|
|
* @param string $text The text for the tip.
|
|
* @param string $href An URL that will be used to create the link.
|
|
* @param string $alt The alt attribute for img tag.
|
|
* @param string $class CSS class for the tool tip.
|
|
* @param Application $app The application to get configuration from
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function tooltip($tooltip, $title = '', $image = 'images/tooltip.png', $text = '', $href = '', $alt = 'Tooltip', $class = 'hasTooltip', Application $app = null)
|
|
{
|
|
if (!is_object($app))
|
|
{
|
|
$app = Application::getInstance();
|
|
}
|
|
|
|
if (is_array($title))
|
|
{
|
|
foreach (array('image', 'text', 'href', 'alt', 'class') as $param)
|
|
{
|
|
if (isset($title[$param]))
|
|
{
|
|
$$param = $title[$param];
|
|
}
|
|
}
|
|
|
|
if (isset($title['title']))
|
|
{
|
|
$title = $title['title'];
|
|
}
|
|
else
|
|
{
|
|
$title = '';
|
|
}
|
|
}
|
|
|
|
if (!$text)
|
|
{
|
|
$alt = htmlspecialchars($alt, ENT_COMPAT, 'UTF-8');
|
|
$text = static::image($image, $alt, null, true, $app);
|
|
}
|
|
|
|
if ($href)
|
|
{
|
|
$tip = '<a href="' . $href . '">' . $text . '</a>';
|
|
}
|
|
else
|
|
{
|
|
$tip = $text;
|
|
}
|
|
|
|
$tooltip = self::tooltipText($title, $tooltip, 0);
|
|
|
|
return '<span class="' . $class . '" title="' . $tooltip . '">' . $tip . '</span>';
|
|
}
|
|
|
|
/**
|
|
* Converts a double colon separated string or 2 separate strings to a string ready for bootstrap tooltips
|
|
*
|
|
* @param string $title The title of the tooltip (or combined '::' separated string).
|
|
* @param string $content The content to tooltip.
|
|
* @param int $translate If true will pass texts through Text.
|
|
* @param int $escape If true will pass texts through htmlspecialchars.
|
|
*
|
|
* @return string The tooltip string
|
|
*/
|
|
public static function tooltipText($title = '', $content = '', $translate = 1, $escape = 1)
|
|
{
|
|
// Return empty in no title or content is given.
|
|
if ($title == '' && $content == '')
|
|
{
|
|
return '';
|
|
}
|
|
|
|
// Split title into title and content if the title contains '::' (migrated Joomla! code, using the obsolete Mootools format).
|
|
if ($content == '' && !(strpos($title, '::') === false))
|
|
{
|
|
list($title, $content) = explode('::', $title, 2);
|
|
}
|
|
|
|
// Pass strings through Text.
|
|
if ($translate)
|
|
{
|
|
$title = Text::_($title);
|
|
$content = Text::_($content);
|
|
}
|
|
|
|
// Escape the strings.
|
|
if ($escape)
|
|
{
|
|
$title = str_replace('"', '"', $title);
|
|
$content = str_replace('"', '"', $content);
|
|
}
|
|
|
|
// Return only the content if no title is given.
|
|
if ($title == '')
|
|
{
|
|
return $content;
|
|
}
|
|
|
|
// Return only the title if title and text are the same.
|
|
if ($title == $content)
|
|
{
|
|
return '<strong>' . $title . '</strong>';
|
|
}
|
|
|
|
// Return the formatted sting combining the title and content.
|
|
if ($content != '')
|
|
{
|
|
return '<strong>' . $title . '</strong><br />' . $content;
|
|
}
|
|
|
|
// Return only the title.
|
|
return $title;
|
|
}
|
|
|
|
/**
|
|
* Displays a calendar control field
|
|
*
|
|
* This field can use either jQuery DatePicker (default) or Pikaday. This is controlled by the application Container,
|
|
* namely its awf_date_picker field. It can be either "jQuery" or "Pikaday".
|
|
*
|
|
* @param string $value The date value
|
|
* @param string $name The name of the text field
|
|
* @param string $id The id of the text field
|
|
* @param string $format The date format
|
|
* @param array $attribs Additional HTML attributes
|
|
* @param Application $app The application to get the configuration from
|
|
*
|
|
* @return string HTML markup for a calendar field
|
|
*/
|
|
public static function calendar($value, $name, $id, $format = 'yyyy-mm-dd', $attribs = null, Application $app = null)
|
|
{
|
|
static $done = [];
|
|
|
|
if (!is_object($app))
|
|
{
|
|
$app = Application::getInstance();
|
|
}
|
|
|
|
$attribs['class'] = isset($attribs['class']) ? $attribs['class'] : 'form-control';
|
|
$attribs['class'] = trim($attribs['class'] . ' hasTooltip calendar');
|
|
|
|
$readonly = isset($attribs['readonly']) && $attribs['readonly'] == 'readonly';
|
|
$disabled = isset($attribs['disabled']) && $attribs['disabled'] == 'disabled';
|
|
|
|
if (is_array($attribs))
|
|
{
|
|
$attribs = ArrayHelper::toString($attribs);
|
|
}
|
|
|
|
if (!$readonly && !$disabled)
|
|
{
|
|
// Load the calendar behavior
|
|
Behaviour::calendar();
|
|
|
|
// Only display the triggers once for each control.
|
|
if (!in_array($id, $done))
|
|
{
|
|
// Do I use jQuery date picker or Pikaday?
|
|
$container = $app->getContainer();
|
|
$pickerType = isset($container['awf_date_picker']) ? $container['awf_date_picker'] : 'jQuery';
|
|
$pickerType = !in_array($pickerType, ['jQuery', 'Pikaday']) ? 'jQuery' : $pickerType;
|
|
|
|
// @todo Implement a way for the application to override the language
|
|
$lang = Text::detectLanguage($app->getName());
|
|
|
|
$document = $app->getDocument();
|
|
|
|
if ($pickerType === 'jQuery')
|
|
{
|
|
$document
|
|
->addScriptDeclaration( <<< JS
|
|
akeeba.jQuery(document).ready(function(){
|
|
akeeba.jQuery('#$id-container').datepicker({
|
|
format: "$format",
|
|
todayBtn: "linked",
|
|
language: "$lang",
|
|
autoclose: true
|
|
});
|
|
})
|
|
JS
|
|
|
|
);
|
|
}
|
|
else
|
|
{
|
|
$document
|
|
->addScriptDeclaration( <<< JS
|
|
akeeba.System.documentReady(function() {
|
|
new Pikaday({
|
|
field: document.getElementById('$id-container'),
|
|
format: "$format",
|
|
});
|
|
});
|
|
JS
|
|
);
|
|
|
|
}
|
|
$done[] = $id;
|
|
}
|
|
|
|
return '<div class="input-group date" id="' . $id . '-container"><input type="text" title="' . (0 !== (int) $value ? static::date($value, null, null) : '')
|
|
. '" name="' . $name . '" id="' . $id . '" value="' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '" ' . $attribs . ' />'
|
|
. '<span class="input-group-btn" id="' . $id . '_img"><span class="btn btn-default"><span class="glyphicon glyphicon-calendar"></span></span></span></div>';
|
|
}
|
|
else
|
|
{
|
|
return '<input type="text" title="' . (0 !== (int) $value ? static::date($value, null, null) : '')
|
|
. '" value="' . (0 !== (int) $value ? static::_('date', $value, 'Y-m-d H:i:s', null) : '') . '" ' . $attribs
|
|
. ' /><input type="hidden" name="' . $name . '" id="' . $id . '" value="' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '" />';
|
|
}
|
|
}
|
|
}
|