<?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 2016 by Ryan Cramer
 * https://processwire.com
 * 
 * @property string $dateInputFormat
 * @property String $timeInputFormat
 * @property int $timeInputSelect
 * @property int $datepicker
 * @property string $yearRange
 * @property int|bool $defaultToday
 *
 *
 */

class InputfieldDatetime extends Inputfield {

	const defaultDateInputFormat = 'Y-m-d';

	const datepickerNo = 0;		// no datepicker
	const datepickerClick = 1;	// datepicker on click
	const datepickerInline = 2; 	// inline datepicker, always visible (no timepicker support)
	const datepickerFocus = 3; 	// datepicker on field focus

	public static function getModuleInfo() {
		return array(
			'title' => __('Datetime', __FILE__), // Module Title
			'summary' => __('Inputfield that accepts date and optionally time', __FILE__), // Module Summary
			'version' => 106,
			'permanent' => true, 
			);
	}

	/**
	 * Initialize the date/time inputfield
	 *
	 */
	public function init() {
		$this->attr('type', 'text'); 
		$this->attr('size', 25); 
		$this->attr('placeholder', '');
		$this->set('dateInputFormat', self::defaultDateInputFormat);
		$this->set('timeInputFormat', '');
		$this->set('timeInputSelect', 0);
		$this->set('datepicker', self::datepickerNo); 
		$this->set('yearRange', '');
		$this->set('defaultToday', 0); 

		if($this->languages) foreach($this->languages as $language) {
			/** @var Language $language */
			// account for alternate formats in other languages
			if($language->isDefault()) continue; 
			$this->set("dateInputFormat$language", '');
			$this->set("timeInputFormat$language", '');
		}

		parent::init();
	}

	/**
	 * 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 $this
	 *
	 */
	public function renderReady(Inputfield $parent = null, $renderValueMode = false) {
		$this->addClass('InputfieldNoFocus', 'wrapClass');
		list($dateFormat, $timeFormat) = $this->getInputFormats();
		if($dateFormat) {}
		$useTime = false;
		$language = $this->wire('languages') ? $this->wire('user')->language : null;

		if($this->datepicker) {
			
			$this->wire('modules')->get('JqueryCore'); // Jquery Core required before Jquery UI
			$this->wire('modules')->get('JqueryUI');
			$this->addClass("InputfieldDatetimeDatepicker InputfieldDatetimeDatepicker{$this->datepicker}");
			
			if(strlen($timeFormat) && $this->datepicker != self::datepickerInline) {
				// add in the timepicker script, if applicable
				$useTime = true;
				$url = $this->config->urls->get('InputfieldDatetime');
				$this->config->scripts->add($url . 'timepicker/jquery-ui-timepicker-addon.min.js');
				$this->config->styles->add($url . 'timepicker/jquery-ui-timepicker-addon.min.css');
			}
			if($language) {
				// include i18n support for the datepicker 
				// note that the 'xx' in the filename is just a placeholder to indicate what should be replaced for translations, as that file doesn't exist
				$langFile = ltrim($this->_('/wire/modules/Jquery/JqueryUI/i18n/jquery.ui.datepicker-xx.js'), '/'); // Datepicker translation file // Replace 'xx' with jQuery UI language code or specify your own js file
				if(is_file($this->config->paths->root . $langFile)) {
					// add a custom language file
					$this->config->scripts->add($this->config->urls->root . $langFile);
				} else {
					// attempt to auto-find one based on the language name (which are often 2 char language codes)
					$langFile = "wire/modules/Jquery/JqueryUI/i18n/jquery.ui.datepicker-{$language->name}.js";
					if(is_file($this->config->paths->root . $langFile)) $this->config->scripts->add($this->config->urls->root . $langFile);
				}
				if($useTime) {
					$langFile = $this->_('timepicker/i18n/jquery-ui-timepicker-xx.js'); // Timepicker translation file // Replace 'xx' with jQuery UI language code or specify your own js file. Timepicker i18n files are located in /wire/modules/Inputfield/InputfieldDatetime/timepicker/i18n/.
					$path = $this->config->paths->get('InputfieldDatetime');
					$url = $this->config->urls->get('InputfieldDatetime');
					if(is_file($path . $langFile)) {
						// add a custom language file
						$this->config->scripts->add($url . $langFile);
					} else {
						// attempt to auto-find one based on the language name (which are often 2 char language codes)
						$langFile = str_replace('-xx.', "-$language->name.", $langFile);
						if(is_file($path . $langFile)) {
							$this->config->scripts->add($url . $langFile);
						}
					}
				}
			}
		}
		parent::renderReady($parent, $renderValueMode); 
	}

