artabro/wire/modules/Inputfield/InputfieldDatetime/InputfieldDatetime.module

407 lines
12 KiB
Text
Raw Normal View History

2024-08-27 11:35:37 +02:00
<?php namespace ProcessWire;
/**
* ProcessWire Datetime Inputfield
*
* Provides input for date and optionally time values.
*
* For documentation about the fields used in this class, please see:
* /wire/core/Fieldtype.php
*
* ProcessWire 3.x, Copyright 2023 by Ryan Cramer
* https://processwire.com
*
* ~~~~~~
* // get a datetime Inputfield
* $f = $modules->get('InputfieldDatetime');
* $f->attr('name', 'test_date');
* $f->label = 'Test date';
* $f->val(time()); // value is get or set a UNIX timestamp
*
* // date input with jQuery UI datepicker on focus
* $f->inputType = 'text'; // not necessary as this is the default
* $f->datepicker = InputfieldDatetime::datepickerFocus;
*
* // date selects
* $f->inputType = 'select';
* $f->dateSelectFormat = 'mdy'; // month abbr (i.e. 'Sep'), day, year
* $f->dateSelectFormat = 'Mdy'; // month full (i.e. 'September'), day, year
* $f->yearFrom = 2019; // optional year range from
* $f->yearTo = 2024; // optional year range to
*
* // HTML5 date, time or date+time inputs
* $f->inputType = 'html';
* $f->htmlType = 'date'; // or 'time' or 'datetime'
* ~~~~~~
*
* @property int $value This Inputfield keeps the value in UNIX timestamp format (int).
* @property string $inputType Input type to use, one of: "text", "select" or "html" (when html type is used, also specify $htmlType).
* @property int|bool $defaultToday When no value is present, default to todays date/time?
* @property int $subYear Substitute year when month+day or time only selections are made (default=2010)
* @property int $subDay Substitute day when month+year or time only selectinos are made (default=8)
* @property int $subMonth Substitute month when time-only selections are made (default=4)
* @property int $subHour Substitute hour when date-only selections are made (default=0)
* @property int $subMinute Substitute minute when date-only selection are made (default=0)
* @property bool|int $requiredAttr When combined with "required" option, this also makes it use the HTML5 "required" attribute (default=false).
*
* Properties specific to "text" input type (with optional jQuery UI datepicker)
* =============================================================================
* @property int $datepicker jQuery UI datepicker type (see `datepicker*` constants)
* @property string $yearRange Selectable year range in the format `-30:+20` where -30 is number of years before now and +20 is number of years after now.
* @property int $timeInputSelect jQuery UI timeSelect type (requires datepicker)—specify 1 to use a `<select>` for time input, or 0 to use a slider (default=0)
* @property string $dateInputFormat Date input format to use, see WireDateTime::$dateFormats (default='Y-m-d')
* @property string $timeInputFormat Time input format to use, see WireDateTime::$timeFormats (default='')
* @property string $placeholder Placeholder attribute text
*
* Properties specific to "html" input type
* ========================================
* @property string $htmlType When "html" is selection for $inputType, this should be one of: "date", "time" or "datetime".
* @property int $timeStep Refers to the step attribute on time inputs
* @property string $timeMin Refers to the min attribute on time inputs (HH:MM)
* @property string $timeMax Refers to the max attribute on time inputs (HH:MM)
* @property int $dateStep Refers to the step attribute on date inputs
* @property string $dateMin Refers to the min attribute on date inputs, ISO-8601 (YYYY-MM-DD)
* @property string $dateMax Refers to the max attribute on date inputs, ISO-8601 (YYYY-MM-DD)
*
* Properties specific to "select" input type
* ==========================================
* @property string $dateSelectFormat Format to use for date select
* @property string $timeSelectFormat Format to use for time select
* @property int $yearFrom First selectable year (default=current year - 100)
* @property int $yearTo Last selectable year (default=current year + 20)
* @property bool|int $yearLock Disallow selection of years outside the yearFrom/yearTo range? (default=false)
*
*
*/
class InputfieldDatetime extends Inputfield {
public static function getModuleInfo() {
return array(
'title' => __('Datetime', __FILE__), // Module Title
'summary' => __('Inputfield that accepts date and optionally time', __FILE__), // Module Summary
'version' => 107,
'permanent' => true,
);
}
/**
* ISO-8601 date/time formats (default date input format)
*
* #pw-internal
*
*/
const defaultDateInputFormat = 'Y-m-d';
const defaultTimeInputFormat = 'H:i';
const secondsTimeInputFormat = 'H:i:s';
/**
* jQuery UI datepicker: None
*
*/
const datepickerNo = 0;
/**
* jQuery UI datepicker: Click button to show
*
*/
const datepickerClick = 1;
/**
* jQuery UI datepicker: Inline datepicker always visible (no timepicker support)
*
*/
const datepickerInline = 2;
/**
* jQuery UI datepicker: Show when input focused (recommend option when using datepicker)
*
*/
const datepickerFocus = 3;
/**
* @var InputfieldDatetimeType[]
*
*/
protected $inputTypes = array();
/**
* Initialize the date/time inputfield
*
*/
public function init() {
$this->attr('type', 'text');
$this->attr('size', 25);
$this->attr('placeholder', '');
$this->set('defaultToday', 0);
$this->set('inputType', 'text');
$this->set('subYear', 2010);
$this->set('subMonth', 4);
$this->set('subDay', 8);
$this->set('subHour', 0);
$this->set('subMinute', 0);
$this->set('requiredAttr', 0);
foreach($this->getInputTypes() as $type) {
$this->setArray($type->getDefaultSettings());
}
parent::init();
}
/**
* Return ISO-8601 substitute date (combination of subYear, subMonth, subDay)
*
* #pw-internal
*
* @return string
*
*/
public function subDate() {
$year = (int) parent::getSetting('subYear');
$month = (int) parent::getSetting('subMonth');
$day = (int) parent::getSetting('subDay');
if($year < 1000 || $year > 2500) $year = (int) date('Y');
if($month > 12 || $month < 1) $month = 1;
if($month < 10) $month = "0$month";
if($day > 31 || $day < 1) $day = 1;
if($day < 10) $day = "0$day";
return "$year-$month-$day";
}
/**
* Return ISO-8601 substitute time (combination of subHour:subMinute:00)
*
* #pw-internal
*
* @return string
*
*/
public function subTime() {
$hour = (int) parent::getSetting('subHour');
$minute = (int) parent::getSetting('subMinute');
if($hour > 23 || $hour < 0) $hour = 0;
if($hour < 10) $hour = "0$hour";
if($minute > 59 || $minute < 0) $minute = 0;
if($minute < 10) $minute = "0$minute";
return "$hour:$minute:00";
}
/**
* Get all date/time input types
*
* @return InputfieldDatetimeType[]
*
*/
public function getInputTypes() {
if(count($this->inputTypes)) {
return $this->inputTypes;
}
$path = dirname(__FILE__) . '/';
require_once($path . 'InputfieldDatetimeType.php');
$dir = new \DirectoryIterator($path . 'types/');
foreach($dir as $file) {
if($file->isDir() || $file->isDot() || $file->getExtension() != 'php') continue;
require_once($file->getPathname());
$className = wireClassName($file->getBasename('.php'), true);
/** @var InputfieldDatetimeType $type */
$type = $this->wire(new $className($this));
$name = $type->getTypeName();
$this->inputTypes[$name] = $type;
}
return $this->inputTypes;
}
/**
* Get current date/time input type instance
*
* @param string $typeName
* @return InputfieldDatetimeType
*
*/
public function getInputType($typeName = '') {
$inputTypes = $this->getInputTypes();
if(!$typeName) $typeName = $this->inputType;
if(!$typeName || !isset($inputTypes[$typeName])) $typeName = 'text';
return $inputTypes[$typeName];
}
/**
* Set property
*
* @param string $key
* @param mixed $value
* @return Inputfield|WireData
*
*/
public function set($key, $value) {
if($key === 'dateMin' || $key === 'dateMax') {
if(is_int($value)) $value = date(self::defaultDateInputFormat, $value);
} else if($key === 'timeMin' || $key === 'timeMax') {
if(is_int($value)) $value = date(self::defaultTimeInputFormat, $value);
}
return parent::set($key, $value);
}
/**
* Called before the render method, from a hook in the Inputfield class
*
* We are overriding it here and checking for a datepicker, so that we can make sure
* jQuery UI is loaded before the InputfieldDatetime.js
*
* @param Inputfield $parent
* @param bool $renderValueMode
* @return bool
*
*/
public function renderReady(Inputfield $parent = null, $renderValueMode = false) {
$this->addClass("InputfieldNoFocus", 'wrapClass');
$this->getInputType()->renderReady();
return parent::renderReady($parent, $renderValueMode);
}
/**
* Render the date/time inputfield
*
* @return string
*
*/
public function ___render() {
return $this->getInputType()->render();
}
/**
* Render value for presentation, non-input
*
*/
public function ___renderValue() {
$out = $this->getInputType()->renderValue();
if($out) return $out;
$value = $this->attr('value');
if(!$value) return '';
$format = self::defaultDateInputFormat . ' ';
if($this->timeStep > 0 && $this->timeStep < 60) {
$format .= self::secondsTimeInputFormat;
} else {
$format .= self::defaultTimeInputFormat;
}
return $this->wire()->datetime->formatDate($value, trim($format));
}
/**
* Process input
*
* @param WireInputData $input
* @return Inputfield|InputfieldDatetime
*
*/
public function ___processInput(WireInputData $input) {
$valuePrevious = $this->val();
$value = $this->getInputType()->processInput($input);
if($value === false) {
// false indicates type is not processing input
parent::___processInput($input);
$value = $this->getAttribute('value');
} else {
$this->setAttribute('value', $value);
}
if($value !== $valuePrevious) {
$this->trackChange('value', $valuePrevious, $value);
$parent = $this->getParent();
if($parent) $parent->trackChange($this->name);
}
return $this;
}
/**
* Capture setting of the 'value' attribute and convert string dates to unix timestamp
*
* @param string $key
* @param mixed $value
* @return Inputfield|InputfieldDatetime
*
*/
public function setAttribute($key, $value) {
if($key === 'value') {
if(empty($value) && "$value" !== "0") {
// empty value thats not 0
$value = '';
} else if(is_int($value) || ctype_digit("$value")) {
// unix timestamp
$value = (int) $value;
} else if(strlen($value) > 8 && $value[4] === '-' && $value[7] === '-' && ctype_digit(substr($value, 0, 4))) {
// ISO-8601, i.e. 2010-04-08 02:48:00
$value = $this->wire()->datetime->strtotime($value);
if(!$value) $value = '';
} else {
$value = $this->getInputType()->sanitizeValue($value);
}
}
return parent::setAttribute($key, $value);
}
/**
* Date/time Inputfield configuration, per field
*
*/
public function ___getConfigInputfields() {
$inputfields = parent::___getConfigInputfields();
$inputTypes = $this->getInputTypes();
$modules = $this->wire()->modules;
/** @var InputfieldRadios $f */
$f = $modules->get('InputfieldRadios');
$f->attr('name', 'inputType');
$f->label = $this->_('Input Type');
$f->icon = 'calendar';
foreach($inputTypes as $inputTypeName => $inputType) {
$f->addOption($inputTypeName, $inputType->getTypeLabel());
}
$inputTypeVal = $this->getSetting('inputType');
if(!$inputTypeVal) $inputTypeVal = 'text';
if(!isset($inputTypes[$inputTypeVal])) $inputTypeVal = 'text';
$f->val($inputTypeVal);
$inputfields->add($f);
foreach($inputTypes as $inputTypeName => $inputType) {
/** @var InputfieldFieldset $fieldset */
$fieldset = $modules->get('InputfieldFieldset');
$fieldset->attr('name', '_' . $inputTypeName . 'Options');
$fieldset->label = $inputType->getTypeLabel();
$fieldset->showIf = 'inputType=' . $inputTypeName;
$inputType->getConfigInputfields($fieldset);
$inputfields->add($fieldset);
}
/** @var InputfieldCheckbox $f */
$f = $modules->get('InputfieldCheckbox');
$f->setAttribute('name', 'defaultToday');
$f->attr('value', 1);
if($this->defaultToday) $f->attr('checked', 'checked');
$f->label = $this->_('Default to todays date?');
$f->description = $this->_('If checked, this field will hold the current date when no value is entered.'); // Default today description
$inputfields->append($f);
return $inputfields;
}
}