Spade

Mini Shell

Directory:~$ /proc/self/root/home/lmsyaran/public_html/joomla5/plugins/fabrik_element/jdate/
Upload File

[Home] [System Details] [Kill Me]
Current File:~$ //proc/self/root/home/lmsyaran/public_html/joomla5/plugins/fabrik_element/jdate/jdate.php

<?php
/**
 * Plugin element to render date picker
 *
 * @package     Joomla.Plugin
 * @subpackage  Fabrik.element.date
 * @copyright   Copyright (C) 2005-2020  Media A-Team, Inc. - All rights
reserved.
 * @license     GNU/GPL http://www.gnu.org/copyleft/gpl.html
 */

// No direct access
defined('_JEXEC') or die('Restricted access');

use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutInterface;
use Joomla\CMS\Date\Date;
use Joomla\CMS\Profiler\Profiler;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\Utilities\ArrayHelper;
use Joomla\String\StringHelper;
use Fabrik\Helpers\Php;

/**
 * Plugin element to render date picker
 *
 * @package     Joomla.Plugin
 * @subpackage  Fabrik.element.date
 * @since       3.0
 */
class PlgFabrik_ElementJdate extends PlgFabrik_ElementList
{
	/**
	 * States the element should be ignored from advanced search all queries.
	 *
	 * @var bool  True, ignore in extended search all.
	 */
	protected $ignoreSearchAllDefault = true;

	/**
	 * Toggle to determine if storeDatabaseFormat resets the date to GMT
	 *
	 * @var bool
	 */
	protected $resetToGMT = true;

	/**
	 * Is the element a ranged filter (can depend on request data)
	 *
	 * @var bool
	 */
	protected $rangeFilterSet = false;

	/**
	 * Date offset with TZ, for use in front end display
	 *
	 * @var string
	 */
	protected $offsetDate = null;

	/**
	 * Does the element contain sub elements e.g checkboxes radiobuttons
	 *
	 * @var bool
	 */
	public $hasSubElements = false;

	/**
	 * Shows the data formatted for the list view
	 *
	 * @param   string   $data     Elements data
	 * @param   stdClass &$thisRow All the data in the lists current row
	 * @param   array    $opts     Rendering options
	 *
	 * @return  string    formatted value
	 */
	public function renderListData($data, stdClass &$thisRow, $opts =
array())
	{
        $profiler = Profiler::getInstance('Application');
        JDEBUG ? $profiler->mark("renderListData:
{$this->element->plugin}: start: {$this->element->name}")
: null;

        if ($data == '')
		{
			return parent::renderListData($data, $thisRow, $opts);
		}

		// @TODO: deal with time options (currently can be defined in
jdate_table_format param).
		$timeZone = new
\DateTimeZone($this->config->get('offset'));
		$params   = $this->getParams();
		$data     = FabrikWorker::JSONtoData($data, true);
		$f        = $params->get('jdate_table_format',
'Y-m-d');
		$format   = array();

		if (strstr($f, '%'))
		{
			$f = FabDate::strftimeFormatToDateFormat($f);
		}

		foreach ($data as $d)
		{
			if (FabrikWorker::isDate($d))
			{
				$date = Factory::getDate($d);
				$view = FArrayHelper::getValue($opts, 'view',
'list');

				if ($this->shouldApplyTz($view))
				{
					$date->setTimeZone($timeZone);
				}

				if ($f == '{age}')
				{
					$format[] = date('Y') - $date->format('Y',
true);
				}
				else
				{
					$format[] = $date->format($f, true);
				}
			}
			else
			{
				$format[] = '';
			}
		}

		$data = json_encode($format);

		return parent::renderListData($data, $thisRow, $opts);
	}

	/**
	 * Prepares the element data for CSV export
	 *
	 * @param   string $data     Element data
	 * @param   object &$thisRow All the data in the lists current row
	 *
	 * @return  string    formatted value
	 */
	public function renderListData_csv($data, &$thisRow)
	{
		// @TODO: deal with time options (currently can be defined in
jdate_table_format param).
		$timeZone = new
\DateTimeZone($this->config->get('offset'));
		$params   = $this->getParams();

		$this->getGroup();
		$data = FabrikWorker::JSONtoData($data, true);
		$f    = $params->get('jdate_table_format',
'Y-m-d');
		/* $$$ hugh - see http://fabrikar.com/forums/showthread.php?p=87507
		 * Really don't think we need to worry about $app->input
'incraw' here. The raw, GMT/MySQL data will get
		 * included in the _raw version of the element if incraw is selected.
Here we just want to output
		 * the regular non-raw, formatted, TZ'ed version.
		 */
		$incRaw = false;
		$format = array();

		foreach ($data as $d)
		{
			if (FabrikWorker::isDate($d))
			{
				if ($incRaw)
				{
					$format[] = $d;
				}
				else
				{
					$date = Factory::getDate($d);

					if ($this->shouldApplyTz('list'))
					{
						$date->setTimeZone($timeZone);
					}

					if ($f == '{age}')
					{
						$format[] = date('Y') - $date->format('Y',
true);
					}
					else
					{
						$format[] = $date->format($f, true);
					}
				}
			}
			else
			{
				$format[] = '';
			}
		}