	/**
	 * Render the date/time inputfield
	 * 
	 * @return string
	 *
	 */
	public function ___render() {
	
		$sanitizer = $this->wire('sanitizer');
		$datetime = $this->wire('datetime');
		list($dateFormat, $timeFormat) = $this->getInputFormats();
		$useTime = false;
		if(strlen($timeFormat) && $this->datepicker && $this->datepicker != self::datepickerInline) $useTime = true; 

		$attrs = $this->getAttributes();
		$value = $attrs['value'];
		$valueTS = (int) $value*1000; // TS=for datepicker/javascript, which uses milliseconds rather than seconds
		unset($attrs['value']);

		if(!$value && $this->defaultToday) {
			$value = date($dateFormat);
			if($timeFormat) $value .= ' ' . date($timeFormat);
			$valueTS = time()*1000;

		} else if($value) {
			$value = trim(date($dateFormat . ' ' . $timeFormat, (int) $value));
		}

		$value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');

		$dateFormatJS = $sanitizer->entities($datetime->convertDateFormat($dateFormat, 'js'));
		$timeFormatJS = $useTime ? $datetime->convertDateFormat($timeFormat, 'js') : '';

		if(strpos($timeFormatJS, 'h24') !== false) {
			// 24 hour format
			$timeFormatJS = str_replace(array('hh24', 'h24'), array('HH', 'H'), $timeFormatJS);
			$ampm = 0;
		} else {
			$ampm = 1;
		}

		if(strlen($timeFormatJS)) $timeFormatJS = $sanitizer->entities($timeFormatJS);
		if(empty($value)) $value = '';
		$yearRange = $sanitizer->entities($this->yearRange);

		$out = 	
			"<input " . $this->getAttributesString($attrs) . " " .
			"value='$value' " .
			"data-dateformat='$dateFormatJS' " .
			"data-timeformat='$timeFormatJS' " .
			"data-timeselect='$this->timeInputSelect' " . 
			"data-ts='$valueTS' " .
			"data-ampm='$ampm' " .
			(strlen($yearRange) ? "data-yearrange='$yearRange' " : '') .
			"/>";

		return $out;
	}

	/**
	 * Render value for presentation, non-input
	 *
	 */
	public function ___renderValue() {
		$value = $this->attr('value'); 
		if(!$value) return '';
		$format = trim($this->dateInputFormat . ' ' . $this->timeInputFormat); 
		return $this->wire('datetime')->formatDate($value, trim($format));
	}

	/**
	 * Capture setting of the 'value' attribute and convert string dates to unix timestamp
	 * 
	 * @param string $key
	 * @param mixed $value
	 * @return $this
	 *
	 */
	public function setAttribute($key, $value) {
		if($key == 'value') {
			$value = $this->wire('datetime')->stringToTimestamp($value, $this->getInputFormats(true));
		}
		return parent::setAttribute($key, $value); 
	}

	/**
	 * Get the input format string for the user's language
	 *
	 * thanks to @oliverwehn (#1463)
	 *
	 * @param bool $getString Specify true to get a format string rather than an array
	 * @return array|string of dateInputFormat timeInputFormat
	 *
	 */
	protected function getInputFormats($getString = false) {

		$inputFormats = array();
		$language = $this->wire('user')->language;
		$useLanguages = $this->wire('languages') && $language && !$language->isDefault();

		foreach(array('date', 'time') as $type) {

			$inputFormat = '';

			if($useLanguages) {
				$inputFormat = trim($this->getSetting("{$type}InputFormat{$language->id}"));
			}

			if(!strlen($inputFormat)) {
				// fallback to default language
				$inputFormat = $this->get("{$type}InputFormat");
			}

			$inputFormats[] = $inputFormat;
		}

		if($getString) return trim(implode(' ', $inputFormats));

		return $inputFormats;
	}

