%PDF- <> %âãÏÓ endobj 2 0 obj <> endobj 3 0 obj <>/ExtGState<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI] >>/Annots[ 28 0 R 29 0 R] /MediaBox[ 0 0 595.5 842.25] /Contents 4 0 R/Group<>/Tabs/S>> endobj ºaâÚÎΞ-ÌE1ÍØÄ÷{òò2ÿ ÛÖ^ÔÀá TÎ{¦?§®¥kuµùÕ5sLOšuY>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<> endobj 2 0 obj<>endobj 2 0 obj<>es 3 0 R>> endobj 2 0 obj<> ox[ 0.000000 0.000000 609.600000 935.600000]/Fi endobj 3 0 obj<> endobj 7 1 obj<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI]>>/Subtype/Form>> stream
<?php
/**
* @package yii2-datecontrol
* @author Kartik Visweswaran <kartikv2@gmail.com>
* @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2022
* @version 1.9.9
*/
namespace kartik\datecontrol;
use DateTime;
use DateTimeZone;
use Exception;
use kartik\base\Config;
use kartik\base\InputWidget;
use kartik\base\Lib;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use yii\helpers\Url;
/**
* DateControl widget enables you to manage formatting of date/time separately for display (View) and saving to
* database (Model).
*
* For example,
*
* ```php
* use kartik\datecontrol\DateControl;
* // usage of rendering date control widget as an active field within Yii active form
* echo $form->field($model, 'datetime_2')->widget(DateControl::class, [
* 'displayFormat' => 'php:d-M-Y H:i:s',
* 'type' => DateControl::FORMAT_DATETIME
* ]);
* ```
*
* @author Kartik Visweswaran <kartikv2@gmail.com>
*/
class DateControl extends InputWidget
{
/**
* @var string date only format type.
*/
const FORMAT_DATE = 'date';
/**
* @var string time only format type.
*/
const FORMAT_TIME = 'time';
/**
* @var string date and time format type.
*/
const FORMAT_DATETIME = 'datetime';
/**
* @var array the english date settings.
*/
private static $_enSettings = [
'days' => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
'daysShort' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
'months' => [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
],
'monthsShort' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
'meridiem' => ['AM', 'PM'],
];
/**
* @var string data type to use for the displayed date control. One of the FORMAT constants.
*/
public $type = self::FORMAT_DATE;
/**
* @var boolean whether to use ajaxConversion to process date format for the widget.
*/
public $ajaxConversion;
/**
* @var string the format string for displaying the date. If not set, will automatically use the settings from the
* [[Module]] based on the [[type]] property.
*/
public $displayFormat;
/**
* @var string the default format string to be save the date as. If not set, will automatically use the settings
* from the [[Module]].
*/
public $saveFormat;
/**
* @var string the timezone for the displayed date. If not set, no timezone setting will be applied for formatting.
* @see http://php.net/manual/en/timezones.php
*/
public $displayTimezone;
/**
* @var string the timezone for the saved date. If not set, no timezone setting will be applied for formatting.
* @see http://php.net/manual/en/timezones.php
*/
public $saveTimezone;
/**
* @var boolean whether to automatically use \kartik\widgets based on `$type`. Will use these widgets:
*
* - [[\kartik\date\DatePicker]] when [[type]] is set to [[FORMAT_DATE]]
* - [[\kartik\time\TimePicker]] when [[type]] is set to [[FORMAT_TIME]]
* - [[\kartik\datetime\DateTimePicker]] when [[type]] is set to [[FORMAT_DATETIME]]
*
* If this property is not set, this will default to `true.`
*/
public $autoWidget;
/**
* @var string any custom widget class to use. Will only be used if [[autoWidget]] is set to `false`.
*/
public $widgetClass;
/**
* @var array the configuration options for the widget which will be parsed only in one of the following cases:
*
* - when [[autoWidget]] is `true` and this corresponds to widget settings for `DatePicker`, `TimePicker`, or
* `DateTimePicker` based on the [[$type]] setting, OR
* - when [[autoWidget]] is `false` and [[widgetClass]] is set and this allows to set the configuration options for
* the particular widget class.
*/
public $widgetOptions = [];
/**
* @var array the HTML attributes for the display input. This property is applicable and parsed only if
* [[autoWidget]] is `false` and [[widgetClass]] is empty or not set. For a widget, the [[widgetOptions]] must be
* used to configure the widget settings.
*/
public $options = [];
/**
* @var array the HTML attributes for the base model input that will be saved typically to database. The following
* special options are recognized:
* - `type`: _string_, whether to generate a 'hidden' or 'text' input. Defaults to 'hidden'.
* - `label`: _string_, any label to be placed before the input. Will be only displayed if 'type' is 'text'.
*/
public $saveOptions = [];
/**
* @var boolean whether to fire an asynchronous ajax request. Defaults to `true`. You can set this to `false` for
* cases, where you need this to be fired synchronously. For example when using this widget as a filter in
* [[\kartik\grid\GridView]].
*/
public $asyncRequest = true;
/**
* @inheritdoc
*/
public $pluginName = 'datecontrol';
/**
* @var Module the `datecontrol` module instance.
*/
protected $_module;
/**
* @var array the parsed widget class settings for each type (defaults from the module setting if not set).
*/
protected $_widgetSettings = [];
/**
* @var boolean whether translation is needed.
*/
private $_doTranslate = false;
/**
* Parses and normalizes a date source and converts it to a [[DateTime]] object by parsing it based on specified
* format.
*
* @param string $source the date source pattern.
* @param string $format the date format.
* @param string $timezone the date timezone.
* @param array|string $settings the locale/language date settings.
*
* @return DateTime
*/
public static function getTimestamp($source, $format, $timezone = null, $settings = [])
{
if (!isset($source) || $source === '') {
return null;
}
$source = static::parseLocale($source, $format, $settings);
if (Lib::substr($format, 0, 1) !== '!') {
$format = '!' . $format;
}
if ($timezone != null) {
$timestamp = DateTime::createFromFormat($format, $source, new DateTimeZone($timezone));
} else {
$timestamp = DateTime::createFromFormat($format, $source);
}
return $timestamp;
}
/**
* Fetches the locale settings file.
*
* @param string $lang the locale/language ISO code.
*
* @return string the locale file name.
*/
protected static function getLocaleFile($lang)
{
$s = DIRECTORY_SEPARATOR;
$file = __DIR__ . "{$s}locales{$s}{$lang}{$s}dateSettings.php";
if (!file_exists($file)) {
$langShort = Config::getLang($lang);
$file = __DIR__ . "{$s}locales{$s}{$langShort}{$s}dateSettings.php";
}
return $file;
}
/**
* Parses locale data and returns an english format.
*
* @param string $source the date source pattern.
* @param string $format the date format.
* @param array|string $settings the locale/language date settings.
*
* @return string the converted date source to english.
*/
protected static function parseLocale($source, $format, $settings = [])
{
if (empty($settings)) {
return $source;
}
foreach (self::$_enSettings as $key => $value) {
if (!empty($settings[$key]) && static::checkFormatKey($format, $key)) {
$source = Lib::str_ireplace($settings[$key], $value, $source);
}
}
return $source;
}
/**
* Checks if the format string contains the relevant date format pattern based on the passed key.
*
* @param string $format the date format string
* @param string $key the key to check
*
* @return boolean
*/
protected static function checkFormatKey($format, $key)
{
switch ($key) {
case 'months':
return Lib::strpos($format, 'F') !== false;
case 'monthsShort':
return Lib::strpos($format, 'M') !== false;
case 'days':
return Lib::strpos($format, 'l') !== false;
case 'daysShort':
return Lib::strpos($format, 'D') !== false;
case 'meridiem':
return Lib::stripos($format, 'A') !== false;
default:
return false;
}
}
/**
* @inheritdoc
*/
public function init()
{
$this->initConfig();
if (!isset($this->ajaxConversion)) {
$this->ajaxConversion = $this->_module->ajaxConversion;
}
if (!$this->ajaxConversion && ($this->displayTimezone != null || $this->saveTimezone != null)) {
throw new InvalidConfigException(
"You must set 'ajaxConversion' to 'true' when using time-zones for display or save."
);
}
parent::init();
$this->initLanguage();
$this->setDataVar($this->pluginName);
$this->saveOptions['id'] = $this->options['id'];
$this->options['id'] .= '-disp';
if ($this->isWidget()) {
$this->widgetOptions['options']['id'] = $this->options['id'];
}
$this->_doTranslate = isset($this->language) && $this->language != 'en';
if ($this->autoWidget && !isset($this->_widgetSettings[$this->type]['bsVersion']) && isset($this->bsVersion)) {
$this->_widgetSettings[$this->type]['bsVersion'] = $this->bsVersion;
}
$this->initBsVersion();
if ($this->_doTranslate && $this->autoWidget) {
$this->_widgetSettings[$this->type]['options']['language'] = $this->language;
}
$this->setLocale();
}
/**
* @inheritdoc
*/
public function run()
{
$this->registerAssets();
echo $this->getDisplayInput() . $this->getSaveInput();
parent::run();
}
/**
* Gets the formatted display date value.
*
* @param string $data the input date data.
*
* @return string
*/
public function getDisplayValue($data)
{
$saveDate = $data;
$saveFormat = $this->saveFormat;
$settings = $this->_doTranslate ? ArrayHelper::getValue($this->pluginOptions, 'dateSettings', []) : [];
$date = static::getTimestamp($saveDate, $saveFormat, $this->saveTimezone, $settings);
if ($date && $date instanceof DateTime) {
if ($this->displayTimezone != null) {
$date->setTimezone(new DateTimeZone($this->displayTimezone));
}
if ($this->type === self::FORMAT_DATE) {
$value = date($this->displayFormat, $date->getTimestamp());
} else {
$value = $date->format($this->displayFormat);
}
if ($this->_doTranslate) {
$value = $this->translateDate($value, $this->displayFormat);
}
return $value;
}
return null;
}
/**
* Initializes widget based on module settings.
*
* @throws InvalidConfigException
*/
protected function initConfig()
{
$this->_module = Config::initModule(Module::class);
if (!isset($this->autoWidget)) {
$this->autoWidget = $this->_module->autoWidget;
}
if (!$this->autoWidget && !empty($this->widgetClass) && !class_exists($this->widgetClass)) {
throw new InvalidConfigException("The widgetClass '{$this->widgetClass}' entered is invalid.");
}
if ($this->autoWidget === null) {
$this->autoWidget = true;
}
$this->_widgetSettings = $this->_module->widgetSettings;
if (empty($this->displayFormat)) {
$this->displayFormat = $this->_module->getDisplayFormat($this->type);
} else {
$this->displayFormat = Module::parseFormat($this->displayFormat, $this->type);
}
if (empty($this->saveFormat)) {
$this->saveFormat = $this->_module->getSaveFormat($this->type);
} else {
$this->saveFormat = Module::parseFormat($this->saveFormat, $this->type);
}
if (empty($this->displayTimezone)) {
$this->displayTimezone = $this->_module->getDisplayTimezone();
}
if (empty($this->saveTimezone)) {
$this->saveTimezone = $this->_module->getSaveTimezone();
}
// skip timezone validations when using date only inputs
if ($this->type === self::FORMAT_DATE) {
$this->displayTimezone = $this->saveTimezone = null;
}
if ($this->autoWidget) {
$this->_widgetSettings = [
self::FORMAT_DATE => ['class' => '\kartik\date\DatePicker'],
self::FORMAT_DATETIME => ['class' => '\kartik\datetime\DateTimePicker'],
self::FORMAT_TIME => ['class' => '\kartik\time\TimePicker'],
];
Config::validateInputWidget(
$this->_widgetSettings[$this->type]['class'],
"for DateControl '{$this->type}' format"
);
foreach ($this->_widgetSettings as $type => $setting) {
$this->_widgetSettings[$type]['options'] = $this->_module->autoWidgetSettings[$type];
$this->_widgetSettings[$type]['disabled'] = $this->disabled;
$this->_widgetSettings[$type]['readonly'] = $this->readonly;
}
}
if (empty($this->widgetClass) && !empty($this->_widgetSettings[$this->type]['class'])) {
$this->widgetClass = $this->_widgetSettings[$this->type]['class'];
}
}
/**
* Whether a widget is used to render the display.
*
* @return boolean
*/
protected function isWidget()
{
return ($this->autoWidget || !empty($this->widgetClass));
}
/**
* Generates the display input.
*
* @return string
* @throws Exception
*/
protected function getDisplayInput()
{
$name = ($this->hasModel() ? $this->attribute : $this->name) . '-' . $this->options['id'];
$value = !isset($this->value) || $this->value === '' ? '' : $this->getDisplayValue($this->value);
if (!$this->isWidget()) {
if (empty($this->options['class'])) {
$this->options['class'] = 'form-control';
}
return Html::textInput($name, $value, $this->options);
}
$opts = $this->widgetOptions;
if (!empty($this->displayFormat) && $this->autoWidget) {
$defaultOptions = Module::defaultWidgetOptions($this->type, $this->displayFormat);
$opts = ArrayHelper::merge($defaultOptions, $opts);
}
if (!empty($this->_widgetSettings[$this->type]['options'])) {
$opts = ArrayHelper::merge(
$this->_widgetSettings[$this->type]['options'], $opts
);
}
unset($opts['model'], $opts['attribute']);
$opts['name'] = $name;
$opts['value'] = $value;
/**
* @var InputWidget $class
*/
$class = $this->widgetClass;
if (property_exists($class, 'disabled')) {
$opts['disabled'] = $this->disabled;
}
if (property_exists($class, 'readonly')) {
$opts['readonly'] = $this->readonly;
}
$this->widgetOptions = $opts;
return $class::widget($opts);
}
/**
* Generates the save input.
*
* @return string
*/
protected function getSaveInput()
{
$type = ArrayHelper::remove($this->saveOptions, 'type', 'hidden');
$label = ArrayHelper::remove($this->saveOptions, 'label', '');
if ($type === 'text') {
$this->saveOptions['tabindex'] = 10000;
return $label . ($this->hasModel() ?
Html::activeTextInput($this->model, $this->attribute, $this->saveOptions) :
Html::textInput($this->name, $this->value, $this->saveOptions));
}
return $this->hasModel() ?
Html::activeHiddenInput($this->model, $this->attribute, $this->saveOptions) :
Html::hiddenInput($this->name, $this->value, $this->saveOptions);
}
/**
* Translate the date string.
*
* @param string $data the input date data.
* @param string $format the input date format.
*
* @return string the translated date.
*/
protected function translateDate($data, $format)
{
$out = $data;
foreach (self::$_enSettings as $key => $value) {
if (static::checkFormatKey($format, $key)) {
$out = $this->translate($out, $key);
}
}
return $out;
}
/**
* Translate a date pattern based on type.
*
* @param string $string input date string.
* @param string $type the type of date pattern as set in [[$_enSettings]].
*
* @return string the translated string.
*/
protected function translate($string, $type)
{
if (empty($this->pluginOptions['dateSettings'][$type])) {
return $string;
}
return Lib::str_ireplace(self::$_enSettings[$type], $this->pluginOptions['dateSettings'][$type], $string);
}
/**
* Sets the locale using the locales configuration settings.
*/
protected function setLocale()
{
if (!$this->_doTranslate || !empty($this->pluginOptions['dateSettings'])) {
return;
}
$file = static::getLocaleFile($this->language);
if (file_exists($file)) {
/** @noinspection PhpIncludeInspection */
$this->pluginOptions['dateSettings'] = require($file);
}
}
/**
* Registers assets for the [[DateControl]] widget.
*/
protected function registerAssets()
{
$view = $this->getView();
DateFormatterAsset::register($view);
DateControlAsset::register($view);
$pluginOptions = empty($this->pluginOptions) ? [] : $this->pluginOptions;
$this->pluginOptions = ArrayHelper::merge(
[
'idSave' => $this->saveOptions['id'],
'url' => $this->ajaxConversion ? Url::to($this->_module->convertAction) : '',
'type' => $this->type,
'saveFormat' => $this->saveFormat,
'dispFormat' => $this->displayFormat,
'saveTimezone' => $this->saveTimezone,
'dispTimezone' => $this->displayTimezone,
'asyncRequest' => $this->asyncRequest,
], $pluginOptions
);
$this->registerPlugin($this->pluginName);
$pluginData = 'data-krajee-' . $this->pluginName;
if (!empty($this->options[$pluginData])) {
$this->widgetOptions['options'][$pluginData] = $this->options[$pluginData];
}
}
}