		if (count($format) > 1)
		{
			return json_encode($format);
		}
		else
		{
			return implode('', $format);
		}
	}

	/**
	 * Used in things like date when its id is suffixed with _cal
	 * called from getLabel();
	 *
	 * @param   string &$id Initial id
	 *
	 * @return  void
	 */
	protected function modHTMLId(&$id)
	{
		$id = $id . '_cal';
	}

	/**
	 * Called by form model to build an array of values to encrypt
	 *
	 * Dates should have tz applied if needed - their value should be the
	 * same as the displayed readonly date.
	 *
	 * @param   array &$values Previously encrypted values
	 * @param   array $data    Form data
	 * @param   int   $c       Repeat group counter
	 *
	 * @return  void
	 */
	public function getValuesToEncrypt(&$values, $data, $c)
	{
		$name  = $this->getFullName(true, false);
		$group = $this->getGroup();

		if ($group->canRepeat())
		{
			if (!array_key_exists($name, $values))
			{
				$values[$name]['data'] = array();
			}

			$gmt                       = $this->getValue($data, $c);
			$localDate                 = $this->displayDate($gmt);
			$values[$name]['data'][$c] = FabrikWorker::isDate($gmt) ?
$localDate->toSql(true) : '';
		}
		else
		{
			$gmt                   = $this->getValue($data, $c);
			$localDate             = $this->displayDate($gmt);
			$values[$name]['data'] = FabrikWorker::isDate($gmt) ?
$localDate->toSql(true) : '';
		}
	}

	/**
	 * Draws the html form element
	 *
	 * @param   array $data          To pre-populate element with
	 * @param   int   $repeatCounter Repeat group counter
	 *
	 * @return  string    elements html
	 */
	public function render($data, $repeatCounter = 0)
	{
		$this->offsetDate = '';
		$name             = $this->getHTMLName($repeatCounter);
		$id               = $this->getHTMLId($repeatCounter);
		$params           = $this->getParams();
		$element          = $this->getElement();
		$format           = $params->get('jdate_form_format',
$params->get('jdate_table_format', 'Y-m-d'));

		if (strstr($format, '%'))
		{
			$format = FabDate::strftimeFormatToDateFormat($format);
		}

		$timeFormat = $this->getTimeFormat();

		// Value is in mySQL format GMT
		$gmt = $this->getValue($data, $repeatCounter);

		if (!FabrikWorker::isDate($gmt))
		{
			$date = '';
			$time = '';
		}
		else
		{
			// Set the date to display local time
			$localDate = $this->displayDate($gmt);

			// Get the formatted time
			$time = ($params->get('jdate_showtime', 0)) ? ' '
. $localDate->format($timeFormat, true) : '';

			// Formatted date
			$date = $localDate->format($format, true);
		}

		if (!$this->isEditable())
		{
			return $date . $time;
		}

		// Build HTML widget
		if ($params->get('jdate_showtime', 0) &&
!$element->hidden)
		{
			// Can't have names as simply [] as json only picks up the last one
			$timeElName = $name . '[time]';
			$name .= '[date]';
		}

		$class          = 'fabrikinput inputbox input ' .
$params->get('bootstrap_class', 'col-sm-6');
		$element->width = (int) $element->width < 0 ? 1 : (int)
$element->width;
		$calOpts        = array('class' => $class, 'size'
=> $element->width, 'maxlength' => '19');

		if ($params->get('jdate_allow_typing_in_field', true) ==
false)
		{
			//$calOpts['readonly'] = 'readonly';
		}

		if ($placeholder = $params->get('placeholder'))
		{
			$calOpts['placeholder'] = Text::_($placeholder);
		}

		if ($params->get('jdate_showtime', '0'))
		{
			$calOpts['showTime']   = '1';
			$calOpts['timeFormat'] =
$params->get('jdate_time_24', '1') ? '24'
: '12';
			$format .= ' ' . $timeFormat;
		}

		// J!4.4.0 issue breaks the time picker if 24h enabled; showing week
number will make it work again
		$calOpts['weekNumbers'] = true;
//$params->get('jdate_show_week_numbers', '0') ===
'1';

		$str[] = '<div class="fabrikSubElementContainer"
id="' . $id . '">';
		$str[] = $this->calendar($this->offsetDate, $name, $id .
'_cal', $format, $calOpts, $repeatCounter);

		$str[] = '</div>';

		return implode("\n", $str);
	}

	/**
	 * Render time button
	 *
	 * @param   string $timeElName Element name
	 * @param   string $time       Time value
	 * @param   array  &$str       Markup to append time button to
	 *
	 * @since   3.1b2
	 *
	 * @return  void
	 */
	protected function timeButton($timeElName, $time, &$str)
	{
		$params     = $this->getParams();
		$timeFormat = $params->get('jdate_time_format',
'H:i');
		$class      = 'inputbox fabrikinput timeField input ' .
$params->get('bootstrap_time_class', 'col-sm-2');
		$readOnly   = $params->get('jdate_allow_typing_in_field',
true) == false ? ' readonly="readonly" ' :
'';

		$str[] = '<div class="input-append">';

		$timeLength = StringHelper::strlen($timeFormat);
		FabrikHelperHTML::addPath(COM_FABRIK_BASE .
'plugins/fabrik_element/date/images/', 'image',
'form', false);
		$str[] = '<input type="text" class="' .
$class . '" ' . $readOnly . ' size="' .
$timeLength . '" value="' . $time . '"
name="'
			. $timeElName . '" />';
		$opts  = array('alt' =>
Text::_('PLG_ELEMENT_JDATE_TIME'), 'class' =>
'timeButton');
		$file  = 'clock';

		$btnLayout  = FabrikHelperHTML::getLayout('fabrik-button');
		$layoutData = (object) array(
			'class' => 'timeButton',
			'label' => FabrikHelperHTML::image($file, 'form',
@$this->tmpl, $opts),
			'aria'	=> ' aria-label="' .
Text::_('PLG_ELEMENT_JDATE_ARIA_LABEL_TIME') . '"'
		);

		$str[] = $btnLayout->render($layoutData);

		if ($j3)
		{
			$str[] = '</div>';
		}
	}

	/**
	 * Create the local datetime
	 *
	 * @param   string $gmt Datetime
	 *
	 * @since  3.0.9
	 *
	 * @return  Date
	 */
	protected function displayDate($gmt)
	{
		$date = Factory::getDate($gmt);

		if ($this->shouldApplyTz())
		{
			$timeZone = new
\DateTimeZone($this->config->get('offset'));
			$date->setTimeZone($timeZone);
		}

		$this->offsetDate = $date->toSql(true);

		return $date;
	}

	/**
	 * Should we apply the users timezone offset to the date for display
purposes
	 *
	 * This is a bit long winded and could be reduced. BUT the logic is such
that I think the
	 * verbosity maintains the readability of the logic
	 *
	 * @param   string $view Component view [list/form]
	 *
	 * @since   3.0.9
	 *
	 * @return  boolean
	 */
	protected function shouldApplyTz($view = 'form')
	{
		// if formatting for plugins (getProcessData), TZ has already been dealt
with
		if ($view == 'email')
		{
			return false;
		}

		$formModel    = $this->getFormModel();
		$params       = $this->getParams();
		$storeAsLocal = (bool) $params->get('jdate_store_as_local',
0);
		$newRecord    = $formModel->isNewRecord();
		$alwaysToday  = $params->get('jdate_alwaystoday', false);
		$defaultToday = $params->get('jdate_defaulttotoday');

		if ($storeAsLocal)
		{
			if ($view === 'list')
			{
				return false;
			}

			if ($newRecord)
			{
				if (($alwaysToday || $defaultToday) || $formModel->hasErrors())
				//if (($alwaysToday || $defaultToday))
				{
					// User supplied defaults should be in GMT, they are only applied if
no other default found.
					return true;
				}
			}

			if (!$newRecord)
			{
				return false;
			}
		}
		else
		{
			if ($newRecord)
			{
				if (($alwaysToday || $defaultToday) || $formModel->hasErrors() ||
$view === 'list')
				{
					// User supplied defaults should be in GMT, they are only applied if
no other default found.
					return true;
				}
			}

			if (!$newRecord)
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * Individual store database format
	 *
	 * @param   string $val Value
	 *
	 * @return  string    MySQL formatted date
	 */
	private function _indStoreDBFormat($val)
	{

		$params       = $this->getParams();
		$timeZone     = new
\DateTimeZone($this->config->get('offset'));
		$storeAsLocal = (bool) $params->get('jdate_store_as_local',
false);
		$alwaysToday  = $params->get('jdate_alwaystoday', false);

		if ($alwaysToday)
		{
			if (!$storeAsLocal)
			{
				$val = Factory::getDate('now');
			}
			else
			{
				$val = Factory::getDate('now', $timeZone);
			}

			$val = $val->toSql(true);

			return $val;
		}

		// $$$ hugh - sometimes still getting $val as an array with date and
time,
		// like on AJAX submissions?  Or maybe from getEmailData()?  Or both?
		if (is_array($val))
		{
			$val = FArrayHelper::getValue($val, 'date', '');
		}
		else
		{
			$val = empty($val) ? $val : urldecode($val);
		}

		if (!FabrikWorker::isDate($val))
		{
			return null;
		}

		jimport('joomla.utilities.date');
		$listModel = $this->getListModel();

		// $$$ hugh - offset_tz of 1 means 'in MySQL format, GMT'
		// $$$ hugh - offset_tz of 2 means 'in MySQL format, Local TZ'
		if ($listModel->importingCSV &&
$params->get('jdate_csv_offset_tz', '0') ==
'1')
		{
			return $val;
		}
		elseif ($listModel->importingCSV &&
$params->get('jdate_csv_offset_tz', '0') ==
'2')
		{
			return $this->toMySQLGMT(Factory::getDate($val));
		}

		// $$$ rob - as the date js code formats to the db format - just return
the value.
		$timeZone = new
\DateTimeZone($this->config->get('offset'));
		$val      = Factory::getDate($val, $timeZone)->toSql($storeAsLocal);

		return $val;
	}

	/**
	 * reset the date to GMT - reversing the offset
	 *
	 * @param   object $date Date to convert
	 *
	 * @return  string    MySQL formatted GMT date
	 */
	protected function toMySQLGMT($date)
	{
		if ($this->resetToGMT)
		{
			// $$$ rob 3.0 offset is no longer an integer but a timezone string
			$timeZone = new
\DateTimeZone($this->config->get('offset'));
			$hours    = $timeZone->getOffset($date) / (60 * 60);
			$invert   = false;

			if ($hours < 0)
			{
				$invert = true;

				// Intervals can only be positive - set invert property
				$hours = $hours * -1;
			}
			// 5.3 only
			if (class_exists('DateInterval'))
			{
				// need to use minutes, as DateInterval will barf on fractional hours
like 5.5 (India)
				$dateInterval         = new DateInterval('PT' . $hours * 60 .
'M');
				$dateInterval->invert = $invert;
				$date->sub($dateInterval);
			}
			else
			{
				$date->modify('+' . $hours . ' hour');
			}

			return $date->toSql(true);
		}

		return $date->toSql();
	}

	/**
	 * Manipulates posted form data for insertion into database
	 *
	 * @param   mixed $val  This elements posted form data
	 * @param   array $data Posted form data
	 *
	 * @return  mixed
	 */
	public function storeDatabaseFormat($val, $data)
	{

		if (!is_array($val))
		{
			/* $$$ hugh - we really need to work out why some AJAX data is not
getting urldecoded.
			 * but for now ... a bandaid.
			 */
			$val = empty($val) ? $val : urldecode($val);
		}

		// @TODO: deal with failed validations
		$groupModel = $this->getGroup();

		if ($groupModel->isJoin() && is_array($val))
		{
			$val = FArrayHelper::getValue($val, 'date', null);
		}
		else
		{
			if ($groupModel->canRepeat())
			{
				if (is_array($val))
				{
					$res = array();

					foreach ($val as $v)
					{
						$res[] = $this->_indStoreDBFormat($v);
					}

					return json_encode($res);
				}
			}
		}

		return $this->_indStoreDBFormat($val);
	}

	/**
	 * Used to format the data when shown in the form's email
	 *
	 * @param   mixed $value         Element's data
	 * @param   array $data          Form records data
	 * @param   int   $repeatCounter Repeat group counter
	 *
	 * @return  string    formatted value
	 */
	public function getEmailValue($value, $data = array(), $repeatCounter = 0)
	{
		if (
			(is_array($value) && empty($value))
			|| (
				is_array($value)
				&& FArrayHelper::getValue($value, 'date',
'') === ''
				&& FArrayHelper::getValue($value, 'time',
'') === ''
			)
			|| (!is_array($value) && trim($value) === '')
		)
		{
			return '';
		}

		$groupModel = $this->getGroup();

		if ($groupModel->isJoin() && $groupModel->canRepeat())
		{
			$value = $value[$repeatCounter];
		}

		if (is_array($value))
		{
			$date = FArrayHelper::getValue($value, 'date');
			$d    = Factory::getDate($date);
			$time = FArrayHelper::getValue($value, 'time', '');

			if ($time !== '')
			{
				$bits = explode(':', $time);
				$h    = FArrayHelper::getValue($bits, 0, 0);
				$m    = FArrayHelper::getValue($bits, 1, 0);
				$s    = FArrayHelper::getValue($bits, 2, 0);
				$d->setTime($h, $m, $s);
			}

			$value = $d->toSql();
		}

		// $$$ hugh - need to convert to database format so we GMT-ified date
		$dummy = new stdClass;
		$opts  = array(
			'view' => 'email'
		);
		return $this->renderListData($value, $dummy, $opts);
		/* $$$ rob - no need to covert to db format now as its posted as db
format already.
		 *return $this->renderListData($this->storeDatabaseFormat($value,
$data), new stdClass);
		 */
	}

	/**
	 * Determines the label used for the browser title
	 * in the form/detail views
	 *
	 * @param   array $data          Form data
	 * @param   int   $repeatCounter When repeating joined groups we need to
know what part of the array to access
	 * @param   array $opts          Options
	 *
	 * @return  string    Text to add to the browser's title
	 */
	public function getTitlePart($data, $repeatCounter = 0, $opts = array())
	{
		$gmt_date = $this->getValue($data, $repeatCounter, $opts);
		/* OK, now we've got the GMT date, convert it
		 * ripped the following off from renderListData ... SURELY we must have a
func
		 * somewhere that does this?
		 */
		$params       = $this->getParams();
		$storeAsLocal = (int) $params->get('jdate_store_as_local',
0);
		$timeZone     = new
\DateTimeZone($this->config->get('offset'));
		$f            = $params->get('jdate_table_format',
'Y-m-d');
		$tz_date      = '';

		if (FabrikWorker::isDate($gmt_date))
		{
			$date = Factory::getDate($gmt_date);

			if (!$storeAsLocal)
			{
				$date->setTimeZone($timeZone);
			}

			if ($f == '{age}')
			{
				$tz_date = date('Y') - $date->format('Y', true);
			}
			else
			{
				$tz_date = $date->format($f, true);
			}
		}

		return $tz_date;
	}

	/**
	 * Converts a raw value into its label equivalent
	 *
	 * @param   string &$v Raw value
	 *
	 * @return  void
	 */
	protected function toLabel(&$v)
	{
		$params   = $this->getParams();
		$f        = $params->get('jdate_table_format',
'Y-m-d');
		$timeZone = new
\DateTimeZone($this->config->get('offset'));

		if (FabrikWorker::isDate($v))
		{
			$date = Factory::getDate($v);
			/**
			 * $$$ rob - if not time selector then the date gets stored as
2009-11-13 00:00:00
			 * if we have a -1 timezone then date gets set to 2009-11-12 23:00:00
			 * then shown as 2009-11-12 which is wrong
			 */
			if ($params->get('jdate_showtime'))
			{
				$date->setTimeZone($timeZone);
			}

			if ($f == '{age}')
			{
				$v = date('Y') - $date->format('Y', true);
			}
			else
			{
				$v = $date->format($f, true);
			}
		}
		else
		{
			$v = '';
		}
	}

	/**
	 * Displays a calendar control field
	 *
	 * hacked from behaviour as you need to check if the element exists
	 * it might not as you could be using a custom template
	 *
	 * @param   string $value         The date value (must be in the same
format as supplied by $format)
	 * @param   string $name          The name of the text field
	 * @param   string $id            The id of the text field
	 * @param   string $format        The date format (not used)
	 * @param   array  $attribs       Additional html attributes
	 * @param   int    $repeatCounter Repeat group counter (not used)
	 *
	 * @return  string
	 */
	public function calendar($value, $name, $id, $format = 'Y-m-d',
$attribs = null, $repeatCounter = 0)
	{
		if (!strstr($format, '%'))
		{
			$format = FabDate::dateFormatToStrftimeFormat($format);
		}

		//$attribs['onChange'] = 'Fabrik.calSelect()';
		return HTMLHelper::calendar($value, $name, $id, $format, $attribs);
	}

	/**
	 * get the options used for the date elements calendar
	 *
	 * @param   int $id Repeat counter
	 *
	 * @return object ready for js encoding
	 */
	protected function _CalendarJSOpts($id)
	{
		$params            = $this->getParams();
		$opts              = new stdClass;
		$opts->inputField  = $id;
		$opts->button      = $id . "_btn";
		$opts->align       = "Tl";
		$opts->singleClick = true;
		$opts->firstDay    =
intval($params->get('jdate_firstday'));
		$format           = $params->get('jdate_form_format',
$params->get('jdate_table_format', 'Y-m-d'));

		if (strstr($format, '%'))
		{
			$format = FabDate::strftimeFormatToDateFormat($format);
		}

		$timeFormat = $this->getTimeFormat();

		if (!empty($timeFormat))
		{
			$format .= ' ' . $timeFormat;
		}

		$opts->timeFormat  = 24;
		$opts->ifFormat    = FabDate::dateFormatToStrftimeFormat($format);
		$opts->dateAllowFunc = $params->get('jdate_allow_func');

		return $opts;
	}

	/**
	 * Returns javascript which creates an instance of the class defined in
formJavascriptClass()
	 *
	 * @param   int $repeatCounter Repeat group counter
	 *
	 * @return  array
	 */
	public function elementJavascript($repeatCounter)
	{
		$params       = $this->getParams();
		$element      = $this->getElement();
		$id           = $this->getHTMLId($repeatCounter);
		$opts         = $this->getElementJSOptions($repeatCounter);

		// if read only, convert back to local display time
		if (!$this->isEditable() && !empty($opts->value))
		{
			$localDate = $this->displayDate($opts->value);
			$opts->value = $localDate->toSql(true);
		}

		$opts->hidden = (bool) $this->getElement()->hidden;

		// Used uniquely in reset();
		$opts->defaultVal     = $this->getFrontDefaultValue();
		$opts->showtime       = (!$element->hidden &&
$params->get('jdate_showtime', 0)) ? true : false;
		$opts->timelabel      =
Text::_('PLG_ELEMENT_JDATE_TIME_LABEL', true);
		$opts->typing         = (bool)
$params->get('jdate_allow_typing_in_field', true);
		$opts->timedisplay    = $params->get('jdate_timedisplay',
1);
		//$opts->dateTimeFormat =
$params->get('jdate_time_format', '');
		$opts->allowedDates   = $this->getAllowedPHPDates();
		$opts->watchElement   = $this->getWatchId();
		$opts->id             = $this->getId();
		$opts->locale         =
Factory::getApplication()->getLanguage()->getTag();

		// For reuse if element is duplicated in repeat group
		$opts->calendarSetup = $this->_CalendarJSOpts($id);
		$opts->advanced      = true;

		return array('FbJDateTime', $id, $opts);
	}

	/**
	 * Get the HTML id for the watch element
	 *
	 * @param   int $repeatCounter repeat group counter
	 *
	 * @return  string
	 */
	protected function getWatchId($repeatCounter = 0)
	{
		$elementModel = $this->getWatchElement();

		return $elementModel ? $elementModel->getHTMLId($repeatCounter) :
'';
	}

	/**
	 * Get the element to watch. Changes to this element will trigger the
cdd's lookup
	 *
	 * @return  plgFabrik_Element
	 */
	protected function getWatchElement()
	{
		if (!isset($this->watchElement))
		{
			$watch = $this->getParams()->get('jdate_observe',
'');

			if ($watch === '')
			{
				return false;
			}

			$this->watchElement = $this->getFormModel()->getElement($watch,
true);

			if (!$this->watchElement)
			{
				// This element is a child element, so $watch is in the parent element
(in another form)
				$pluginManager = FabrikWorker::getPluginManager();
				$parent        = $pluginManager->getElementPlugin($watch);

				// These are the possible watch elements
				$children = $parent->getElementDescendents();

				// Match the possible element ids with the current form's element
ids
				$elIds   = $this->getFormModel()->getElementIds();
				$matched = array_values(array_intersect($elIds, $children));

				// Load the matched element
				$this->watchElement =
$pluginManager->getElementPlugin($matched[0]);
			}
		}

		return $this->watchElement;
	}

	/**
	 * Ajax - get the allowed dates - called when watched element changes
	 *
	 * @return  void
	 */
	public function onAjax_getAllowedDates()
	{
		$this->loadMeForAjax();
		$dates = $this->getAllowedPHPDates();
		echo json_encode($dates);
	}

	/**
	 * Get the allowed dates based on evaluated PHP code
	 *
	 * @return multitype:|array
	 */
	protected function getAllowedPHPDates()
	{
		$params = $this->getParams();
		$data   = $this->getFormModel()->data;
		$php    = $params->get('jdate_allow_php_func',
'');
		$dates  = array();

		if ($php === '')
		{
			return $dates;
		}

		FabrikWorker::clearEval();
		$dates = Php::Eval(['code' => $php,
'vars'=>['data'=>$data]]);
		FabrikWorker::logEval($dates, 'Eval exception : ' .
$this->getElement()->name . '::getAllowedPHPDates() : %s');

		return (array) $dates;
	}

	/**
	 * Get database field description
	 *
	 * @return  string  Db field type
	 */
	public function getFieldDescription()
	{
		if ($this->encryptMe())
		{
			return 'BLOB';
		}

		return "DATETIME";
	}

	/**
	 * Get an array of element html ids and their corresponding
	 * js events which trigger a validation.
	 * Examples of where this would be overwritten include timedate element
with time field enabled
	 *
	 * @param   int $repeatCounter Repeat group counter
	 *
	 * @return  array  html ids to watch for validation
	 */
	public function getValidationWatchElements($repeatCounter)
	{
		$return   = array();
		$id       = $this->getHTMLId($repeatCounter);
		$return[] = array('id' => $id, 'triggerEvent'
=> 'blur');

		return $return;
	}

	/**
	 * Element plugin specific method for setting unencrypted values back into
post data
	 *
	 * @param   array  &$post Data passed by ref
	 * @param   string $key   Key
	 * @param   string $data  Elements unencrypted data
	 *
	 * @return  void
	 */
	public function setValuesFromEncryt(&$post, $key, $data)
	{
        $group     = $this->getGroup();

        if ($group->canRepeat())
        {
            foreach ($data as &$d)
            {
                if (FabrikWorker::isDate($d)) {
                    // Only apply date logic to actual date data, if blank
for example we should leave blank
                    $date = Factory::getDate($d);
                    $date = $date->toSql();

                    // Put in the correct format
                    list($dateOnly, $time) = explode(' ', $date);
                }
                else {
                    $date = '';
                    $time = '';
                }

                $d = array('date' => $date, 'time'
=> $time);
            }
        }
        else
        {
            $d = $data[0];

            if (FabrikWorker::isDate($d)) {
                // Only apply date logic to actual date data, if blank for
example we should leave blank
                $date = Factory::getDate($d);
                $date = $date->toSql();

                // Put in the correct format
                list($dateOnly, $time) = explode(' ', $date);
            }
            else {
                $date = '';
                $time = '';
            }

            $data = array('date' => $date, 'time'
=> $time);
        }

        parent::setValuesFromEncryt($post, $key, $data);

	}

	/**
	 * Get the GMT Date time - tz offset applied in render() if needed
	 *
	 * @param   array $data          Form data date will be GMT if store as
local OFF, otherwise as local time
	 * @param   int   $repeatCounter When repeating joined groups we need to
know what part of the array to access
	 * @param   array $opts          Options
	 *
	 * @return  string    value  Date as GMT time
	 */
	public function getValue($data, $repeatCounter = 0, $opts = array())
	{
		$params       = $this->getParams();
		$alwaysToday  = $params->get('jdate_alwaystoday', false);
		$defaultToday = $params->get('jdate_defaulttotoday', false);
		$formModel    = $this->getFormModel();
		$value        = parent::getValue($data, $repeatCounter, $opts);
		$db           = FabrikWorker::getDbo();

		if (is_array($value))
		{
			// Submission posted as array but date & time in date key. Can be
keyed to 0 if parent class casts string to array.
			$value = FArrayHelper::getValue($value, 'date',
FArrayHelper::getValue($value, 0));
		}

		// in some corner cases, date will be db name quoted, like in CSV export
after an advanced search!
		$value = trim($value??"", "'");
        $value = $value == "" ? null : $value;

		//if ($input->get('task') == 'form.process' ||
($app->isClient('administrator') &&
$input->get('task') == 'process'))
		if (FabrikWorker::inFormProcess())
		{
			// Don't mess with posted value - can cause double offsets -
instead do in _indStoareDBFormat();
			return $value;
		}

		// Element could be a date element (in which case no time stored) - check
for both datetime and date null dates.
		$nullDate      = $db->getNullDate();
		$shortNullDate = explode(' ', $nullDate);
		$shortNullDate = FArrayHelper::getValue($shortNullDate, 0);
		$isNullDate    = $nullDate == $value || $shortNullDate == $value;

		if ($isNullDate) $value = null;
		if ($formModel->isEditable() && ($alwaysToday ||
(($formModel->isNewRecord() || $this->newGroup) &&
$defaultToday))){
			$value = 'now';
		}

		// Don't offset if null date.
		if ($value === null)
		{
			return $value;
		}

		$timeZone = new
\DateTimeZone($this->config->get('offset'));
		$date     = Factory::getDate($value, $timeZone);

		// Querystring value passed into new record
		if ($formModel->isNewRecord() && $value !== '')
		{
			// OK for : Default to current  = no Local time = yes
			if (!$defaultToday && !$formModel->failedValidation())
			{
				$date = new Date($date, $timeZone);
				return $date->format('Y-m-d H:i:s');
			}

			// Ok for : Default to current = yes, Local time = yes OR no
			$date = new Date($date, $timeZone);
			$date->setTimeZone(new \DateTimeZone('UTC'));
			return $date->format('Y-m-d H:i:s');
		}

		$local = $formModel->hasErrors();

		$local = $local === true ? $local :
$params->get('jdate_store_as_local', 0) == 1;
		$local = $local === true ? $local : $value != 'now';
		
//		$local = $formModel->hasErrors() ||
$params->get('jdate_store_as_local', 0) == 1) &&
$value != 'now' ? false : true;
		$value = $date->toSQL($local);

		return $value;
	}

	/**
	 * Does the format string contain time formatting options
	 *
	 * @param   string $format Date format
	 *
	 * @since 2.1.1
	 *
	 * @return  bool
	 */
	protected function formatContainsTime($format)
	{
		$format = FabDate::dateFormatToStrftimeFormat($format);

		$times = array('%H', '%I', '%l',
'%M', '%p', '%P', '%r',
'%R', '%S', '%T', '%X',
'%z', '%Z');

		foreach ($times as $t)
		{
			if (strpos($format, $t))
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * Should the element's data be returned in the search all?
	 *
	 * @param   bool   $advancedMode Is the elements' list is extended
search all mode?
	 * @param   string $search       Search string
	 *
	 * @return  bool    true
	 */
	public function includeInSearchAll($advancedMode = false, $search =
'')
	{
		if (!FabrikWorker::isDate($search))
		{
			return false;
		}

		return parent::includeInSearchAll($advancedMode);
	}

	/**
	 * Builds an array containing the filters value and condition
	 *
	 * @param   string $value     Initial value
	 * @param   string $condition Initial $condition
	 * @param   string $eval      How the value should be handled
	 *
	 * @return  array    (value condition)
	 */
	public function getFilterValue($value, $condition, $eval)
	{
		/* if its a search all value it may not be a date - so use parent method.
		 * see http://fabrikar.com/forums/showthread.php?t=25255
		 */
		if (!is_array($value) && !FabrikWorker::isDate($value))
		{
			if (($this->rangeFilterSet))
			{
				// It's already been set as a range expression - so split that
into an array
				$condition = 'between';
				$value     = explode(' AND ', $value);

				foreach ($value as &$v)
				{
					$v = str_replace(array("'", '"'),
'', $v);
				}

				$filterType = FABRIKFILTER_NOQUOTES;
			}
			else
			{
				$filterType = FabrikWorker::isNullDate($value) ? FABRIKFILTER_TEXT :
$eval;
			}

			return parent::getFilterValue($value, $condition, $filterType);
		}

		$params       = $this->getParams();
		$storeAsLocal = (int) $params->get('jdate_store_as_local',
0);

		$timeZone     = $storeAsLocal ? new
\DateTimeZone($this->config->get('offset')) : null;

		if (!$params->get('jdate_showtime', 0) || $storeAsLocal)
		{
			$this->resetToGMT = false;
		}

		$exactTime =
$this->formatContainsTime($params->get('jdate_table_format'));

		// $$$ rob if filtering in querystring and ranged value set then force
filter type to range

		$filterType = is_array($value) ? 'range' :
$this->getFilterType();

		switch ($filterType)
		{
			case 'range':
				// Ranged dates should be sent in sql format
				break;
			case 'field':
			case 'dropdown':
			case 'auto-complete':
			default:
				// Oddity when filtering from qs
				$value = str_replace("'", '', $value);

				/**
				 *  parse through Date, to allow for special filters such as
'now' 'tomorrow' etc.
				 *  for searches on simply the year - Date will presume its a timestamp
and mung the results
				 *  so we have to use this specific format string to get now and next
				 */
				if (is_numeric($value) && StringHelper::strlen($value) == 4)
				{
					// Will only work on php 5.3.6
					$value = Factory::getDate('first day of January ' .
$value)->toSql();
					$next  = Factory::getDate('first day of January ' . ($value
+ 1));
				}
				elseif ($this->isMonth($value))
				{
					$value = Factory::getDate('first day of ' .
$this->untranslateMonth($value))->toSql();
					$next  = Factory::getDate('last day of ' .
$this->untranslateMonth($value))->setTime(23, 59, 59);
				}
				elseif (trim(StringHelper::strtolower($value)) === 'last
week')
				{
					$value = Factory::getDate('last week')->toSql();
					$next  = Factory::getDate();
				}
				elseif (trim(StringHelper::strtolower($value)) === 'last
month')
				{
					$value = Factory::getDate('last month')->toSql();
					$next  = Factory::getDate();
				}
				elseif (trim(StringHelper::strtolower($value)) === 'last
year')
				{
					$value = Factory::getDate('last year')->toSql();
					$next  = Factory::getDate();
				}
				elseif (trim(StringHelper::strtolower($value)) === 'next
week')
				{
					$value = Factory::getDate()->toSql();
					$next  = Factory::getDate('next week');
				}
				elseif (trim(StringHelper::strtolower($value)) === 'next
month')
				{
					$value = Factory::getDate()->toSql();
					$next  = Factory::getDate('next month');
				}
				elseif (trim(StringHelper::strtolower($value)) === 'next
year')
				{
					$value = Factory::getDate()->toSql();
					$next  = Factory::getDate('next year');
				}
				else
				{
					$value = Factory::getDate($value, $timeZone)->toSql();

					/**
					 *  $$$ hugh - strip time if not needed.  Specific case is element
filter,
					 *  first time submitting filter from list, will have arbitrary
"now" time.
					 *  Dunno if this will break anything else!
					 */
					if (!$exactTime)
					{
						$value = $this->setMySQLTimeToZero($value);
					}

					$next = Factory::getDate(strtotime($this->addDays($value, 1)) - 1,
$timeZone);
					/**
					 *  $$$ now we need to reset $value to GMT.
					 *  Probably need to take $storeAsLocal into account here?
					 */
					if (!$storeAsLocal)
					{
						$this->resetToGMT = true;
						$value            = $this->toMySQLGMT(Factory::getDate($value));
						$this->resetToGMT = false;
					}
					else
					{
						$seconds  = $timeZone->getOffset($next);
						$invert = true;
						if ($seconds < 0)
						{
							$invert = false;
							$seconds  = $seconds * -1;
						}
						// need to use minutes, as DateInterval will barf on fractional hours
like 5.5 (India)
						$dateInterval         = new DateInterval('PT' . $seconds .
'S');
						$dateInterval->invert = $invert;
						$next->sub($dateInterval);
					}
				}

				// Only set to a range if condition is matching (so don't set to
range for < or > conditions)
				if ($condition == 'contains' || $condition == '='
|| $condition == 'REGEXP')
				{
					if (!$params->get('jdate_showtime', 0) || $exactTime ==
false)
					{
						// $$$ rob turn into a ranged filter to search the entire day  values
should be in sql format
						$value     = (array) $value;
						$condition = 'BETWEEN';
						$value[1]  = $next->toSql();

						// Set a flat to stop getRangedFilterValue from adding an additional
day to end value
						$this->rangeFilterSet = true;
					}
				}
				elseif ($condition == 'is null')
				{
					$value = "";
				}

				break;
		}

		$this->resetToGMT = true;
		$value            = parent::getFilterValue($value, $condition, $eval);

		return $value;
	}

	/**
	 * Is a string a month?
	 *
	 * @param   string $test String to test
	 *
	 * @return  bool
	 */
	protected function isMonth($test)
	{
		$months = array(Text::_('JANUARY_SHORT'),
Text::_('JANUARY'), Text::_('FEBRUARY_SHORT'),
Text::_('FEBRUARY'), Text::_('MARCH_SHORT'),
			Text::_('MARCH'), Text::_('APRIL'),
Text::_('APRIL_SHORT'), Text::_('MAY_SHORT'),
Text::_('MAY'), Text::_('JUNE_SHORT'),
			Text::_('JUNE'), Text::_('JULY_SHORT'),
Text::_('JULY'), Text::_('AUGUST_SHORT'),
Text::_('AUGUST'), Text::_('SEPTEMBER_SHORT'),
			Text::_('SEPTEMBER'), Text::_('OCTOBER_SHORT'),
Text::_('OCTOBER'), Text::_('NOVEMBER_SHORT'),
Text::_('NOVEMBER'),
			Text::_('DECEMBER_SHORT'), Text::_('DECEMBER'));

		return in_array($test, $months);
	}

	/**
	 * Get English name for translated month
	 *
	 * @param   string $test Month name
	 *
	 * @return string|boolean
	 */
	protected function untranslateMonth($test)
	{
		switch ($test)
		{
			case Text::_('JANUARY_SHORT'):
			case Text::_('JANUARY'):
				return 'January';
				break;
			case Text::_('FEBRUARY_SHORT'):
			case Text::_('FEBRUARY'):
				return 'February';
				break;
			case Text::_('MARCH_SHORT'):
			case Text::_('MARCH'):
				return 'March';
				break;
			case Text::_('APRIL_SHORT'):
			case Text::_('APRIL'):
				return 'April';
				break;
			case Text::_('MAY_SHORT'):
			case Text::_('MAY'):
				return 'May';
				break;
			case Text::_('JUNE_SHORT'):
			case Text::_('JUNE'):
				return 'June';
				break;
			case Text::_('JULY_SHORT'):
			case Text::_('JULY'):
				return 'July';
				break;
			case Text::_('AUGUST_SHORT'):
			case Text::_('AUGUST'):
				return 'August';
				break;
			case Text::_('SEPTEMBER_SHORT'):
			case Text::_('SEPTEMBER'):
				return 'September';
				break;
			case Text::_('OCTOBER_SHORT'):
			case Text::_('OCTOBER'):
				return 'October';
				break;
			case Text::_('NOVEMBER_SHORT'):
			case Text::_('NOVEMBER'):
				return 'November';
				break;
			case Text::_('DECEMBER_SHORT'):
			case Text::_('DECEMBER'):
				return 'December';
				break;
		}

		return false;
	}

	/**
	 * Get a value to use as an empty filter value
	 *
	 * @return  string
	 */
	public function emptyFilterValue()
	{
		$listModel = $this->getListModel();
		$db        = $listModel->getDb();

		return $db->quote($db->getNullDate());
	}

	/**
	 * Get the list filter for the element
	 * Note: uses FabDate as if date element first to be found in advanced
search, and advanced search run on another
	 * element the list model in getAdvancedSearchElementList() builds the
first filter (this element) with the data
	 * from the first search which was throwing '"500 -
DateTime::__construct() ' errors
	 *
	 * see: http://fabrikar.com/forums/showthread.php?t=28231
	 *
	 * @param   int  $counter Filter order
	 * @param   bool $normal  Do we render as a normal filter or as an
advanced search filter
	 *                        if normal include the hidden fields as well
(default true, use false for advanced filter
	 *                        rendering)
	 *
	 * @return  string    filter html
	 */
	public function getFilter($counter = 0, $normal = true, $container =
'')
	{
		$listModel = $this->getListModel();
		$table     = $listModel->getTable();
		$element   = $this->getElement();
		$origTable = $table->db_table_name;
		$elName    = $this->getFullName(true, false);
		$v         = $this->filterName($counter, $normal);

		// Correct default got
		$default                   = $this->getDefaultFilterVal($normal,
$counter);
		$this->filterDisplayValues = (array) $default;

		// $$$ hugh - in advanced search, _aJoins wasn't getting set
		$joins = $listModel->getJoins();

		foreach ($joins as $aJoin)
		{
			// Not sure why the group id key wasn't found - but put here to
remove error
			if (property_exists($aJoin, 'group_id'))
			{
				if ($aJoin->group_id == $element->group_id &&
$aJoin->element_id == 0)
				{
					$fromTable = $aJoin->table_join;
					$elName    = str_replace($origTable . '.', $fromTable .
'.', $elName);
				}
			}
		}

		$where       = $listModel->buildQueryPrefilterWhere($this);
		$elName      = FabrikString::safeColName($elName);
		$requestName = $elName . '___filter';

		if (array_key_exists($elName, $_REQUEST))
		{
			if (is_array($_REQUEST[$elName]) &&
array_key_exists('value', $_REQUEST[$elName]))
			{
				$_REQUEST[$requestName] = $_REQUEST[$elName]['value'];
			}
		}

		$fType = $this->getFilterType();

		if (in_array($fType, array('dropdown', 'checkbox',
'multiselect')))
		{
			$rows = $this->filterValueList($normal);
			$this->getFilterDisplayValues($default, $rows);
		}

		$return = array();

		switch ($fType)
		{
			case 'checkbox':
				$return[] = $this->checkboxFilter($rows, $default, $v);
				break;
			case 'range':
			case 'range-hidden':
				$return[] = $this->rangeFilter($default, $v);
				break;

			case 'dropdown':
			case 'multiselect':
				$return[] = $this->selectListFilter($rows, $default, $v);
				break;
			default:
			case 'field':
				$return[] = $this->singleFilter($default, $v);
				break;

			case 'hidden':
				$return[] = $this->hiddenFilter($default, $v);
				break;

			case 'auto-complete':
				$return[] = $this->autoCompleteFilter($default, $v);
				break;
		}

		if ($normal)
		{
			$return[] = $this->getFilterHiddenFields($counter, $elName, false,
$normal);
		}
		else
		{
			$return[] = $this->getAdvancedFilterHiddenFields();
		}

		return implode("\n", $return);
	}

	/**
	 * Override for main model getFilterRO to handle search all corner case
	 *
	 * @param   mixed $data String or array of filter value(s)
	 *
	 * @since   3.0.7
	 *
	 * @return  string
	 */
	public function getFilterRO($data)
	{
		/**
		 * This rather fugly chunk of code is needed to handle 'search
all', where the filter data has already
		 * been converted into textual AND format.
		 */
		$v = FArrayHelper::getValue($data, $this->getFullName(true, false),
'');

		if (is_string($v) && strstr($v, ' AND '))
		{
			foreach (explode(' AND ', FArrayHelper::getValue($data,
$this->getFullName(true, false), array())) as $d)
			{
				$return[] = $this->getROElement(trim($d, "'"));
			}

			return Text::_('COM_FABRIK_BETWEEN') . '<br
/>' . implode('<br />' .
Text::_('COM_FABRIK_AND') . "<br />", $return);
		}

		return parent::getFilterRO($data);
	}

	/**
	 * Build the HTML for the auto-complete filter
	 *
	 * @param   string $default    Label
	 * @param   string $v          Field name
	 * @param   string $labelValue Label value
	 * @param   bool   $normal     Do we render as a normal filter or as an
advanced search filter
	 *                             if normal include the hidden fields as well
(default true, use false for advanced
	 *                             filter rendering)
	 *
	 * @return  string LayoutInterface render
	 */
	protected function autoCompleteFilter($default, $v, $labelValue = null,
$normal = true, $container = null)
	{
		$default = htmlspecialchars($default);
		$htmlId  = $this->getHTMLId();

		$layout             =
$this->getLayout('list-filter-autocomplete');
		$displayData        = new stdClass;
		$displayData->class = $this->filterClass();
		$displayData->name  = $v;

		$displayData->default = $default;
		$displayData->htmlId  = $htmlId;

		$autoId = '#' . $htmlId . '-auto-complete';

		if (!$normal)
		{
			$autoId = '.advanced-search-list .autocomplete-trigger';
		}

		FabrikHelperHTML::autoComplete($autoId, $this->getElement()->id,
$this->getFormModel()->getId(), 'date');

		return $layout->render($displayData);
	}

	/**
	 * @param $default
	 * @param $v
	 *
	 * @return  string  filter
	 */
	protected function hiddenFilter($default, $v)
	{
		if (is_array($default))
		{
			$default = array_shift($default);
		}

		$default = htmlspecialchars($default);
		$class   = $this->filterClass();

		// Don't add id as caused issues with inline edit plugin and
clashing ids.
		return '<input type="hidden" name="' . $v .
'" class="' . $class . '" value="'
. $default . '" />';
	}

	/**
	 * @param $rows
	 * @param $default
	 * @param $v
	 *
	 * @return string filter
	 */
	protected function selectListFilter($rows, $default, $v)
	{
		$element   = $this->getElement();
		$listModel = $this->getListModel();
		$fabrikDb  = $listModel->getDb();
		$timeZone  = new
\DateTimeZone($this->config->get('offset'));
		$params    = $this->getParams();
		$format    = $params->get('jdate_table_format',
'Y-m-d');
		$storeAsLocal = $params->get('jdate_store_as_local',
'0') == '1';

		/**
		 *  cant do the format in the MySQL query as its not the same formatting
		 *  e.g. M in MySQL is month and J's date code its minute
		 */
		$max   = count($rows) < 7 ? count($rows) : 7;
		$fType = $this->getFilterType();
		$size  = $element->filter_type === 'multiselect' ?
'multiple size="' . $max . '"' :
'size="1"';
		$v     = $fType === 'multiselect' ? $v . '[]' : $v;

		$class = [];

		jimport('joomla.utilities.date');
		$ddData = array();

		foreach ($rows as $k => $o)
		{
			if ($fabrikDb->getNullDate() === $o->text)
			{
				$o->text  = '';
				$o->value = '';
			}
			else
			{
				$d = new FabDate($o->text);
				if (!$storeAsLocal)
				{
					$d->setTimeZone($timeZone);
					$o->value = $d->toSql(true);
					$o->text  = $d->format($format, true);
				}
				else
				{
					$o->value = $d->toSql(false);
					$o->text  = $d->format($format, false);

				}
			}

			if (!array_key_exists($o->value, $ddData))
			{
				$ddData[$o->value] = $o;
			}
		}

		array_unshift($ddData, HTMLHelper::_('select.option',
'', $this->filterSelectLabel()));
		$layout               =
$this->getLayout('list-filter-dropdown');
		$displayData          = new stdClass;
		$displayData->rows    = $ddData;
		$displayData->name    = $v;
		$displayData->class   = "form-select "
.$this->filterClass();
		$displayData->size    = $size;
		$displayData->default = $default;
		$displayData->htmlId  = $this->getHTMLId();

		return $layout->render($displayData);
	}

	/**
	 * Create a input type field filter
	 *
	 * @param $default
	 * @param $v
	 *
	 * @return string  filter
	 */
	protected function singleFilter($default, $v, $type = 'text')
	{
		$params = $this->getParams();
		$format = $params->get('jdate_table_format',
'Y-m-d');
		$rawValue = $default;

		if (is_array($default))
		{
			$default = array_shift($default);
		}

		if ($default !== '')
		{
			$d       = new FabDate($default);
			$default = $d->format($format);
		}

		$layout          = $this->getLayout('list-filter-field');
		$displayData     = new stdClass;
		$from            = new stdClass;
		$from->id        = $this->getFilterHtmlId(0);
		$from->value     = $default;
		$from->name      = $v;
		$from->rawValue  = $rawValue;

		$imageOpts = array('alt' => 'calendar');
		$from->img = FabrikHelperHTML::image('calendar',
'form', @$this->tmpl, $imageOpts);

		$displayData->from = $from;

		$displayData->format  = $format;
		$displayData->calOpts = $this->filterCalendarOpts();

		$displayData->jCal = $this->calendar($rawValue, $v,
$this->getFilterHtmlId(0), $format, $this->filterCalendarOpts(), 0);

		return $layout->render($displayData);
	}

	/**
	 * @param $default
	 * @param $v
	 *
	 * @return string filter
	 */
	protected function rangeFilter($default, $v)
	{
		$params = $this->getParams();
		$format = $params->get('jdate_table_format',
'Y-m-d');

		if (empty($default))
		{
			$default = array('', '');
			$rawValues = array('', '');
		}
		else
		{
			$rawValues = array(
				$default[0],
				$default[1]
			);
			$d          = new FabDate($default[0]);
			$default[0] = $d->format($format);
			$d          = new FabDate($default[1]);
			$default[1] = $d->format($format);
		}

		// Add wrapper div for list filter toggling
		$return[] = '<div
class="fabrik_filter_container">';

		$layout              =
$this->getLayout('list-filter-range');
		$displayData         = new stdClass;
		$displayData->htmlId = $this->getHTMLId();
		$displayData->class  = $this->filterClass();
		$from                = new stdClass;
		$from->id            = $this->getFilterHtmlId(0);
		$from->value         = $default[0];
		$from->name          = $v . '[0]';

		$imageOpts = array('alt' => 'calendar');
		$from->img = FabrikHelperHTML::image('calendar',
'form', @$this->tmpl, $imageOpts);

		$displayData->from = $from;
		$displayData->jCalFrom = $this->calendar($rawValues[0],
$v.'[0]', $this->getFilterHtmlId(0), $format,
$this->filterCalendarOpts(), 0);

		$displayData->format  = $format;
		$displayData->calOpts = $this->filterCalendarOpts();

		$to        = new stdClass;
		$to->id    = $this->getFilterHtmlId(1);
		$to->value = $default[1];
		$to->name  = $v . '[1]';

		$imageOpts = array('alt' => 'calendar');
		$to->img   = FabrikHelperHTML::image('calendar',
'form', @$this->tmpl, $imageOpts);

		$displayData->to         = $to;
		$displayData->filterType = $this->getFilterType();
		$displayData->jCalTo = $this->calendar($rawValues[1],
$v.'[1]', $this->getFilterHtmlId(1), $format,
$this->filterCalendarOpts(), 0);

		return $layout->render($displayData);
	}

	/**
	 * Get    filter HTML id
	 *
	 * @param   int $range Which ranged filter we are getting
	 *
	 * @return  string  html filter id
	 */
	protected function getFilterHtmlId($range)
	{
		$input   = $this->app->input;
		$counter = $input->get('counter', 0);
		$listRef = $this->getListModel()->getRenderContext();

		return $this->getHTMLId() . '_' . $listRef .
'_filter_range_' . $range . '_' .
$input->get('task') . '.' . $counter;
	}

	/**
	 * Cache method to populate auto-complete options
	 *
	 * @param   plgFabrik_Element $elementModel Element model
	 * @param   string            $search       Search string
	 * @param   array             $opts         Options, 'label'
=> field to use for label (db join)
	 *
	 * @since   3.0.7
	 *
	 * @return string  json encoded search results
	 */
	public static function cacheAutoCompleteOptions($elementModel, $search,
$opts = array())
	{
		$listModel = $elementModel->getListModel();
		$table     = $listModel->getTable();
		$db        = $listModel->getDb();
		$name      = $elementModel->getFullName(false, false);
		$query     = $db->getQuery(true);
		$params    = $elementModel->getParams();
		$format    = $params->get('jdate_table_format');
		$elementModel->strftimeTFormatToMySQL($format);
		$search = $db->quote('%' . addslashes($search) .
'%');
		$query->select('DISTINCT(' . $name . ') AS value,
' . $name . ' AS text')->from($table->db_table_name)
			->where($name . ' LIKE ' . $search . ' OR
DATE_FORMAT(' . $name . ', "' . $format . '"
) LIKE ' . $search);
		$db->setQuery($query);
		$tmp    = $db->loadObjectList();
		$ddData = array();

		foreach ($tmp as &$t)
		{
			$elementModel->toLabel($t->text);

			if (!array_key_exists($t->text, $ddData))
			{
				$ddData[$t->text] = $t;
			}
		}

		$ddData = array_values($ddData);
		echo json_encode($ddData);
	}

	/**
	 * When importing csv data you can run this function on all the data to
	 * format it into the format that the form would have submitted the date
	 *
	 * @param   array  &$data To prepare
	 * @param   string $key   List column heading
	 * @param   bool   $isRaw Data is raw
	 *
	 * @return  array  data
	 */
	public function prepareCSVData(&$data, $key, $isRaw = false)
	{
		if ($isRaw)
		{
			return;
		}

		$params = $this->getParams();
		$csvFormat = $params->get('jdate_csv_offset_tz',
'0');

		// 0 is "format format", 1 is "list format"
		if ($csvFormat === '0' || $csvFormat === '3')
		{
			$paramName = $csvFormat === '0' ?
'jdate_form_format' : 'jdate_table_format';
			$format = $params->get($paramName, 'Y-m-d H:i:s');

			// Go through data and convert specified format to MySQL
			for ($j = 0; $j < count($data); $j++)
			{
				$orig_data = $data[$j][$key];

				if (empty($orig_data))
				{
					$data[$j][$key] = '0000-00-00 00:00:00';
					continue;
				}

				try
				{
					$date           = Date::createFromFormat($format, $orig_data);
					$exactTime = $this->formatContainsTime($format);

					if ($date !== false)
					{
						if (!$params->get('jdate_showtime', 0) || $exactTime ==
false)
						{
							$date->setTime(0, 0, 0);
						}

						$data[$j][$key] = $date->format('Y-m-d H:i:s');
					}
				}
				catch (Exception $e)
				{
					// Suppress date time format error
				}
			}
		}
	}

	/**
	 * Does the element consider the data to be empty
	 * Used in isempty validation rule
	 *
	 * @param   array $data          Data to test against
	 * @param   int   $repeatCounter Repeat group #
	 *
	 * @return  bool
	 */
	public function dataConsideredEmpty($data, $repeatCounter)
	{
		return ($data == '') ? true : false;
	}

	/**
	 * This builds an array containing the filters value and condition
	 * when using a ranged search
	 *
	 * @param   array  $value     Initial value
	 * @param   string $condition Filter condition e.g BETWEEN
	 *
	 * @return  array  (value condition)
	 */
	protected function getRangedFilterValue($value, $condition = '')
	{
		$db     = FabrikWorker::getDbo();
		$params = $this->getParams();
		/* $$$ hugh - need to convert dates to MySQL format for the query
		 * $$$ hugh - not any more, since we changed to always submit in MySQL
format
		 * $$$ hugh - removing the MySQL conversion has broken
'special' range handling,
		 * which used to happen in the MySQL conversion function.  So ...
		 * Created new helper function specialStrToMySQL() which turns things
		 * like 'midnight yesterday' etc. into MySQL dates, defaulting
to GMT.
		 * This lets us do ranged query string and content plugin filters like
...
		 *
table___date[value][]=midnight%20yesterday&table___date[value][]=midnight%20today&table___date[condition]=BETWEEN
		 */
		$value[0] =
FabrikWorker::specialStrToMySQL(FArrayHelper::getValue($value, 0));
		$value[1] =
FabrikWorker::specialStrToMySQL(FArrayHelper::getValue($value, 1));

		// $$$ hugh - if the first date is later than the second, swap 'em
round  to keep 'BETWEEN' in the query happy
		if (strtotime($value[0]) > strtotime($value[1]))
		{
			$tmp_value = $value[0];
			$value[0]  = $value[1];
			$value[1]  = $tmp_value;
		}

		$exactTime =
$this->formatContainsTime($params->get('jdate_table_format'));

		if (!$params->get('jdate_showtime', 0) || $exactTime ==
false)
		{
			// Range values could already have been set in getFilterValue
			if (!$this->rangeFilterSet)
			{
				/* $$$ due to some changes in how we handle ranges, the following was
no longer getting
				 * applied in getFilterValue, needed because on first submit of a
filter an arbitrary time
				 * is being set (i.e. time "now").
				 */
				$value[0] = $this->setMySQLTimeToZero($value[0]);
				$value[1] = $this->setMySQLTimeToZero($value[1]);

				/* $$$ hugh - need to back this out by one second, otherwise we're
including next day.
				 * So ... say we are searching from '2009-07-17' to
'2009-07-21', the
				 * addDays(1) changes '2009-07-21 00:00:00' to
'2009-07-22 00:00:00',
				 * but what we really want is '2009-07-21 23:59:59'
				 */
				$value[1] = date("Y-m-d H:i:s",
strtotime($this->addDays($value[1], 1)) - 1);
			}
		}

		// $$$ rob 20/07/2012 Date is posted as local time, need to set it back
to GMT. Seems needed even if dates are saved without timeselector
		// $$$ hugh - think we may need to take 'store as local' in to
account here?
		$localTimeZone = new
\DateTimeZone($this->config->get('offset'));

		$params       = $this->getParams();
		$storeAsLocal = $params->get('jdate_store_as_local',
'0') == '1';

		$date     = Factory::getDate($value[0], $localTimeZone);
		$value[0] = $date->toSql($storeAsLocal);

		$date = Factory::getDate($value[1], $localTimeZone);
		/* $$$ hugh - why are we setting the 'local' arg on toSql() for
end date but not the start date of the range?
		 * This ends up with queries like "BETWEEN '2012-01-26
06:00:00' AND '2012-01-26 23:59:59'"
		 * with CST (GMT -6), which chops out 6 hours of the day range.
		 * Also, see comment above about maybe needing to take "save as
local" in to account on this.
		 */

		// $value[1] = $date->toSql(true);
		$value[1] = $date->toSql($storeAsLocal);

		$value     = $db->quote($value[0]) . ' AND ' .
$db->quote($value[1]);
		$condition = 'BETWEEN';

		return array($value, $condition);
	}

	/**
	 * simple minded method to set a MySQL formatted date's time to
00:00:00
	 *
	 * @param   string $date In MySQL format
	 *
	 * @return  string    MySQL formatted date with time set to 0
	 */
	protected function setMySQLTimeToZero($date)
	{
		$date_array    = explode(' ', $date);
		$date_array[1] = '00:00:00';

		return implode(' ', $date_array);
	}

	/**
	 * Add days to a date
	 *
	 * @param   mixed   $date The initial time for the FabDate object
	 * @param   integer $add  Number of days to add (negative to remove days)
	 *
	 * @return  string    MySQL formatted date
	 */
	protected function addDays($date, $add = 0)
	{
		$params          = $this->getParams();
		$storeAsLocal    = $params->get('jdate_store_as_local', 0)
== 1;
		$date            = Factory::getDate($date);
		/*
		$PHPDate         = getdate($date->toUnix());
		$PHPDate['mday'] = $PHPDate['mday'] + $add;
		$v               = mktime($PHPDate['hours'],
$PHPDate['minutes'], $PHPDate['seconds'],
$PHPDate['mon'], $PHPDate['mday'],
$PHPDate['year']);
		$date            = Factory::getDate($v);
		*/
		$hours = 'PT' . abs($add) * 24 . 'H';

		if ($add < 0)
		{
			$date->sub(new DateInterval($hours));
		}
		else
		{
			$date->add(new DateInterval($hours));
		}

		return $date->toSql($storeAsLocal);
	}

	/**
	 * Build the query for the avg calculation
	 *
	 * @param   model &$listModel list model
	 * @param   array $labels     Labels
	 *
	 * @return  string    sql statement
	 */
	protected function getAvgQuery(&$listModel, $labels = array())
	{
		$label    = count($labels) == 0 ? "'calc' AS label" :
'CONCAT(' . implode(', " & " , ',
$labels) . ')  AS label';
		$table    = $listModel->getTable();
		$db       = $listModel->getDb();
		$joinSQL  = $listModel->buildQueryJoin();
		$whereSQL = $listModel->buildQueryWhere();
		$name     = $this->getFullName(false, false);

		return 'SELECT FROM_UNIXTIME(AVG(UNIX_TIMESTAMP(' . $name .
'))) AS value, ' . $label . ' FROM '
		. $db->qn($table->db_table_name) . ' ' . $joinSQL .
' ' . $whereSQL;
	}

	/**
	 * Get sum query
	 *
	 * @param   object &$listModel List model
	 * @param   array  $labels     Label
	 *
	 * @return string
	 */
	protected function getSumQuery(&$listModel, $labels = array())
	{
		$label    = count($labels) == 0 ? "'calc' AS label" :
'CONCAT(' . implode(', " & " , ',
$labels) . ')  AS label';
		$table    = $listModel->getTable();
		$db       = $listModel->getDb();
		$joinSQL  = $listModel->buildQueryJoin();
		$whereSQL = $listModel->buildQueryWhere();
		$name     = $this->getFullName(false, false);

		// $$$rob not actually likely to work due to the query easily exceeding
MySQL's TIMESTAMP_MAX_VALUE value but the query in itself is correct
		return 'SELECT FROM_UNIXTIME(SUM(UNIX_TIMESTAMP(' . $name .
'))) AS value, ' . $label . ' FROM '
		. $db->qn($table->db_table_name) . ' ' . $joinSQL .
' ' . $whereSQL;
	}

	/**
	 * find an average from a set of data
	 * can be overwritten in plugin - see date for example of averaging dates
	 *
	 * @param   array $data To average
	 *
	 * @return  string  average result
	 */
	public function simpleAvg($data)
	{
		$avg = $this->simpleSum($data) / count($data);

		return Factory::getDate($avg)->toSql();
	}

	/**
	 * find the sum from a set of data
	 * can be overwritten in plugin - see date for example of averaging dates
	 *
	 * @param   array $data To sum
	 *
	 * @return  string  sum result
	 */
	public function simpleSum($data)
	{
		$sum = 0;

		foreach ($data as $d)
		{
			$date = Factory::getDate($d);
			$sum += $date->toUnix();
		}

		return $sum;
	}

	/**
	 * Takes date's time value and turns it into seconds. Used in timer
	 *
	 * @param   string $date Object $date
	 *
	 * @return  int        seconds
	 */
	protected function toSeconds($date)
	{
		return (int) ($date->format('H') * 60 * 60) + (int)
($date->format('i') * 60) + (int)
$date->format('s');
	}

	/**
	 * Takes strftime time formatting -
http://fr.php.net/manual/en/function.strftime.php
	 * and converts to format used in MySQL DATE_FORMAT
	 * http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html
	 *
	 * @param   string &$format PHP date format string => MySQL string
format
	 *
	 * @return  void
	 */
	protected function strftimeTFormatToMySQL(&$format)
	{
		/**
		 * $$$ hugh - can't do direct %x to %y, because str_replace's
left to right processing,
		 * so (for instance) %B translates to %M, which then gets translated
again to %i
		 * So ... do %x to ^@y (hopefully nobody will ever use ^@ in their format
string!),
		 * then replace all ^@'s with %'s.
		 */
		$search = array('%e', '%j', '%u',
'%V', '%W', '%h', '%B',
'%C', '%g', '%G', '%M',
'%P', '%r', '%R', '%T',
'%X', '%z', '%Z', '%D',
'%F', '%s',
			'%x', '%A');

		$replace = array('^@e', '^@j', '^@w',
'^@U', '^@U', '^@b', '^@M',
'', '^@y', '^@Y', '^@i',
'^@p', '^@I:^@i:^@S ^@p', '^@H:^@i',
			'^@H:^@i:^@S', '', '',
'^@H:^@i:^@S', '^@m/^@c/^@y', '^@Y-^@m-^@c',
'', '^@Y-^@m-^@c', '^@W');

		$format = str_replace($search, $replace, $format);
		$format = str_replace('^@', '%', $format);
	}

	/**
	 * Build the filter query for the given element.
	 * Can be overwritten in plugin - e.g. see checkbox element which checks
for partial matches
	 *
	 * @param   string $key           Element name in format
`tablename`.`elementname`
	 * @param   string $condition     =/like etc
	 * @param   string $value         Search string - already quoted if
specified in filter array options
	 * @param   string $originalValue Original filter value without quotes or
%'s applied
	 * @param   string $type          Filter type
advanced/normal/prefilter/search/querystring/searchall
	 * @param   string  $evalFilter     evaled
	 *                                  
	 * @return  string    sql query part e,g, "key = value"
	 */
	public function getFilterQuery($key, $condition, $value, $originalValue,
$type = 'normal', $evalFilter = '0')
	{
		$this->encryptFieldName($key);

		switch ($condition)
		{
			case 'thisyear':
				$query = ' YEAR(' . $this->addConvert($key) . ') =
YEAR(NOW()) ';
				break;
			case 'earlierthisyear':
				$query = ' (DAYOFYEAR(' . $this->addConvert($key) .
') <= DAYOFYEAR(NOW()) AND YEAR(' . $this->addConvert($key)
. ') = YEAR(NOW())) ';
				break;
			case 'laterthisyear':
				$query = ' (DAYOFYEAR(' . $this->addConvert($key) .
') >= DAYOFYEAR(NOW()) AND YEAR(' . $this->addConvert($key)
. ') = YEAR(NOW())) ';
				break;
			case 'today':
				$query = ' (' . $this->addConvert($key) . ' >=
CURDATE() AND ' . $this->addConvert($key) . ' < CURDATE() +
INTERVAL 1 DAY) ';
				break;
			case 'yesterday':
				$query = ' (' . $this->addConvert($key) . ' >=
CURDATE() - INTERVAL 1 DAY AND ' . $this->addConvert($key) . '
< CURDATE()) ';
				break;
			case 'tomorrow':
				$query = ' (' . $this->addConvert($key) . ' >=
CURDATE() + INTERVAL 1 DAY  AND ' . $this->addConvert($key) .
' < CURDATE() + INTERVAL 2 DAY ) ';
				break;
			case 'thismonth':
				$query = ' (' . $this->addConvert($key) . ' >=
DATE_ADD(LAST_DAY(DATE_SUB(now(), INTERVAL 1 MONTH)), INTERVAL 1 DAY)  AND
' . $this->addConvert($key)
					. ' <= LAST_DAY(NOW()) ) ';
				break;
			case 'lastmonth':
				$query = ' (' . $this->addConvert($key) . ' >=
DATE_ADD(LAST_DAY(DATE_SUB(now(), INTERVAL 2 MONTH)), INTERVAL 1 DAY)  AND
' . $this->addConvert($key)
					. ' <= LAST_DAY(DATE_SUB(NOW(), INTERVAL 1 MONTH)) ) ';
				break;
			case 'nextmonth':
				$query = ' (' . $this->addConvert($key) . ' >=
DATE_ADD(LAST_DAY(now()), INTERVAL 1 DAY)  AND ' .
$this->addConvert($key)
					. ' <= DATE_ADD(LAST_DAY(NOW()), INTERVAL 1 MONTH) ) ';
				break;
			case 'nextweek1':
				$query = ' (YEARWEEK(' . $this->addConvert($key) .
',1) = YEARWEEK(DATE_ADD(NOW(), INTERVAL 1 WEEK), 1))';
				break;
			case 'birthday':
				$query = '(MONTH(' . $this->addConvert($key) . ') =
MONTH(CURDATE()) AND  DAY(' . $this->addConvert($key) . ') =
DAY(CURDATE())) ';
				break;

			default:
				$params = $this->getParams();
				$format = $params->get('jdate_table_format');

				if ($format == '%a' || $format == '%A')
				{
					/**
					 * special cases where we want to search on a given day of the week
					 * note it wont work with ranged searches
					 */
					$this->strftimeTFormatToMySQL($format);
					$key = "DATE_FORMAT( $key , '$format')";
				}
				elseif ($format == '%Y %B')
				{
					/* $$$ hugh - testing horrible hack for different languages, initially
for andorapro's site
					 * Problem is, he has multiple language versions of the site, and
needs to filter tables
					 * by "%Y %B" dropdown (i.e. "2010 November") in
multiple languages.
					 * FabDate automagically uses the selected language when we render the
date
					 * but when we get to this point, month names are still localized,
i.e. in French or German
					 * which MySQL won't grok (until 5.1.12)
					 * So we need to translate them back again, *sigh*
					 * FIXME - need to make all this more generic, so we can handle any
date format which uses
					 * month or day names.
					 */
					$matches = array();

					if (preg_match('#\d\d\d\d\s+(\S+)\b#', $value, $matches))
					{
						$this_month = $matches[1];
						$en_month   = $this->_monthToEnglish($this_month);
						$value      = str_replace($this_month, $en_month, $value);
						$this->strftimeTFormatToMySQL($format);
						$key = "DATE_FORMAT( $key , '$format')";
					}
				}

				if ($type == 'querystring' &&
StringHelper::strtolower($value) == 'now')
				{
					$value = 'NOW()';
				}

				$query = " $key $condition $value ";
				break;
		}

		return $query;
	}

	/**
	 * Called when copy row list plugin called
	 *
	 * @param   mixed $val Value to copy into new record
	 *
	 * @return mixed value to copy into new record
	 */
	public function onCopyRow($val)
	{
		if ($this->defaultOnCopy())
		{
			$val = $this->getFrontDefaultValue();
		}

		if (!FabrikWorker::isDate($val))
		{
			return $val;
		}

		$params = $this->getParams();

		if ($params->get('jdate_showtime', 0))
		{
			$storeAsLocal = (int) $params->get('jdate_store_as_local',
0);

			if (!$storeAsLocal)
			{
				$date     = Factory::getDate($val);
				$timeZone = new
\DateTimeZone($this->config->get('offset'));
				$date->setTimeZone($timeZone);
				$val = $date->toSql(true);
			}
		}

		return $val;
	}

	/**
	 * Called when save as copy form button clicked
	 *
	 * @param   mixed $val value to copy into new record
	 *
	 * @return  mixed  value to copy into new record
	 */
	public function onSaveAsCopy($val)
	{
		return $this->onCopyRow($val);
	}

	/**
	 * Used by validations
	 *
	 * @param   string $data    This elements data
	 * @param   string $cond    What condition to apply
	 * @param   string $compare Data to compare element's data to (if
date already set to Y-m-d H:I:S so no need to
	 *                          apply storeDatabaseForm() on it
	 *
	 * @return bool
	 */
	public function greaterOrLessThan($data, $cond, $compare)
	{
		$data = $this->storeDatabaseFormat($data, null);

		/**
		 * $$$ rob 30/06/2011 the line below was commented out - but if doing
date compare on 2 fields
		 * formatting %d/%m/%Y then the compare unix time was not right
		 */
		$compare = $this->storeDatabaseFormat($compare, null);
		$data    = Factory::getDate($data)->toUnix();
		$compare = Factory::getDate($compare)->toUnix();

		return parent::greaterOrLessThan($data, $cond, $compare);
	}

	/**
	 * Part of horrible hack for translating non-English words back
	 * to something MySQL will understand.
	 *
	 * @param   string $month Original month name
	 * @param   bool   $abbr  Is the month abbreviated
	 *
	 * @return  string  English month name
	 */
	private function _monthToEnglish($month, $abbr = false)
	{
		if ($abbr)
		{
			if (StringHelper::strcmp($month, Text::_('JANUARY_SHORT')) ===
0)
			{
				return 'Jan';
			}

			if (StringHelper::strcmp($month, Text::_('FEBRUARY_SHORT'))
=== 0)
			{
				return 'Feb';
			}

			if (StringHelper::strcmp($month, Text::_('MARCH_SHORT')) ===
0)
			{
				return 'Mar';
			}

			if (StringHelper::strcmp($month, Text::_('APRIL_SHORT')) ===
0)
			{
				return 'Apr';
			}

			if (StringHelper::strcmp($month, Text::_('MAY_SHORT')) === 0)
			{
				return 'May';
			}

			if (StringHelper::strcmp($month, Text::_('JUNE_SHORT')) === 0)
			{
				return 'Jun';
			}

			if (StringHelper::strcmp($month, Text::_('JULY_SHORT')) === 0)
			{
				return 'Jul';
			}

			if (StringHelper::strcmp($month, Text::_('AUGUST_SHORT')) ===
0)
			{
				return 'Aug';
			}

			if (StringHelper::strcmp($month, Text::_('SEPTEMBER_SHORT'))
=== 0)
			{
				return 'Sep';
			}

			if (StringHelper::strcmp($month, Text::_('OCTOBER_SHORT')) ===
0)
			{
				return 'Oct';
			}

			if (StringHelper::strcmp($month, Text::_('NOVEMBER_SHORT'))
=== 0)
			{
				return 'Nov';
			}

			if (StringHelper::strcmp($month, Text::_('DECEMBER_SHORT'))
=== 0)
			{
				return 'Dec';
			}
		}
		else
		{
			if (StringHelper::strcmp($month, Text::_('JANUARY')) === 0)
			{
				return 'January';
			}

			if (StringHelper::strcmp($month, Text::_('FEBRUARY')) === 0)
			{
				return 'February';
			}

			if (StringHelper::strcmp($month, Text::_('MARCH')) === 0)
			{
				return 'March';
			}

			if (StringHelper::strcmp($month, Text::_('APRIL')) === 0)
			{
				return 'April';
			}

			if (StringHelper::strcmp($month, Text::_('MAY')) === 0)
			{
				return 'May';
			}

			if (StringHelper::strcmp($month, Text::_('JUNE')) === 0)
			{
				return 'June';
			}

			if (StringHelper::strcmp($month, Text::_('JULY')) === 0)
			{
				return 'July';
			}

			if (StringHelper::strcmp($month, Text::_('AUGUST')) === 0)
			{
				return 'August';
			}

			if (StringHelper::strcmp($month, Text::_('SEPTEMBER')) === 0)
			{
				return 'September';
			}

			if (StringHelper::strcmp($month, Text::_('OCTOBER')) === 0)
			{
				return 'October';
			}

			if (StringHelper::strcmp($month, Text::_('NOVEMBER')) === 0)
			{
				return 'November';
			}

			if (StringHelper::strcmp($month, Text::_('DECEMBER')) === 0)
			{
				return 'December';
			}
		}

		return $month;
	}

	/**
	 * load a new set of default properties and params for the element
	 *
	 * @param   array $properties Default props
	 *
	 * @return  FabrikTableElement    element (id = 0)
	 */
	public function getDefaultProperties($properties = array())
	{
		$item         = parent::getDefaultProperties();
		$item->hidden = 1;

		return $item;
	}

	/**
	 * convert XML format data into fabrik data (used by web services)
	 *
	 * @param   mixed $v Data
	 *
	 * @return  mixed  data
	 */
	public function fromXMLFormat($v)
	{
		return Factory::getDate($v)->toSql();
	}

	/**
	 * If used as a filter add in some JS code to watch observed filter
element's changes
	 * when it changes update the contents of this elements dd filter's
options
	 *
	 * @param   bool   $normal    Is the filter a normal (true) or advanced
filter
	 * @param   string $container Container
	 *
	 * @return  void
	 */
	public function filterJS($normal, $container)
	{
		$element = $this->getElement();
		$type    = $this->getFilterType();

		if ($normal && ($type !== 'field' && $type !==
'range'))
		{
			return;
		}

		$params                        = $this->getParams();
		$id                            = $this->getFilterHtmlId(0);
		$id2                           = $this->getFilterHtmlId(1);
		$opts                          = new stdClass;
		$opts->calendarSetup           = $this->_CalendarJSOpts($id);
		$opts->calendarSetup->ifFormat =
$params->get('jdate_table_format', 'Y-m-d');
		$opts->calendarSetup->ifFormat =
FabDate::dateFormatToStrftimeFormat($opts->calendarSetup->ifFormat);
		$opts->type    = $type;
		$opts->ids     = $type == 'field' ? array($id) : array($id,
$id2);
		$opts->buttons = $type == 'field' ? array($id .
'_btn') : array($id . '_btn', $id2 . '_btn');
		$opts          = json_encode($opts);
		$script        = 'Fabrik.filter_' . $container .
'.addFilter(\'' . $element->plugin . '\', new
JDateFilter(' . $opts . '));' . "\n";

		if ($normal)
		{
			FabrikHelperHTML::script('plugins/fabrik_element/jdate/filter.js');

			return $script;
		}
		else
		{
			FabrikHelperHTML::script('plugins/fabrik_element/jdate/filter.js',
$script);
		}
	}

	/**
	 * Get calendar filter widget options
	 *
	 * @return array  options
	 */
	protected function filterCalendarOpts()
	{
		$params  = $this->getParams();
		$class   = $this->filterClass();
		$calOpts = array('class' => $class, 'maxlength'
=> '19', 'size' => 16);

		if ($params->get('jdate_allow_typing_in_field', true) ==
false)
		{
			//$calOpts['readonly'] = 'readonly';
		}

		return $calOpts;
	}

	/**
	 * Get the class to manage the form element
	 * to ensure that the file is loaded only once
	 *
	 * @param   array  &$srcs  Scripts previously loaded
	 * @param   string $script Script to load once class has loaded
	 * @param   array  &$shim  Dependant class names to load before
loading the class - put in requirejs.config shim
	 *
	 * @return void
	 */
	public function formJavascriptClass(&$srcs, $script = '',
&$shim = array())
	{
		$key = FabrikHelperHTML::isDebug() ? 'element/jdate/jdate' :
'element/jdate/jdate-min';
		// Ensure that we keep advanced dependencies from previous date elements
regardless of current elements settings.
		$deps   = array_key_exists($key, $shim) ? $shim[$key]->deps : array();
		$params = $this->getParams();

		$globalShim = new stdClass();
		if (empty($deps))
		{
			/**
			 * Main datejs files (core, parser) require the globalization to be
loaded first,
			 * to add the Date.CultureInfo object, so we have to shim it thusly
			 */
			$s          = new stdClass;
			$s->deps    = [];
			$globalShim->deps = ['lib/datejs/globalization/' .
Factory::getApplication()->getLanguage()->getTag()];
			$s->deps[] = 'lib/datejs/globalization/' .
Factory::getApplication()->getLanguage()->getTag();
			$s->deps[] = 'lib/datejs/core';
			$shim['lib/datejs/core'] = $globalShim;
			$s->deps[] = 'lib/datejs/parser';
			$shim['lib/datejs/parser'] = $globalShim;
			$s->deps[] = 'lib/datejs/extras';
			$shim['lib/datejs/extras'] = $globalShim;
			$shim[$key] = $s;
		}

		/* If the jdate element is loaded on an ajax page, we need to manually
include the j! calendar  & javascript files */
		if (FabrikHelperHTML::inAjaxLoadedPage()) {
			$min = FabrikHelperHTML::isDebug() ? '.min' : '';
			$jsloc = Juri::root() .'media/system/js/fields/';
			$cssloc = Juri::root() .'media/system/css/fields/';
			echo "<script
src='".$jsloc."calendar-locales/date/gregorian/date-helper".$min.".js'
type='text/javascript'></script>";
			echo "<script
src='".$jsloc."calendar".$min.".js'
type='text/javascript'></script>";
			echo "<link
href='".$cssloc."calendar".$min.".css'
rel='stylesheet'>";
			/* We also need to add them to the shim so they are loaded before the
calendar is initialized */
			$shim[$key]->deps[] = Juri::root()
.'media/system/js/fields/calendar.min.js';
			$shim[Juri::root() .'media/system/js/fields/calendar.min.js']
= $globalShim;
			$shim[$key]->deps[] = Juri::root()
.'media/system/js/fields/calendar-locales/date/gregorian/date-helper.min.js';
			$shim[Juri::root()
.'media/system/js/fields/calendar-locales/date/gregorian/date-helper.min.js']
= $globalShim;
		}

		parent::formJavascriptClass($srcs, $script, $shim);

		// Return false, as we need to be called on per-element (not per-plugin)
basis
		return false;
	}

	/**
	 * Get the Front end JS default date
	 *
	 * @param   array $data Form data
	 *
	 * @return string
	 */
	public function getFrontDefaultValue($data = array())
	{
		$params       = $this->getParams();
		$db           =
Factory::getContainer()->get('DatabaseDriver');
		$alwaysToday  = $params->get('jdate_alwaystoday', false);
		$defaultToday = $params->get('jdate_defaulttotoday', false);

		if ($alwaysToday || $defaultToday)
		{
			$this->default = HTMLHelper::_('date', 'now',
$db->getDateFormat());
		}
		else
		{
			$this->default = parent::getDefaultValue($data);
		}

		return $this->default;
	}

	/*
	 * If date is stored as UTC, wrap the necessary CONVERT_TZ() around the
key name to offset it, so 'foo'
	 * becomes 'CONVERT_TZ(foo, "+0:00",
"+5:00")'.  If storing as local, leave key intact.  Used
when building things
	 * like pre-filter queries for "today".
	 */
	protected function addConvert($key)
	{
		$params       = $this->getParams();
		$storeAsLocal = (int) $params->get('jdate_store_as_local',
0);

		if ($params->get('jdate_store_as_local', '0') !==
'1')
		{
			$timeZone = new
\DateTimeZone($this->config->get('offset'));
			$zoneDate = new Date('now', $timeZone);
			$tzStr    = $zoneDate->format('P');
			$key = 'CONVERT_TZ(' . $key . ', "+0:00",
"' . $tzStr . '")';
		}

		return $key;
	}

	/**
	 * Internal element validation
	 *
	 * For the date element, this is just here to add the element to the
'modified' array for AJAX validation,
	 * so it gets re-displayed.  Need to do this because we change the
displayed date format when submitting (or on
	 * change page in multipage forms, etc).
	 *
	 * @param   array $data          form data
	 * @param   int   $repeatCounter repeat group counter
	 *
	 * @return bool
	 */
	public function validate($data, $repeatCounter = 0)
	{
		if ($this->app->input->get('fabrik_ajax',
'0') === '1')
		{
			if (FabrikWorker::isDate($data))
			{
				$params    = $this->getParams();
				$localDate = $this->displayDate($data);
				$formModel = $this->getFormModel();
				$name      = $this->getFullName(true, false);
				$group     = $this->getGroup();

				if ($group->canRepeat())
				{
					$formModel->modifiedValidationData[$name][$repeatCounter] =
$localDate->format('Y-m-d H:i:s', true);
				}
				else
				{
					$formModel->modifiedValidationData[$name] =
$localDate->format('Y-m-d H:i:s', true);
				}
			}
		}

		return true;
	}

	/**
	 * Get the time format according to time settings
	 *
	 * @return mixed|string
	 */
	private function getTimeFormat()
	{
		$params = $this->getParams();
		$timeFormat = '';

		if ($params->get('jdate_showtime', '0'))
		{
			$timeFormat = $params->get('jdate_time_format',
'');

			if (empty($timeFormat))
			{
				if ($params->get('jdate_time_24', '1'))
				{
					$timeFormat = 'H:i';
				}
				else
				{
					// NOTE - 't usedon 'g' (hours without leading 0) as it
translates to %l, which won't work on Windows
					$timeFormat = 'h:i A';
				}
			}
		}

		return $timeFormat;
	}

	public function beforeSave(&$row)
    {
        $groups = $this->getFormModel()->getGroupsHiarachy();
        $found = false;

        foreach ($groups as $groupModel)
        {
            $elementModels = $groupModel->getPublishedElements();

            foreach ($elementModels as $elementModel)
            {
                if ($elementModel->element->plugin ===
'date') {
                    $found = true;
                    break 2;
                }
            }
        }

        if ($found)
        {
           
$this->app->enqueueMessage(Text::_('PLG_ELEMENT_JDATE_DATE_WARNING'));
        }
    }

	/**
	 * run on formModel::setFormData()
	 * 
	 * purpose is to convert an empty string value into a null, current sql
does not support an empty string value
	 *
	 * @param   int $c repeat group counter
	 *
	 * @return void
	 */
	public function preProcess($c)
	{

		$input  = $this->app->input;
		$form = $this->getFormModel();
		$data = unserialize(serialize($form->formData));

		$key       = $this->getFullName(true, false);
		$rawKey    = $key . '_raw';
		
		if ($data[$key] != '') return;

		/* We have an empty string for a date field, convert it into null */
		$form->updateFormData($key, null);
		$form->updateFormData($rawKey, null);
		$input->post->set($key, null);
		$input->post->set($rawKey, null);
	}
}

/**
 * very small override to Date to stop 500 errors occurring (when Jdebug is
on) if $date is not a valid date string
 *
 * @package  Fabrik
 * @since    3.0
 */
class FabDate extends Date
{
	/**
	 * GMT Date
	 *
	 * @var DateTimeZone
	 */
	protected static $gmt;

	/**
	 * Default tz date
	 *
	 * @var DateTimeZone
	 */
	protected static $stz;

	/**
	 * Construct
	 *
	 * @param   string $date Date
	 * @param   mixed  $tz   Timezone
	 */
	public function __construct($date = 'now', $tz = null)
	{
		$app  = Factory::getApplication();
		$orig = $date;
		$date = $this->stripDays($date);
		/* not sure if this one needed?
		 * $date = $this->monthToInt($date);
		 */
		$date = $this->removeDashes($date);

		try
		{
			$dt = new Date($date);
		}
		catch (Exception $e)
		{
			JDEBUG ? $app->enqueueMessage('date format unknown for ' .
$orig . ' replacing with today\'s date', 'notice')
: '';
			$date = 'now';
			/* catches 'Failed to parse time string (ublingah!) at position 0
(u)' exception.
			 * don't use this object
			 */
		}

		// Create the base GMT and server time zone objects.
		if (empty(self::$gmt) || empty(self::$stz))
		{
			self::$gmt = new \DateTimeZone('GMT');
			self::$stz = new \DateTimeZone(@date_default_timezone_get());
		}

		parent::__construct($date, $tz);
	}

	/**
	 * Remove '-' from string
	 *
	 * @param   string $str String to remove - from
	 *
	 * @return  string
	 */
	protected function removeDashes($str)
	{
		$str = FabrikString::ltrimword($str, '-');

		return $str;
	}

	/**
	 * Month name to integer
	 *
	 * @param   string $str Month name
	 *
	 * @return  int  month number
	 */
	protected function monthToInt($str)
	{
		$abbrs = array(true, false);

		for ($a = 0; $a < count($abbrs); $a++)
		{
			for ($i = 0; $i < 13; $i++)
			{
				$month = $this->monthToString($i, $abbrs[$a]);

				if (StringHelper::stristr($str, $month))
				{
					$monthNum = StringHelper::strlen($i) === 1 ? '0' . $i : $i;
					$str      = StringHelper::str_ireplace($month, $monthNum, $str);
				}
			}
		}

		return $str;
	}

	/**
	 * Converts strftime format into PHP date() format
	 *
	 * @param   string $format  Strftime format
	 *
	 * @since   3.0.7
	 *
	 * @return  string  converted format
	 */
	static public function strftimeFormatToDateFormat($format)
	{
		$app = Factory::getApplication();

		if (strstr($format, '%C'))
		{
			$app->enqueueMessage('Cant convert %C strftime date format to
date format, substituted with Y', 'notice');

			return;
		}

		$search = array('%e', '%j', '%u',
'%V', '%W', '%h', '%B',
'%C', '%g', '%G', '%M',
'%P', '%r', '%R', '%T',
'%X', '%z', '%Z', '%D',
'%F', '%s',
			'%x', '%A', '%Y', '%m',
'%d', '%H', '%S');

		$replace = array('j', 'z', 'w',
'W', 'W', 'M', 'F', 'Y',
'y', 'Y', 'i', 'a',
'"g:i:s a', 'H:i', 'H:i:s',
'H:i:s', 'O', 'O', 'm/d/y"',
'Y-m-d', 'U',
			'Y-m-d', 'l', 'Y', 'm',
'd', 'H', 's');

		return str_replace($search, $replace, $format);
	}

	/**
	 * Convert strftime to PHP time format
	 *
	 * @param   string  $format Format
	 *
	 * @return  string  converted format
	 */
	static public function dateFormatToStrftimeFormat($format)
	{
		$trs = array(
			'd' => '%d',
			'D' => '%a',
			'j' => '%d', //@@@trob: force j to %d (instead of
the 'related' %e which is not working in form view);
			'l' => '%A',
			'N' => '%u',
			'S' => '',
			'w' => '%w',
			'z' => '%j',
			'W' => '%V',
			'F' => '%B',
			'm' => '%m',
			'M' => '%b',
			'n' => '%m',
			't' => '',
			'L' => '',
			'o' => '%g',
			'Y' => '%Y',
			'y' => '%y',
			'a' => '%P',
			'A' => '%p',
			'B' => '',
			'g' => '%l',
			'G' => '%H',
			'h' => '%I',
			'H' => '%H',
			'i' => '%M',
			's' => '%S',
			'e' => '%z',
			'u' => '',
			'I' => '',
			'O' => '',
			'P' => '',
			'T' => '%z',
			'Z' => '',
			'c' => '%c',
			'r' => '%a, %d %b %Y %H:%M:%S %z',
			'U' => '%s'
		);

		return strtr($format, $trs);
	}

	/**
	 * Strip days
	 *
	 * @param   string $str Date string
	 *
	 * @return  string date without days
	 */
	protected function stripDays($str)
	{
		$abbrs = array(true, false);

		for ($a = 0; $a < count($abbrs); $a++)
		{
			for ($i = 0; $i < 7; $i++)
			{
				$day = $this->dayToString($i, $abbrs[$a]);

				if (StringHelper::stristr($str, $day))
				{
					$str = StringHelper::str_ireplace($day, '', $str);
				}
			}
		}

		return $str;
	}
}