Файловый менеджер - Редактировать - /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; } }
| ver. 1.4 |
Github
|
.
| PHP 8.1.33 | Генерация страницы: 0 |
proxy
|
phpinfo
|
Настройка