artabro/wire/modules/Inputfield/InputfieldDatetime/InputfieldDatetime.module
2024-08-27 11:35:37 +02:00

406 lines
12 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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;
}
}