	/**
	 * Date/time Inputfield configuration, per field
	 *
	 */
	public function ___getConfigInputfields() {

		$inputfields = parent::___getConfigInputfields();
		$languages = $this->wire('languages');
		$datetime = $this->wire('datetime');

		/** @var InputfieldInteger $f */
		$f = $this->modules->get('InputfieldInteger');
		$f->setAttribute('name', 'size'); 
		$f->label = $this->_('Size');
		$f->attr('value', $this->attr('size')); 
		$f->attr('size', 4); 
		$f->description = $this->_('The displayed width of this field (in characters).'); 
		$inputfields->append($f);

		/** @var InputfieldRadios $f */
		$f= $this->modules->get('InputfieldRadios');
		$f->label = $this->_('Date Picker');
		$f->setAttribute('name', 'datepicker'); 
		$f->addOption(self::datepickerNo, $this->_('No date/time picker'));
		$f->addOption(self::datepickerFocus, $this->_('Date/time picker on field focus') . ' ' . 
			$this->_('(recommended)')); 
		$f->addOption(self::datepickerClick, $this->_('Date/time picker on button click')); 
		// @todo this datepickerInline option displays a datepicker that is too large, not fully styled
		$f->addOption(self::datepickerInline, $this->_('Inline date picker always visible (no time picker)')); 
		$f->attr('value', (int) $this->datepicker); 
		$inputfields->append($f);

		/** @var InputfieldFieldset $fieldset */
		$fieldset = $this->modules->get('InputfieldFieldset'); 
		$fieldset->label = $this->_('Date/Time Input Formats');  

		/** @var InputfieldSelect $f */
		$f = $this->modules->get('InputfieldSelect');
		$f->attr('name', '_dateInputFormat');
		$f->label = $this->_('Date Input Format');
		$f->description = $this->_('Select the format to be used for user input to this field. Your selection will populate the field below this, which you may customize further if needed.');
		$f->icon = 'calendar';
		//$f->addOption('', $this->_('None'));
		$date = strtotime('2016-04-08 5:10:02 PM');
		foreach($datetime->getDateFormats() as $format) {
			$dateFormatted = $datetime->formatDate($date, $format);
			if($format == 'U') $dateFormatted .= " " . $this->_('(unix timestamp)');
			$f->addOption($format, $dateFormatted);
			if($this->dateInputFormat == $format) $f->attr('value', $format);
		}
		$f->attr('onchange', "$('#Inputfield_dateInputFormat').val($(this).val());");
		$fieldset->add($f);

		/** @var InputfieldSelect $f */
		$f = $this->modules->get('InputfieldSelect');
		$f->attr('name', '_timeInputFormat');
		$f->label = $this->_('Time Input Format');
		$f->addOption('', $this->_('None'));
		$f->description = $this->_('Select an optional time format to be used for input. If used, the calendar option will include a time picker.'); 
		$f->icon = 'clock-o';
		foreach($datetime->getTimeFormats() as $format) {
			if(strpos($format, '!') === 0) continue; // skip relative formats
			$timeFormatted = $datetime->formatDate($date, $format);
			$f->addOption($format, $timeFormatted);
			if($this->timeInputFormat == $format) $f->attr('value', $format);
		}
		$f->attr('onchange', "$('#Inputfield_timeInputFormat').val($(this).val());");
		$f->collapsed = Inputfield::collapsedBlank;
		$f->columnWidth = 50;
		$fieldset->add($f);
	
		/** @var InputfieldRadios $f */
		$f = $this->modules->get("InputfieldRadios");
		$f->attr('name', 'timeInputSelect');
		$f->label = $this->_('Time Input Type');
		$f->description = $this->_('Sliders (default) let the user slide controls to choose the time, where as Select lets the user select the time from a drop-down select.');
		$f->icon = 'clock-o';
		$f->addOption(0, $this->_('Sliders'));
		$f->addOption(1, $this->_('Select'));
		$f->optionColumns = 1;
		$f->columnWidth = 50;
		$f->showIf = "_timeInputFormat!='', datepicker!=" . self::datepickerNo;
		$f->attr('value', $this->timeInputSelect);
		$fieldset->add($f);

		/** @var InputfieldText $f */
		$f = $this->modules->get("InputfieldText");
		$f->attr('name', 'dateInputFormat');
		$f->attr('value', $this->dateInputFormat ? $this->dateInputFormat : self::defaultDateInputFormat);
		$f->attr('size', 20);
		$f->label = $this->_('Date Input Format Code');
		$f->description = $this->_('This is automatically built from the date select above, unless you modify it.'); 
		$f->icon = 'calendar';
		$notes = $this->_('See the [PHP date](http://www.php.net/manual/en/function.date.php) function reference for more information on how to customize these formats.'); 
		if($languages) $notes .= "\n" . $this->_('You may optionally specify formats for other languages here as well. Any languages left blank will inherit the default setting.'); 
		$f->notes = $notes; 
		$f->collapsed = Inputfield::collapsedYes;
		$f1 = $f;
		$fieldset->add($f);
		
		/** @var InputfieldText $f */
		$f = $this->modules->get("InputfieldText");
		$f->attr('name', 'timeInputFormat');
		$f->attr('value', $this->timeInputFormat ? $this->timeInputFormat : '');
		$f->attr('size', 20);
		$f->label = $this->_('Time Input Format Code');
		$f->description = $this->_('This is automatically built from the time select above, unless you modify it.'); 
		$f->icon = 'clock-o';
		$f->notes = $notes; 
		$f->collapsed = Inputfield::collapsedYes;
		$f2 = $f;
		

		if($languages) {
			$f1->useLanguages = true; 
			$f2->useLanguages = true; 
			foreach($languages as $language) {
				if($language->isDefault()) continue; 
				$f1->set("value$language", (string) $this->get("dateInputFormat$language")); 	
				$f2->set("value$language", (string) $this->get("timeInputFormat$language")); 	
			}
		}

		$fieldset->add($f1);
		$fieldset->add($f2);
		
		$inputfields->add($fieldset);
		
		/** @var InputfieldText $f */
		$f = $this->modules->get("InputfieldText");
		$f->attr('name', 'yearRange');
		$f->attr('value', $this->yearRange);
		$f->attr('size', 10);
		$f->label = $this->_('Date Picker Year Range');
		$f->description = $this->_('When the date picker is used, it has a selectable year range minus and plus 10 years from the current year. To extend or reduce that, specify the quantity of years before and after [current year] in this format: "-30:+20", which would show 30 years before now and 20 years after now.');
		$f->notes = $this->_('Default when no value present is "-10:+10" which shows a date picker year range 10 years before now, and 10 years after now.');
		$f->icon = 'arrows-h';
		$f->collapsed = Inputfield::collapsedBlank;
		$f->showIf = 'datepicker!=' . self::datepickerNo;
		$inputfields->append($f);

		/** @var InputfieldCheckbox $f */
		$f = $this->modules->get('InputfieldCheckbox');
		$f->setAttribute('name', 'defaultToday'); 
		$f->attr('value', 1); 
		if($this->defaultToday) $f->attr('checked', 'checked'); 
		$f->label = $this->_("Default to today's date?");
		$f->description = $this->_("If checked, this field will hold the current date when no value is entered."); // Default today description
		$f->columnWidth = 50;
		$inputfields->append($f);
		
		/** @var InputfieldText $field */
		$field = $this->modules->get('InputfieldText');
		$field->setAttribute('name', 'placeholder');
		$field->label = $this->_('Placeholder Text');
		$field->setAttribute('value', $this->attr('placeholder'));
		$field->description = $this->_('Optional placeholder text that appears in the field when blank.');
		$field->columnWidth = 50;
		$inputfields->append($field);


		return $inputfields; 
	}
	
}
