Enhancement: Date and time formats are now configurable in iTop !! (beta version, beware!)

SVN:trunk[4011]
This commit is contained in:
Denis Flaven
2016-04-22 09:26:27 +00:00
parent b318d27b19
commit 8eba9ae714
44 changed files with 3211 additions and 115 deletions

View File

@@ -630,7 +630,6 @@ EOF
{
if ($iFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE))
{
// Check if the attribute is not read-only because of a synchro...
$aReasons = array();
$sSynchroIcon = '';
@@ -1312,8 +1311,8 @@ EOF
else
{
$iDate = AttributeDateTime::GetAsUnixSeconds($sDate);
$aRow[] = '<td>'.date('Y-m-d', $iDate).'</td>';
$aRow[] = '<td>'.date('H:i:s', $iDate).'</td>';
$aRow[] = '<td>'.date('Y-m-d', $iDate).'</td>'; // Format kept as-is for 100% backward compatibility of the exports
$aRow[] = '<td>'.date('H:i:s', $iDate).'</td>'; // Format kept as-is for 100% backward compatibility of the exports
}
}
else if($oAttDef instanceof AttributeCaseLog)
@@ -1711,10 +1710,7 @@ EOF
$aEventsList[] ='validate';
$aEventsList[] ='keyup';
$aEventsList[] ='change';
if (($iFlags & OPT_ATT_MANDATORY) && (empty($sDisplayValue)))
{
$sDisplayValue = date($oAttDef->GetDateFormat());
}
$sHTMLValue = "<input title=\"$sHelpText\" class=\"date-pick\" type=\"text\" size=\"12\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/>&nbsp;{$sValidationSpan}{$sReloadSpan}";
break;
@@ -1722,11 +1718,8 @@ EOF
$aEventsList[] ='validate';
$aEventsList[] ='keyup';
$aEventsList[] ='change';
if (($iFlags & OPT_ATT_MANDATORY) && (empty($sDisplayValue)))
{
$sDisplayValue = date($oAttDef->GetDateFormat());
}
$sHTMLValue = "<input title=\"$sHelpText\" class=\"datetime-pick\" type=\"text\" size=\"20\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/>&nbsp;{$sValidationSpan}{$sReloadSpan}";
$sHTMLValue = "<input title=\"$sHelpText\" class=\"datetime-pick\" type=\"text\" size=\"15\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/>&nbsp;{$sValidationSpan}{$sReloadSpan}";
break;
case 'Duration':
@@ -3161,6 +3154,14 @@ EOF
'to_be_added' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tba", '[]', 'raw_data'), true),
'to_be_removed' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbr", '[]', 'raw_data'), true) );
}
else if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
{
$value = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data');
if ($value != null)
{
$value = AttributeDateTime::Parse($value, $oAttDef->GetFormat());
}
}
else
{
$value = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data');

View File

@@ -63,6 +63,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
$this->add_header("Cache-control: no-cache");
$this->add_linked_stylesheet("../css/jquery.treeview.css");
$this->add_linked_stylesheet("../css/jquery.autocomplete.css");
$this->add_linked_stylesheet("../css/jquery-ui-timepicker-addon.css");
$this->add_linked_stylesheet("../css/fg.menu.css");
$this->add_linked_stylesheet("../css/jquery.multiselect.css");
$this->add_linked_stylesheet("../css/magnific-popup.css");
@@ -73,6 +74,8 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
$this->add_linked_script("../js/jquery.treeview.js");
$this->add_linked_script("../js/jquery.autocomplete.js");
$this->add_linked_script("../js/date.js");
$this->add_linked_script("../js/jquery-ui-timepicker-addon.js");
$this->add_linked_script("../js/jquery-ui-timepicker-addon-i18n.min.js");
$this->add_linked_script("../js/jquery.blockUI.js");
$this->add_linked_script("../js/utils.js");
$this->add_linked_script("../js/swfobject.js");
@@ -83,8 +86,8 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
$this->add_linked_script('../js/fg.menu.js');
$this->add_linked_script('../js/icon_select.js');
$this->add_linked_script('../js/raphael-min.js');
$this->add_linked_script('../js/d3.min.js');
$this->add_linked_script('../js/c3.min.js');
$this->add_linked_script('../js/d3.js');
$this->add_linked_script('../js/c3.js');
$this->add_linked_script('../js/jquery.multiselect.js');
$this->add_linked_script('../js/ajaxfileupload.js');
$this->add_linked_script('../js/jquery.mousewheel.js');
@@ -152,6 +155,11 @@ EOF;
$sJSMonthsShort = json_encode(array(Dict::S('Month-01-Short'), Dict::S('Month-02-Short'), Dict::S('Month-03-Short'), Dict::S('Month-04-Short'), Dict::S('Month-05-Short'), Dict::S('Month-06-Short'),
Dict::S('Month-07-Short'), Dict::S('Month-08-Short'), Dict::S('Month-09-Short'), Dict::S('Month-10-Short'), Dict::S('Month-11-Short'), Dict::S('Month-12-Short')));
$iFirstDayOfWeek = (int) Dict::S('Calendar-FirstDayOfWeek');
$sDateFormat = AttributeDate::GetDatePickerFormat();
$sJSDateFormat = json_encode($sDateFormat);
$sJSTimeFormat = json_encode(trim(str_replace($sDateFormat, '', AttributeDateTime::GetDatePickerFormat())));
$sJSLangShort = json_encode(strtolower(substr(Dict::GetUserLanguage(), 0, 2)));
$sJSOk = json_encode(Dict::S('UI:Button:Ok'));
$this->m_sInitScript =
<<< EOF
@@ -393,7 +401,7 @@ EOF
showOn: 'button',
buttonImage: '../images/calendar.png',
buttonImageOnly: true,
dateFormat: 'yy-mm-dd',
dateFormat: $sJSDateFormat,
constrainInput: false,
changeMonth: true,
changeYear: true,
@@ -401,18 +409,28 @@ EOF
monthNamesShort: $sJSMonthsShort,
firstDay: $iFirstDayOfWeek
});
$(".datetime-pick").datepicker({
$(".datetime-pick").datetimepicker({
showOn: 'button',
buttonImage: '../images/calendar.png',
buttonImageOnly: true,
dateFormat: 'yy-mm-dd 00:00:00',
dateFormat: $sJSDateFormat,
constrainInput: false,
changeMonth: true,
changeYear: true,
dayNamesMin: $sJSDaysMin,
monthNamesShort: $sJSMonthsShort,
firstDay: $iFirstDayOfWeek
});
firstDay: $iFirstDayOfWeek,
// time picker options
timeFormat: $sJSTimeFormat,
controlType: 'select',
timeText: $.timepicker.regional[$sJSLangShort].timeText,
hourText: $.timepicker.regional[$sJSLangShort].hourText,
minuteText: $.timepicker.regional[$sJSLangShort].minuteText,
secondText: $.timepicker.regional[$sJSLangShort].secondText,
currentText: $.timepicker.regional[$sJSLangShort].currentText,
closeText: $sJSOk
});
// Make sortable, everything that claims to be sortable
$('.sortable').sortable( {axis: 'y', cursor: 'move', handle: '.drag_handle', stop: function()
@@ -454,6 +472,11 @@ EOF
EOF
);
$this->add_ready_script(InlineImage::FixImagesWidth());
/*
* Not used since the sorting of the tables is always performed server-side
AttributeDateTime::InitTableSorter($this, 'custom_date_time');
AttributeDate::InitTableSorter($this, 'custom_date');
*/
$sUserPrefs = appUserPreferences::GetAsJSON();
$this->add_script(

View File

@@ -482,6 +482,19 @@ class utils
}
// http://www.spaweditor.com/scripts/regex/index.php
}
/**
* Convert an old date/time format specifciation (using % placeholders)
* to a format compatible with DateTime::createFromFormat
* @param string $sOldDateTimeFormat
* @return string
*/
static public function DateTimeFormatToPHP($sOldDateTimeFormat)
{
$aSearch = array('%d', '%m', '%y', '%Y', '%H', '%i', '%s');
$aReplacement = array('d', 'm', 'y', 'Y', 'H', 'i', 's');
return str_replace($aSearch, $aReplacement, $sOldDateTimeFormat);
}
static public function GetConfig()
{

View File

@@ -13,6 +13,8 @@ Class XLSXWriter
protected $shared_strings = array();//unique set
protected $shared_string_count = 0;//count of non-unique references to the unique set
protected $temp_files = array();
protected $date_format = 'YYYY-MM-DD';
protected $date_time_format = 'YYYY-MM-DD\ HH:MM:SS';
public function __construct(){}
public function setAuthor($author='') { $this->author=$author; }
@@ -26,6 +28,16 @@ Class XLSXWriter
}
}
public function setDateFormat($date_format)
{
$this->date_format = $date_format;
}
public function setDateTimeFormat($date_time_format)
{
$this->date_time_format = $date_time_format;
}
protected function tempFilename()
{
$filename = tempnam("/tmp", "xlsx_writer_");
@@ -183,8 +195,8 @@ Class XLSXWriter
fwrite($fd, '<numFmts count="4">');
fwrite($fd, '<numFmt formatCode="GENERAL" numFmtId="164"/>');
fwrite($fd, '<numFmt formatCode="[$$-1009]#,##0.00;[RED]\-[$$-1009]#,##0.00" numFmtId="165"/>');
fwrite($fd, '<numFmt formatCode="YYYY-MM-DD\ HH:MM:SS" numFmtId="166"/>');
fwrite($fd, '<numFmt formatCode="YYYY-MM-DD" numFmtId="167"/>');
fwrite($fd, '<numFmt formatCode="'.$this->date_time_format.'" numFmtId="166"/>');
fwrite($fd, '<numFmt formatCode="'.$this->date_format.'" numFmtId="167"/>');
fwrite($fd, '</numFmts>');
fwrite($fd, '<fonts count="4">');
fwrite($fd, '<font><name val="Arial"/><charset val="1"/><family val="2"/><sz val="10"/></font>');

View File

@@ -34,7 +34,7 @@ class ExecAsyncTask implements iBackgroundProcess
public function Process($iTimeLimit)
{
$sNow = date('Y-m-d H:i:s');
$sNow = date(AttributeDateTime::GetSQLFormat());
// Criteria: planned, and expected to occur... ASAP or in the past
$sOQL = "SELECT AsyncTask WHERE (status = 'planned') AND (ISNULL(planned) OR (planned < '$sNow'))";
$iProcessed = 0;

View File

@@ -3537,9 +3537,353 @@ class AttributeMetaEnum extends AttributeEnum
*/
class AttributeDateTime extends AttributeDBField
{
static public function GetDateFormat()
static $sDateTimeFormat = null;
static $sTimeFormat = null;
static public function GetFormat()
{
return "Y-m-d H:i:s";
if (self::$sDateTimeFormat == null)
{
static::LoadFormatFromConfig();
}
return self::$sDateTimeFormat;
}
/**
* Load the 3 settings: date format, time format and data_time format from the configuration
*/
protected static function LoadFormatFromConfig()
{
$aFormats = MetaModel::GetConfig()->Get('date_and_time_format');
$sLang = Dict::GetUserLanguage();
$sDateFormat = isset($aFormats[$sLang]['date']) ? $aFormats[$sLang]['date'] : (isset($aFormats['default']['date']) ? $aFormats['default']['date'] : 'Y-m-d');
$sTimeFormat = isset($aFormats[$sLang]['time']) ? $aFormats[$sLang]['time'] : (isset($aFormats['default']['time']) ? $aFormats['default']['time'] : 'H:i:s');
$sDateAndTimeFormat = isset($aFormats[$sLang]['date_time']) ? $aFormats[$sLang]['date_time'] : (isset($aFormats['default']['date_time']) ? $aFormats['default']['date_time'] : '$date $time');
$sFormat = str_replace(array('$date', '$time'), array($sDateFormat, $sTimeFormat), $sDateAndTimeFormat);
self::SetFormat($sFormat);
self::SetTimeFormat($sTimeFormat);
AttributeDate::SetFormat($sDateFormat);
}
/**
* Returns the format string used for the date & time stored in memory
* @return string
*/
static public function GetInternalFormat()
{
return 'Y-m-d H:i:s';
}
/**
* Returns the format string used for the date & time written to MySQL
* @return string
*/
static public function GetSQLFormat()
{
return 'Y-m-d H:i:s';
}
static public function SetFormat($sDateTimeFormat)
{
self::$sDateTimeFormat = $sDateTimeFormat;
}
static public function GetTimeFormat()
{
if (self::$sTimeFormat == null)
{
static::LoadFormatFromConfig();
}
return self::$sTimeFormat;
}
static public function GetSQLTimeFormat()
{
return 'H:i:s';
}
static public function SetTimeFormat($sTimeFormat)
{
self::$sTimeFormat = $sTimeFormat;
}
/**
* Return the mapping table for converting between various convention for data formats
*/
public static function GetFormatMapping()
{
return array(
// Days
'd' => array('regexpr' => '(0[1-9]|[1-2][0-9]||3[0-1])', 'datepicker' => 'dd', 'usage' => 'day', 'excel' => 'dd'), // Day of the month: 2 digits (with leading zero)
'j' => array('regexpr' => '([1-9]|[1-2][0-9]||3[0-1])', 'datepicker' => 'd', 'usage' => 'day', 'excel' => '%d'), // Day of the month: 1 or 2 digits (without leading zero)
// Months
'm' => array('regexpr' => '(0[1-9]|1[0-2])', 'datepicker' => 'mm', 'usage' => 'month', 'excel' => 'MM'), // Month on 2 digits i.e. 01-12
'n' => array('regexpr' => '([1-9]|1[0-2])', 'datepicker' => 'm', 'usage' => 'month', 'excel' => '%M'), // Month on 1 or 2 digits 1-12
// Years
'Y' => array('regexpr' => '([0-9]{4})', 'datepicker' => 'yy', 'usage' => 'year', 'excel' => 'YYYY'), // Year on 4 digits
'y' => array('regexpr' => '([0-9]{2})', 'datepicker' => 'y', 'usage' => 'year', 'excel' => 'YY'), // Year on 2 digits
// Hours
'H' => array('regexpr' => '([0-1][0-9]|2[0-3])', 'datepicker' => 'HH', 'usage' => 'hour', 'excel' => 'HH'), // Hour 00..23
'h' => array('regexpr' => '(0[1-9]|1[0-2])', 'datepicker' => 'hh', 'usage' => 'hour', 'excel' => 'hh'), // Hour 01..12
'G' => array('regexpr' => '([1-9]|[1[0-9]|2[0-3])', 'datepicker' => 'H', 'usage' => 'hour', 'excel' => '%H'), // Hour 0..23
'g' => array('regexpr' => '([1-9]|1[0-2])', 'datepicker' => 'h', 'usage' => 'hour', 'excel' => '%h'), // Hour 1..12
'a' => array('regexpr' => '(am|pm)', 'datepicker' => 'tt', 'usage' => 'am/pm', 'excel' => 'am/pm'),
'A' => array('regexpr' => '(AM|PM)', 'datepicker' => 'TT', 'usage' => 'am/pm', 'excel' => 'AM/PM'),
// Minutes
'i' => array('regexpr' => '([0-5][0-9])', 'datepicker' => 'mm', 'usage' => 'minutes', 'excel' => 'mm'),
// Seconds
's' => array('regexpr' => '([0-5][0-9])', 'datepicker' => 'ss', 'usage' => 'seconds', 'excel' => 'ss'),
);
}
/**
* Format a date into the supplied format string
* @param mixed $date An int, string, DateTime object or null !!
* @param string $sFormat The format using PHP createFromFormat convention
* @throws Exception
* @return string The formatted date
*/
public static function Format($date, $sFormat = null)
{
if ($sFormat === null)
{
$sFormat = static::GetFormat();
}
if ($date == null)
{
$sDate = '';
}
else if (($date === '0000-00-00') || ($date === '0000-00-00 00:00:00'))
{
$sDate = '';
}
else if ($date instanceof DateTime)
{
// Parameter is a DateTime
$sDate = $date->format($sFormat);
}
else if (is_int($date))
{
// Parameter is a Unix timestamp
$oDate = new DateTime();
$oDate->setTimestamp($date);
$sDate = $oDate->format($sFormat);
}
else if (is_string($date))
{
$oDate = new DateTime($date);
$sDate = $oDate->format($sFormat);
}
else
{
throw new Exception("AttributeDateTime::Format: Unexpected date value: ".print_r($date, true));
}
return $sDate;
}
/**
* Parse a date in the supplied format and return the date as a string in the internal format
* @param string $sDate The string to parse
* @param string $sFormat The format, in PHP createFromFormat convention
* @throws Exception
* @return string
*/
public static function Parse($sDate, $sFormat)
{
if (($sDate == null) || ($sDate == '0000-00-00 00:00:00') || ($sDate == '0000-00-00'))
{
return null;
}
else
{
$sFormat = preg_replace('/\\?/', '', $sFormat); // replace escaped characters by a wildcard for parsing
$oDate = DateTime::createFromFormat($sFormat, $sDate);
if ($oDate === false)
{
throw new Exception("Unable to parse the date: '$sDate' using the format: '$sFormat'");
}
return $oDate->format(static::GetInternalFormat());
}
}
/**
* Get a date or datetime format string in the jQuery UI date picker format
* @param string $sFormat
* @return string The format string using the date picker convention
*/
static public function GetDatePickerFormat()
{
$sFormat = static::GetFormat();
$aMappings = static::GetFormatMapping();
$sResult = '';
$bEscaping = false;
for($i=0; $i < strlen($sFormat); $i++)
{
if (($sFormat[$i] == '\\'))
{
$bEscaping = true;
continue;
}
if ($bEscaping)
{
$sResult .= "'{$sFormat[$i]}'";
$bEscaping = false;
}
else if(array_key_exists($sFormat[$i], $aMappings))
{
// Not a litteral value, must be replaced by its regular expression pattern
$sResult .= $aMappings[$sFormat[$i]]['datepicker'];
}
else
{
// Normal char with no special meaning
$sResult .= $sFormat[$i];
}
}
return $sResult;
}
/**
* Get a date or datetime format string in the Excel format
* @param string $sFormat
* @return string The format string using the Excel convention
*/
static public function GetExcelFormat($sFormat = null)
{
$sFormat = ($sFormat == null) ? static::GetFormat() : $sFormat;
$aMappings = static::GetFormatMapping();
$sResult = '';
$bEscaping = false;
for($i=0; $i < strlen($sFormat); $i++)
{
if (($sFormat[$i] == '\\'))
{
$bEscaping = true;
continue;
}
if ($bEscaping)
{
$sResult .= $sFormat[$i]; // What's the way to escape characters in Excel format ??
$bEscaping = false;
}
else if(array_key_exists($sFormat[$i], $aMappings))
{
// Not a litteral value, must be replaced by its regular expression pattern
$sResult .= $aMappings[$sFormat[$i]]['excel'];
}
else
{
// Normal char with no special meaning
$sResult .= $sFormat[$i];
}
}
return $sResult;
}
/*
* Unused since the sorting of the tables is always performed server-side
*
public static function GetTableSorterRule()
{
$aOrder = array();
$aPos = array();
$sRegExpr = static::GetRegExpr($aOrder);
foreach(array('year', 'month', 'day', 'hour', 'minutes', 'seconds') as $sUsage)
{
$pos = array_search($sUsage, $aOrder);
if ($pos !== false)
{
$aPos[$sUsage] = '$'.(1+$pos);
}
}
$sIsoDate = "{$aPos['year']}/{$aPos['month']}/{$aPos['day']}";
if (array_key_exists('hour', $aPos))
{
$sIsoDate .= " {$aPos['hour']}:{$aPos['minutes']}:{$aPos['seconds']}";
}
return array('regexpr' => $sRegExpr, 'replacement' => $sIsoDate);
}
public static function InitTableSorter($oPage, $sRuleName)
{
$aDef = static::GetTableSorterRule();
$oPage->add_ready_script(
<<<EOF
$.tablesorter.addParser({
id: "$sRuleName",
is: function (s) {
return /^({$aDef['regexpr']})$/.test(s);
}, format: function (s) {
s = s.replace(/{$aDef['regexpr']}/, "{$aDef['replacement']}");
return $.tablesorter.formatFloat(new Date(s).getTime());
}, type: "numeric"
});
EOF
);
}
*/
/**
* Get the regular expression to (approximately) validate a date/time for the current format
* @param array $aOrder
* @return string The regular expression in PCRE syntax
*/
static public function GetRegExpr(&$aOrder = null)
{
$sFormat = static::GetFormat();
$aMappings = static::GetFormatMapping();
$sSpecialChars = '.?*$^()[]/'; // Characters having a special meaning in a regular expression, must be escaped by prepending a backslash
$sResult = '^';
$bEscaping = false;
for($i=0; $i < strlen($sFormat); $i++)
{
if (($sFormat[$i] == '\\') && !$bEscaping)
{
$bEscaping = true;
continue;
}
if (!$bEscaping && array_key_exists($sFormat[$i], $aMappings))
{
// Not a litteral value, must be replaced by its regular expression pattern
$sResult .= $aMappings[$sFormat[$i]]['regexpr'];
if ($aOrder !== null)
{
$aOrder[] = $aMappings[$sFormat[$i]]['usage'];
}
}
else
{
// Litteral value, take care of special characters in a RegExpr
if (strpos($sSpecialChars, $sFormat[$i]) !== false)
{
$sResult .= '\\'.$sFormat[$i];
}
else
{
// Normal char with no special meaning
$sResult .= $sFormat[$i];
}
}
if ($bEscaping)
{
$bEscaping = false;
}
}
$sResult .= '$';
return $sResult;
}
static public function ListExpectedParams()
@@ -3550,6 +3894,12 @@ class AttributeDateTime extends AttributeDBField
public function GetEditClass() {return "DateTime";}
public function GetEditValue($sValue, $oHostObj = null)
{
return (string)static::Format($sValue, static::GetFormat());
}
protected function GetSQLCol($bFullSpec = false) {return "DATETIME";}
public static function GetAsUnixSeconds($value)
{
@@ -3558,29 +3908,15 @@ class AttributeDateTime extends AttributeDBField
return $iUnixSeconds;
}
// This has been done at the time when itop was using TIMESTAMP columns,
// now that iTop is using DATETIME columns, it seems possible to have IsNullAllowed returning false... later when this is needed
public function IsNullAllowed() {return true;}
public function GetDefaultValue(DBObject $oHostObject = null)
{
$default = parent::GetDefaultValue($oHostObject);
if (!parent::IsNullAllowed())
{
if (empty($default))
{
$default = date($this->GetDateFormat());
}
}
return $default;
// null value will be replaced by the current date, if not already set, in DoComputeValues
return $this->GetNullValue();
}
// END OF THE WORKAROUND
///////////////////////////////////////////////////////////////
public function GetValidationPattern()
{
return "^(([0-9]{4}-(((0[13578]|(10|12))-(0[1-9]|[1-2][0-9]|3[0-1]))|(02-(0[1-9]|[1-2][0-9]))|((0[469]|11)-(0[1-9]|[1-2][0-9]|30))))( (0[0-9]|1[0-9]|2[0-3]):([0-5][0-9])(:([0-5][0-9])){0,1}){0,1}|0000-00-00 00:00:00|0000-00-00)$";
return static::GetRegExpr();
}
public function GetBasicFilterOperators()
@@ -3654,7 +3990,7 @@ class AttributeDateTime extends AttributeDBField
return $proposedValue;
}
return date(self::GetDateFormat(), $proposedValue);
return date(self::GetInternalFormat(), $proposedValue);
}
public function ScalarToSQL($value)
@@ -3673,7 +4009,7 @@ class AttributeDateTime extends AttributeDBField
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
{
return Str::pure2html($value);
return Str::pure2html(static::Format($value, static::GetFormat()));
}
public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
@@ -3683,6 +4019,19 @@ class AttributeDateTime extends AttributeDBField
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
if (empty($sValue) || ($sValue === '0000-00-00 00:00:00') || ($sValue === '0000-00-00'))
{
return '';
}
else if (self::GetFormat() !== self::GetInternalFormat())
{
// Format conversion
$oDate = new DateTime($sValue);
if ($oDate !== false)
{
$sValue = $oDate->format(self::GetFormat());
}
}
$sFrom = array("\r\n", $sTextQualifier);
$sTo = array("\n", $sTextQualifier.$sTextQualifier);
$sEscaped = str_replace($sFrom, $sTo, (string)$sValue);
@@ -3839,13 +4188,40 @@ class AttributeDuration extends AttributeInteger
*/
class AttributeDate extends AttributeDateTime
{
const MYDATEFORMAT = "Y-m-d";
static public function GetDateFormat()
static $sDateFormat = null;
static public function GetFormat()
{
return "Y-m-d";
if (self::$sDateFormat == null)
{
AttributeDateTime::LoadFormatFromConfig();
}
return self::$sDateFormat;
}
static public function SetFormat($sDateFormat)
{
self::$sDateFormat = $sDateFormat;
}
/**
* Returns the format string used for the date & time stored in memory
* @return string
*/
static public function GetInternalFormat()
{
return 'Y-m-d';
}
/**
* Returns the format string used for the date & time written to MySQL
* @return string
*/
static public function GetSQLFormat()
{
return 'Y-m-d';
}
static public function ListExpectedParams()
{
return parent::ListExpectedParams();
@@ -3854,11 +4230,6 @@ class AttributeDate extends AttributeDateTime
public function GetEditClass() {return "Date";}
protected function GetSQLCol($bFullSpec = false) {return "DATE";}
public function GetValidationPattern()
{
return "^[0-9]{4}-(((0[13578]|(10|12))-(0[1-9]|[1-2][0-9]|3[0-1]))|(02-(0[1-9]|[1-2][0-9]))|((0[469]|11)-(0[1-9]|[1-2][0-9]|30)))$";
}
}
/**

View File

@@ -258,7 +258,7 @@ class BulkChange
protected $m_aReconcilKeys; // attcode (attcode = 'id' for the pkey)
protected $m_sSynchroScope; // OQL - if specified, then the missing items will be reported
protected $m_aOnDisappear; // array of attcode => value, values to be set when an object gets out of scope (ignored if no scope has been defined)
protected $m_sDateFormat; // Date format specification, see utils::StringToTime()
protected $m_sDateFormat; // Date format specification, see DateTime::createFromFormat
protected $m_bLocalizedValues; // Values in the data set are localized (see AttributeEnum)
protected $m_aExtKeysMappingCache; // Cache for resolving external keys based on the given search criterias
@@ -800,16 +800,16 @@ class BulkChange
foreach ($this->m_aAttList as $sAttCode => $iCol)
{
if ($sAttCode == 'id') continue;
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
if ($oAttDef instanceof AttributeDateTime)
{
foreach($this->m_aData as $iRow => $aRowData)
{
$sNewDate = utils::StringToTime($this->m_aData[$iRow][$iCol], $this->m_sDateFormat);
if ($sNewDate !== false)
$oDate = DateTime::createFromFormat($this->m_sDateFormat, $this->m_aData[$iRow][$iCol]);
if ($oDate !== false)
{
// Todo - improve the reporting
$sNewDate = $oDate->format($oAttDef->GetInternalFormat());
$this->m_aData[$iRow][$iCol] = $sNewDate;
}
else

View File

@@ -95,7 +95,7 @@ class BulkExportResultGC implements iBackgroundProcess
public function Process($iTimeLimit)
{
$sDateLimit = date('Y-m-d H:i:s', time() - 24*3600); // Every BulkExportResult older than one day will be deleted
$sDateLimit = date(AttributeDateTime::GetSQLFormat(), time() - 24*3600); // Every BulkExportResult older than one day will be deleted
$sOQL = "SELECT BulkExportResult WHERE created < '$sDateLimit'";
$iProcessed = 0;

View File

@@ -898,6 +898,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'date_and_time_format' => array(
'type' => 'array',
'description' => 'Format for date and time display (per language)',
'default' => array('default' => array('date' => 'Y-m-d', 'time' => 'H:i:s', 'date_time' => '$date $time')),
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => true,
),
);
public function IsProperty($sPropCode)

View File

@@ -34,6 +34,7 @@ class CSVBulkExport extends TabularBulkExport
$oP->p(" *\ttext-qualifier: (optional) character to be used around text strings (default is '\"').");
$oP->p(" *\tno_localize: set to 1 to retrieve non-localized values (for instance for ENUM values). Default is 0 (= localized values)");
$oP->p(" *\tformatted_text: set to 1 to export case logs and formatted text fields with their HTML markup. Default is 0 (= plain text)");
$oP->p(" *\tdate_format: the format to use when exporting date and time fields (default = the format used in the user interface). Example: 'm/d/Y H:i:s'");
}
public function ReadParameters()
@@ -57,6 +58,16 @@ class CSVBulkExport extends TabularBulkExport
$this->aStatusInfo['charset'] = strtoupper(utils::ReadParam('charset', 'UTF-8', true, 'raw_data'));
$this->aStatusInfo['formatted_text'] = (bool)utils::ReadParam('formatted_text', 0, true);
$sDateFormatRadio = utils::ReadParam('date_format_radio', 'custom');
if ($sDateFormatRadio == 'default')
{
$this->aStatusInfo['date_format'] = AttributeDateTime::GetFormat();
}
else
{
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', AttributeDateTime::GetFormat(), true, 'raw_data');
}
}
@@ -97,6 +108,7 @@ class CSVBulkExport extends TabularBulkExport
$oP->add('<table class="export_parameters"><tr><td style="vertical-align:top">');
$oP->add('<h3>'.Dict::S('UI:CSVImport:SeparatorCharacter').'</h3>');
$sRawSeparator = utils::ReadParam('separator', ',', true, 'raw_data');
$sCustomDateTimeFormat = utils::ReadParam('', ',', true, 'raw_data');
$aSep = array(
';' => Dict::S('UI:CSVImport:SeparatorSemicolon+'),
',' => Dict::S('UI:CSVImport:SeparatorComma+'),
@@ -162,10 +174,28 @@ class CSVBulkExport extends TabularBulkExport
$sChecked = (utils::ReadParam('formatted_text', 0) == 1) ? ' checked ' : '';
$oP->add('<h3>'.Dict::S('Core:BulkExport:TextFormat').'</h3>');
$oP->add('<input type="checkbox" id="csv_formatted_text" name="formatted_text" value="1"'.$sChecked.'><label for="csv_formatted_text"> '.Dict::S('Core:BulkExport:OptionFormattedText').'</label>');
$oP->add('</td><td style="vertical-align:top">');
$sDateTimeFormat = utils::ReadParam('date_format', AttributeDateTime::GetFormat(), true, 'raw_data');
$sDefaultChecked = ($sDateTimeFormat == AttributeDateTime::GetFormat()) ? ' checked' : '';
$sCustomChecked = ($sDateTimeFormat !== AttributeDateTime::GetFormat()) ? ' checked' : '';
$oP->add('<h3>'.Dict::S('Core:BulkExport:DateTimeFormat').'</h3>');
$sDefaultFormat = htmlentities(AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
$sExample = htmlentities(date(AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
$oP->add('<input type="radio" id="csv_date_time_format_default" name="date_format_radio" value="default"'.$sDefaultChecked.'><label for="csv_date_time_format_default"> '.Dict::Format('Core:BulkExport:DateTimeFormatDefault_Example', $sDefaultFormat, $sExample).'</label><br/>');
$sFormatInput = '<input type="text" size="15" name="date_format" id="csv_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
$oP->add('<input type="radio" id="csv_date_time_format_custom" name="date_format_radio" value="custom"'.$sCustomChecked.'><label for="csv_date_time_format_custom"> '.Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput).'</label>');
$oP->add('</td></tr></table>');
$oP->add('</fieldset>');
$sJSTooltip = json_encode('<div class="date_format_tooltip">'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'</div>');
$oP->add_ready_script(
<<<EOF
$('#csv_custom_date_time_format').tooltip({content: function() { return $sJSTooltip; } });
$('#csv_custom_date_time_format').on('click', function() { $('#csv_date_time_format_custom').prop('checked', true); });
EOF
);
break;
@@ -257,7 +287,10 @@ class CSVBulkExport extends TabularBulkExport
break;
default:
$sPrevFormat = AttributeDateTime::GetFormat();
AttributeDateTime::SetFormat($this->aStatusInfo['date_format']);
$sField = $oObj->GetAsCSV($sAttCode, $this->aStatusInfo['separator'], $this->aStatusInfo['text_qualifier'], $this->bLocalizeOutput, !$this->aStatusInfo['formatted_text']);
AttributeDateTime::SetFormat($sPrevFormat);
}
}
if ($this->aStatusInfo['charset'] != 'UTF-8')

View File

@@ -705,6 +705,17 @@ abstract class DBObject implements iDisplay
if ($aCallInfo["function"] != "ComputeValues") continue;
return; //skip!
}
// Set the "null-not-allowed" datetimes (and dates) whose value is not initialized
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef)
{
// AttributeDate is derived from AttributeDateTime
if (($oAttDef instanceof AttributeDateTime) && (!$oAttDef->IsNullAllowed()) && ($this->Get($sAttCode) == $oAttDef->GetNullValue()))
{
$this->Set($sAttCode, date($oAttDef->GetFormat()));
}
}
$this->ComputeValues();
}
@@ -3300,8 +3311,8 @@ abstract class DBObject implements iDisplay
}
$aContext['current_contact_id'] = UserRights::GetContactId();
$aContext['current_contact_friendlyname'] = UserRights::GetUserFriendlyName();
$aContext['current_date'] = date('Y-m-d');
$aContext['current_time'] = date('H:i:s');
$aContext['current_date'] = date(AttributeDate::GetSQLFormat());
$aContext['current_time'] = date(AttributeDateTime::GetSQLTimeFormat());
$sValue = MetaModel::ApplyParams($sRawValue, $aContext);
$this->Set($sAttCode, $sValue);
break;
@@ -3328,8 +3339,8 @@ abstract class DBObject implements iDisplay
}
$aContext['current_contact_id'] = UserRights::GetContactId();
$aContext['current_contact_friendlyname'] = UserRights::GetUserFriendlyName();
$aContext['current_date'] = date('Y-m-d');
$aContext['current_time'] = date('H:i:s');
$aContext['current_date'] = date(AttributeDate::GetSQLFormat());
$aContext['current_time'] = date(AttributeDateTime::GetSQLTimeFormat());
$sAddendum = MetaModel::ApplyParams($sRawAddendum, $aContext);
$this->Set($sAttCode, $this->Get($sAttCode).$sAddendum);
break;

View File

@@ -47,12 +47,23 @@ class ExcelBulkExport extends TabularBulkExport
$oP->p(" * xlsx format options:");
$oP->p(" *\tfields: the comma separated list of field codes to export (e.g: name,org_id,service_name...).");
$oP->p(" *\tformatted_text: set to 1 to export case logs and formatted text fields with their HTML markup. Default is 0 (= plain text)");
$oP->p(" *\tdate_format: the format to use when exporting date and time fields (default = the format used in the user interface). Example: 'm/d/Y H:i:s'");
}
public function ReadParameters()
{
parent::ReadParameters();
$this->aStatusInfo['formatted_text'] = (bool)utils::ReadParam('formatted_text', 0, true);
$sDateFormatRadio = utils::ReadParam('date_format_radio', 'custom');
if ($sDateFormatRadio == 'default')
{
$this->aStatusInfo['date_format'] = AttributeDateTime::GetFormat();
}
else
{
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', AttributeDateTime::GetFormat(), true, 'raw_data');
}
}
public function EnumFormParts()
@@ -76,9 +87,28 @@ class ExcelBulkExport extends TabularBulkExport
$oP->add('<h3>'.Dict::S('Core:BulkExport:TextFormat').'</h3>');
$oP->add('<input type="checkbox" id="xlsx_formatted_text" name="formatted_text" value="1"'.$sChecked.'><label for="xlsx_formatted_text"> '.Dict::S('Core:BulkExport:OptionFormattedText').'</label>');
$oP->add('</td><td style="vertical-align:top">');
$sDateTimeFormat = utils::ReadParam('date_format', AttributeDateTime::GetFormat(), true, 'raw_data');
$sDefaultChecked = ($sDateTimeFormat == AttributeDateTime::GetFormat()) ? ' checked' : '';
$sCustomChecked = ($sDateTimeFormat !== AttributeDateTime::GetFormat()) ? ' checked' : '';
$oP->add('<h3>'.Dict::S('Core:BulkExport:DateTimeFormat').'</h3>');
$sDefaultFormat = htmlentities(AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
$sExample = htmlentities(date(AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
$oP->add('<input type="radio" id="excel_date_time_format_default" name="date_format_radio" value="default"'.$sDefaultChecked.'><label for="excel_date_time_format_default"> '.Dict::Format('Core:BulkExport:DateTimeFormatDefault_Example', $sDefaultFormat, $sExample).'</label><br/>');
$sFormatInput = '<input type="text" size="15" name="date_format" id="excel_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
$oP->add('<input type="radio" id="excel_date_time_format_custom" name="date_format_radio" value="custom"'.$sCustomChecked.'><label for="excel_date_time_format_custom"> '.Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput).'</label>');
$oP->add('</td></tr></table>');
$oP->add('</fieldset>');
$sJSTooltip = json_encode('<div class="date_format_tooltip">'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'</div>');
$oP->add_ready_script(
<<<EOF
$('#excel_custom_date_time_format').tooltip({content: function() { return $sJSTooltip; } });
$('#excel_custom_date_time_format').on('click', function() { $('#excel_date_time_format_custom').prop('checked', true); });
EOF
);
break;
default:
@@ -141,14 +171,19 @@ class ExcelBulkExport extends TabularBulkExport
else
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
if (array_key_exists('formatted_text', $this->aStatusInfo) && $this->aStatusInfo['formatted_text'])
{
if ($oAttDef instanceof AttributeDateTime)
{
// Date and times are formatted using the ISO encoding, not the localized format
$sRet = $value;
}
else if (array_key_exists('formatted_text', $this->aStatusInfo) && $this->aStatusInfo['formatted_text'])
{
$sRet = $oAttDef->GetEditValue($value, $oObj);
}
else
{
$sRet = $oAttDef->GetAsPlainText($value, $oObj);
}
}
else
{
$sRet = $oAttDef->GetAsPlainText($value, $oObj);
}
}
}
return $sRet;
@@ -269,6 +304,7 @@ class ExcelBulkExport extends TabularBulkExport
$fStartExcel = microtime(true);
$writer = new XLSXWriter();
$writer->setDateTimeFormat(AttributeDateTime::GetExcelFormat($this->aStatusInfo['date_format']));
$writer->setAuthor(UserRights::GetUserFriendlyName());
$aHeaderTypes = array();
$aHeaderNames = array();

View File

@@ -488,7 +488,7 @@ class InlineImageGC implements iBackgroundProcess
public function Process($iTimeLimit)
{
$sDateLimit = date('Y-m-d H:i:s', time()); // Every temporary InlineImage/Attachment expired will be deleted
$sDateLimit = date(AttributeDateTime::GetSQLFormat(), time()); // Every temporary InlineImage/Attachment expired will be deleted
$iProcessed = 0;
$sOQL = "SELECT InlineImage WHERE (item_id = 0) AND (expire < '$sDateLimit')";

View File

@@ -115,14 +115,14 @@ class ormCaseLog {
if (is_int($this->m_aIndex[$index]['date']))
{
// Unix timestamp
$sDate = date(Dict::S('UI:CaseLog:DateFormat'),$this->m_aIndex[$index]['date']);
$sDate = date(AttributeDateTime::GetInternalFormat(),$this->m_aIndex[$index]['date']);
}
elseif (is_object($this->m_aIndex[$index]['date']))
{
if (version_compare(phpversion(), '5.3.0', '>='))
{
// DateTime
$sDate = $this->m_aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat'));
$sDate = $this->m_aIndex[$index]['date']->format(AttributeDateTime::GetInternalFormat());
}
else
{
@@ -234,14 +234,14 @@ class ormCaseLog {
if (is_int($aIndex[$index]['date']))
{
// Unix timestamp
$sDate = date(Dict::S('UI:CaseLog:DateFormat'),$aIndex[$index]['date']);
$sDate = date(AttributeDateTime::GetFormat(), $aIndex[$index]['date']);
}
elseif (is_object($aIndex[$index]['date']))
{
if (version_compare(phpversion(), '5.3.0', '>='))
{
// DateTime
$sDate = $aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat'));
$sDate = $aIndex[$index]['date']->format(AttributeDateTime::GetFormat());
}
else
{
@@ -322,14 +322,14 @@ class ormCaseLog {
if (is_int($aIndex[$index]['date']))
{
// Unix timestamp
$sDate = date(Dict::S('UI:CaseLog:DateFormat'),$aIndex[$index]['date']);
$sDate = date(AttributeDateTime::GetFormat(),$aIndex[$index]['date']);
}
elseif (is_object($aIndex[$index]['date']))
{
if (version_compare(phpversion(), '5.3.0', '>='))
{
// DateTime
$sDate = $aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat'));
$sDate = $aIndex[$index]['date']->format(AttributeDateTime::GetFormat());
}
else
{
@@ -425,14 +425,14 @@ class ormCaseLog {
if (is_int($aIndex[$index]['date']))
{
// Unix timestamp
$sDate = date(Dict::S('UI:CaseLog:DateFormat'),$aIndex[$index]['date']);
$sDate = date(AttributeDateTime::GetFormat(),$aIndex[$index]['date']);
}
elseif (is_object($aIndex[$index]['date']))
{
if (version_compare(phpversion(), '5.3.0', '>='))
{
// DateTime
$sDate = $aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat'));
$sDate = $aIndex[$index]['date']->format(AttributeDateTime::GetFormat());
}
else
{
@@ -498,7 +498,7 @@ class ormCaseLog {
{
$sText = HTMLSanitizer::Sanitize($sText);
$bMergeEntries = false;
$sDate = date(Dict::S('UI:CaseLog:DateFormat'));
$sDate = date(AttributeDateTime::GetInternalFormat());
if ($sOnBehalfOf == '')
{
$sOnBehalfOf = UserRights::GetUserFriendlyName();
@@ -612,7 +612,7 @@ class ormCaseLog {
$sFormat = 'html';
}
$sDate = date(Dict::S('UI:CaseLog:DateFormat'), $iDate);
$sDate = date(AttributeDateTime::GetInternalFormat(), $iDate);
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
$iSepLength = strlen($sSeparator);

View File

@@ -490,7 +490,7 @@ class CheckStopWatchThresholds implements iBackgroundProcess
{
$iPercent = $aThresholdData['percent']; // could be different than the index !
$sNow = date('Y-m-d H:i:s');
$sNow = date(AttributeDateTime::GetSQLFormat());
$sExpression = "SELECT $sClass WHERE {$sAttCode}_laststart AND {$sAttCode}_{$iThreshold}_triggered = 0 AND {$sAttCode}_{$iThreshold}_deadline < '$sNow'";
$oFilter = DBObjectSearch::FromOQL($sExpression);
$oSet = new DBObjectSet($oFilter);

View File

@@ -239,7 +239,7 @@ class iTopOwnershipLock
{
if ($sToken === $this->oToken->Get('token'))
{
$this->oToken->Set('last_seen', date('Y-m-d H:i:s'));
$this->oToken->Set('last_seen', date(AttributeDateTime::GetSQLFormat()));
$this->oToken->DBUpdate();
$aResult['acquired'] = $this->oToken->Get('acquired');
}
@@ -327,9 +327,9 @@ class iTopOwnershipLock
$this->oToken->Set('obj_class', $this->sObjClass);
$this->oToken->Set('obj_key', $this->iObjKey);
}
$this->oToken->Set('acquired', date('Y-m-d H:i:s'));
$this->oToken->Set('acquired', date(AttributeDateTime::GetSQLFormat()));
$this->oToken->Set('user_id', UserRights::GetUserId());
$this->oToken->Set('last_seen', date('Y-m-d H:i:s'));
$this->oToken->Set('last_seen', date(AttributeDateTime::GetSQLFormat()));
if ($sToken === null)
{
$sToken = sprintf('%X', microtime(true));
@@ -342,7 +342,7 @@ class iTopOwnershipLock
protected static function DeleteExpiredLocks()
{
$sOQL = "SELECT iTopOwnershipToken WHERE last_seen < :last_seen_limit";
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL, array('last_seen_limit' => date('Y-m-d H:i:s', time() - MetaModel::GetConfig()->Get('concurrent_lock_expiration_delay')))));
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL, array('last_seen_limit' => date(AttributeDateTime::GetSQLFormat(), time() - MetaModel::GetConfig()->Get('concurrent_lock_expiration_delay')))));
while($oToken = $oSet->Fetch())
{
$oToken->DBDelete();

View File

@@ -31,6 +31,7 @@ class PDFBulkExport extends HTMLBulkExport
$oP->p(" *\tfields: (mandatory) the comma separated list of field codes to export (e.g: name,org_id,service_name...).");
$oP->p(" *\tpage_size: (optional) size of the page. One of A4, A3, Letter (default is 'A4').");
$oP->p(" *\tpage_orientation: (optional) the orientation of the page. Either Portrait or Landscape (default is 'Portrait').");
$oP->p(" *\tdate_format: the format to use when exporting date and time fields (default = the format used in the user interface). Example: 'm/d/Y H:i:s'");
}
public function EnumFormParts()
@@ -44,6 +45,8 @@ class PDFBulkExport extends HTMLBulkExport
{
case 'pdf_options':
$oP->add('<fieldset><legend>'.Dict::S('Core:BulkExport:PDFOptions').'</legend>');
$oP->add('<table class="export_parameters"><tr><td style="vertical-align:top">');
$oP->add('<h3>'.Dict::S('Core:PDFBulkExport:PageFormat').'</h3>');
$oP->add('<table>');
$oP->add('<tr>');
$oP->add('<td>'.Dict::S('Core:BulkExport:PDFPageSize').'</td>');
@@ -53,8 +56,30 @@ class PDFBulkExport extends HTMLBulkExport
$oP->add('<td>'.$this->GetSelectCtrl('page_orientation', array('P', 'L'), 'Core:BulkExport:PageOrientation-', 'L').'</td>');
$oP->add('</tr>');
$oP->add('</table>');
$oP->add('</td><td style="vertical-align:top">');
$sDateTimeFormat = utils::ReadParam('date_format', AttributeDateTime::GetFormat(), true, 'raw_data');
$sDefaultChecked = ($sDateTimeFormat == AttributeDateTime::GetFormat()) ? ' checked' : '';
$sCustomChecked = ($sDateTimeFormat !== AttributeDateTime::GetFormat()) ? ' checked' : '';
$oP->add('<h3>'.Dict::S('Core:BulkExport:DateTimeFormat').'</h3>');
$sDefaultFormat = htmlentities(AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
$sExample = htmlentities(date(AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
$oP->add('<input type="radio" id="pdf_date_time_format_default" name="date_format_radio" value="default"'.$sDefaultChecked.'><label for="pdf_date_time_format_default"> '.Dict::Format('Core:BulkExport:DateTimeFormatDefault_Example', $sDefaultFormat, $sExample).'</label><br/>');
$sFormatInput = '<input type="text" size="15" name="date_format" id="pdf_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
$oP->add('<input type="radio" id="pdf_date_time_format_custom" name="date_format_radio" value="custom"'.$sCustomChecked.'><label for="pdf_date_time_format_custom"> '.Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput).'</label>');
$oP->add('</td></tr></table>');
$oP->add('</fieldset>');
$sJSTooltip = json_encode('<div id="date_format_tooltip">'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'</div>');
$oP->add_ready_script(
<<<EOF
$('#pdf_custom_date_time_format').tooltip({content: function() { return $sJSTooltip; } });
$('#pdf_custom_date_time_format').on('click', function() { $('#pdf_date_time_format_custom').prop('checked', true); });
EOF
);
break;
default:
@@ -88,6 +113,16 @@ class PDFBulkExport extends HTMLBulkExport
parent::ReadParameters();
$this->aStatusInfo['page_size'] = utils::ReadParam('page_size', 'A4', true, 'raw_data');
$this->aStatusInfo['page_orientation'] = utils::ReadParam('page_orientation', 'L', true);
$sDateFormatRadio = utils::ReadParam('date_format_radio', 'custom');
if ($sDateFormatRadio == 'default')
{
$this->aStatusInfo['date_format'] = AttributeDateTime::GetFormat();
}
else
{
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', AttributeDateTime::GetFormat(), true, 'raw_data');
}
}
public function GetHeader()
@@ -106,7 +141,10 @@ class PDFBulkExport extends HTMLBulkExport
public function GetNextChunk(&$aStatus)
{
$sPrevFormat = AttributeDateTime::GetFormat();
AttributeDateTime::SetFormat($this->aStatusInfo['date_format']);
$sData = parent::GetNextChunk($aStatus);
AttributeDateTime::SetFormat($sPrevFormat);
$hFile = @fopen($this->aStatusInfo['tmp_file'], 'ab');
if ($hFile === false)
{

View File

@@ -0,0 +1,30 @@
.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; }
.ui-timepicker-div dl { text-align: left; }
.ui-timepicker-div dl dt { float: left; clear:left; padding: 0 0 0 5px; }
.ui-timepicker-div dl dd { margin: 0 10px 10px 40%; }
.ui-timepicker-div td { font-size: 90%; }
.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; }
.ui-timepicker-div .ui_tpicker_unit_hide{ display: none; }
.ui-timepicker-div .ui_tpicker_time .ui_tpicker_time_input { background: none; color: inherit; border: none; outline: none; border-bottom: solid 1px #555; width: 95%; }
.ui-timepicker-div .ui_tpicker_time .ui_tpicker_time_input:focus { border-bottom-color: #aaa; }
.ui-timepicker-rtl{ direction: rtl; }
.ui-timepicker-rtl dl { text-align: right; padding: 0 5px 0 0; }
.ui-timepicker-rtl dl dt{ float: right; clear: right; }
.ui-timepicker-rtl dl dd { margin: 0 40% 10px 10px; }
/* Shortened version style */
.ui-timepicker-div.ui-timepicker-oneLine { padding-right: 2px; }
.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_time,
.ui-timepicker-div.ui-timepicker-oneLine dt { display: none; }
.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_time_label { display: block; padding-top: 2px; }
.ui-timepicker-div.ui-timepicker-oneLine dl { text-align: right; }
.ui-timepicker-div.ui-timepicker-oneLine dl dd,
.ui-timepicker-div.ui-timepicker-oneLine dl dd > div { display:inline-block; margin:0; }
.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_minute:before,
.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_second:before { content:':'; display:inline-block; }
.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_millisec:before,
.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_microsec:before { content:'.'; display:inline-block; }
.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_unit_hide,
.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_unit_hide:before{ display: none; }

View File

@@ -2268,3 +2268,14 @@ span.refresh-button {
}
.ui-datepicker-buttonpane, .ui-timepicker-div {
font-size: 9pt;
font-family: Tahoma, Verdana, Arial, Helvetica;
}
.date_format_tooltip td {
padding: 0.25em;
}

View File

@@ -1695,3 +1695,10 @@ span.refresh-button {
}
}
}
.ui-datepicker-buttonpane, .ui-timepicker-div {
font-size: 9pt;
font-family: Tahoma, Verdana, Arial, Helvetica;
}
.date_format_tooltip td {
padding: 0.25em;
}

View File

@@ -1048,7 +1048,6 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
'UI:FailedToApplyStimuli' => 'Akce se nezdařila.',
'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Upravuji %2$d objekt(ů) třídy %3$s',
'UI:CaseLogTypeYourTextHere' => 'Zadejte text zde:',
'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s',
'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:',
'UI:CaseLog:InitialValue' => 'Počáteční hodnota:',
'UI:AttemptingToSetASlaveAttribute_Name' => 'Pole %1$s není zapisovatelné, protože je spravováno synchronizací dat.',

View File

@@ -840,7 +840,6 @@ Ved tilknytningen til en trigger, bliver hver handling tildelt et "rækkefølge"
'UI:FailedToApplyStimuli' => 'Handlingen fejlede.',
'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Ændrer %2$d objekter af klasse %3$s',
'UI:CaseLogTypeYourTextHere' => 'Skriv din tekst her:',
'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s',
'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:',
'UI:CaseLog:InitialValue' => 'Begyndelses værdi:',
'UI:AttemptingToSetASlaveAttribute_Name' => 'Feltet %1$s er skrivebeskyttet, fordi det administreres af data synchronization. Værdien er ikke sat.',

View File

@@ -861,7 +861,6 @@ Wenn Aktionen mit Trigger verknüpft sind, bekommt jede Aktion eine Auftragsnumm
'UI:FailedToApplyStimuli' => 'Der Vorgang ist fehlgeschlagen.',
'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Modifiziere %2$d Objekte der Klasse %3$s',
'UI:CaseLogTypeYourTextHere' => 'Geben Sie Ihren Text hier ein:',
'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s',
'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:',
'UI:CaseLog:InitialValue' => 'Anfangswert:',
'UI:AttemptingToSetASlaveAttribute_Name' => 'Das Feld %1$s ist nicht schreibbar, weil es durch die Datensynchronisation geführt wird. Wert nicht gesetzt.',

View File

@@ -853,4 +853,7 @@ Dict::Add('EN US', 'English', 'English', array(
'Core:BulkExportLegacyExport' => 'Click here to access the legacy export.',
'Core:BulkExport:XLSXOptions' => 'Excel Options',
'Core:BulkExport:TextFormat' => 'Text fields containing some HTML markup',
'Core:BulkExport:DateTimeFormat' => 'Date and Time format',
'Core:BulkExport:DateTimeFormatDefault_Example' => 'Default format (%1$s), e.g. %2$s',
'Core:BulkExport:DateTimeFormatCustom_Format' => 'Custom format: %1$s',
));

View File

@@ -1053,7 +1053,6 @@ When associated with a trigger, each action is given an "order" number, specifyi
'UI:FailedToApplyStimuli' => 'The action has failed.',
'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Modifying %2$d objects of class %3$s',
'UI:CaseLogTypeYourTextHere' => 'Type your text here:',
'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s',
'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:',
'UI:CaseLog:InitialValue' => 'Initial value:',
'UI:AttemptingToSetASlaveAttribute_Name' => 'The field %1$s is not writable because it is mastered by the data synchronization. Value not set.',
@@ -1255,7 +1254,26 @@ When associated with a trigger, each action is given an "order" number, specifyi
'UI:CSVImportCreated_items' => 'Created: %1$d',
'UI:CSVImportModified_items' => 'Modified: %1$d',
'UI:CSVImportUnchanged_items' => 'Unchanged: %1$d',
'UI:CSVImport:DateAndTimeFormats' => 'Date and time format',
'UI:CSVImport:DefaultDateTimeFormat_Format_Example' => 'Default format: %1$s (e.g. %2$s)',
'UI:CSVImport:CustomDateTimeFormat' => 'Custom format: %1$s',
'UI:CSVImport:CustomDateTimeFormatTooltip' => 'Available placeholders:<table>
<tr><td>Y</td><td>year (4 digits, e.g. 2016)</td></tr>
<tr><td>y</td><td>year (2 digits, e.g. 16 for 2016)</td></tr>
<tr><td>m</td><td>month (2 digits, e.g. 01..12)</td></tr>
<tr><td>n</td><td>month (1 or 2 digits no leading zero, e.g. 1..12)</td></tr>
<tr><td>d</td><td>day (2 digits, e.g. 01..31)</td></tr>
<tr><td>j</td><td>day (1 or 2 digits no leading zero, e.g. 1..31)</td></tr>
<tr><td>H</td><td>hour (24 hour, 2 digits, e.g. 00..23)</td></tr>
<tr><td>h</td><td>hour (12 hour, 2 digits, e.g. 01..12)</td></tr>
<tr><td>G</td><td>hour (24 hour, 1 or 2 digits no leading zero, e.g. 0..23)</td></tr>
<tr><td>g</td><td>hour (12 hour, 1 or 2 digits no leading zero, e.g. 1..12)</td></tr>
<tr><td>a</td><td>hour, am or pm (lowercase)</td></tr>
<tr><td>A</td><td>hour, AM or PM (uppercase)</td></tr>
<tr><td>i</td><td>minutes (2 digits, e.g. 00..59)</td></tr>
<tr><td>s</td><td>seconds (2 digits, e.g. 00..59)</td></tr>
</table>',
'UI:Button:Remove' => 'Remove',
'UI:AddAnExisting_Class' => 'Add objects of type %1$s...',
'UI:SelectionOf_Class' => 'Selection of objects of type %1$s',

View File

@@ -1017,7 +1017,6 @@ Cuando se asocien con un disparador, cada acción recibe un número de "orden",
'UI:FailedToApplyStimuli' => 'La acción ha fallado.',
'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Modificando %2$d objetos de la clase %3$s',
'UI:CaseLogTypeYourTextHere' => 'Escriba su texto aquí:',
'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s',
'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:',
'UI:CaseLog:InitialValue' => 'Valor inicial:',
'UI:AttemptingToSetASlaveAttribute_Name' => 'El campo %1$s no es escribible porque es manejado por el sincronizador de datos. Valor no cambiado.',

View File

@@ -711,6 +711,9 @@ Opérateurs :<br/>
'Core:BulkExportLegacyExport' => 'Cliquez ici pour exécuter l\'ancienne version de l\'export.',
'Core:BulkExport:XLSXOptions' => 'Options du format Excel',
'Core:BulkExport:TextFormat' => 'Champs texte contenant des balises HTML',
'Core:BulkExport:DateTimeFormat' => 'Format de date et heure',
'Core:BulkExport:DateTimeFormatDefault_Example' => 'Format par défaut (%1$s), ex. %2$s',
'Core:BulkExport:DateTimeFormatCustom_Format' => 'Format spécial: %1$s',
'Core:DeletedObjectLabel' => '%1s (effacé)',
'Core:SyncSplitModeCLIOnly' => 'The synchronization can be executed in chunks only if run in mode CLI~~',
'Core:ExecProcess:Code1' => 'Wrong command or command finished with errors (e.g. wrong script name)~~',

View File

@@ -456,6 +456,25 @@ Dict::Add('FR FR', 'French', 'Français', array(
'UI:CSVImport:AlertMultipleMapping' => 'Veuillez vous assurer que chaque champ cible est sélectionné une seule fois.',
'UI:CSVImport:AlertNoSearchCriteria' => 'Veuillez choisir au moins une clef de recherche.',
'UI:CSVImport:Encoding' => 'Encodage des caractères',
'UI:CSVImport:DateAndTimeFormats' => 'Format de date et heure',
'UI:CSVImport:DefaultDateTimeFormat_Format_Example' => 'Format par défaut: %1$s (ex. %2$s)',
'UI:CSVImport:CustomDateTimeFormat' => 'Format spécial: %1$s',
'UI:CSVImport:CustomDateTimeFormatTooltip' => 'Codes de format:<table>
<tr><td>Y</td><td>année (sur 4 chiffres, ex. 2016)</td></tr>
<tr><td>y</td><td>année (sur 2 chiffres, ex. 16 pour 2016)</td></tr>
<tr><td>m</td><td>mois (sur 2 chiffres: 01..12)</td></tr>
<tr><td>n</td><td>month (sur 1 ou 2 chiffres sans le zero au début: 1..12)</td></tr>
<tr><td>d</td><td>jour (sur 2 chiffres: 01..31)</td></tr>
<tr><td>j</td><td>jour (sur 1 ou 2 chiffres sans le zero au début: 1..31)</td></tr>
<tr><td>H</td><td>heure (24 heures sur 2 chiffres: 00..23)</td></tr>
<tr><td>h</td><td>heure (12 heures sur 2 chiffres: 01..12)</td></tr>
<tr><td>G</td><td>heure (24 heures sur 1 ou 2 chiffres: 0..23)</td></tr>
<tr><td>g</td><td>heure (12 heures sur 1 ou 2 chiffres: 1..12)</td></tr>
<tr><td>a</td><td>am ou pm (en minuscules)</td></tr>
<tr><td>A</td><td>AM ou PM (en majuscules)</td></tr>
<tr><td>i</td><td>minutes (sur 2 chiffres: 00..59)</td></tr>
<tr><td>s</td><td>secondes (sur 2 chiffres: 00..59)</td></tr>
</table>',
'UI:CSVReport-Value-Modified' => 'Modifié',
'UI:CSVReport-Value-SetIssue' => 'Modification impossible - cause : %1$s',
@@ -897,7 +916,6 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
'UI:FailedToApplyStimuli' => 'L\'action a échoué',
'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Modification de %2$d objet(s) de type %3$s',
'UI:CaseLogTypeYourTextHere' => 'Nouvelle entrée ci-dessous:',
'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s',
'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:',
'UI:CaseLog:InitialValue' => 'Valeur initiale:',
'UI:AttemptingToSetASlaveAttribute_Name' => 'Le champ %1$s ne peut pas être modifié car il est géré par une synchronisation avec une source de données. Valeur ignorée.',

View File

@@ -760,7 +760,6 @@ Akció kiváltó okhoz rendelésekor kap egy sorszámot , amely meghatározza az
'UI:FailedToApplyStimuli' => 'A művelet sikertelen',
'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: %3$s osztály %2$d objketumainak módosítása',
'UI:CaseLogTypeYourTextHere' => 'Írjon ide:',
'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s',
'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:',
'UI:CaseLog:InitialValue' => 'Kezdeti érték:',
'UI:AttemptingToSetASlaveAttribute_Name' => '%1$s mező nem írható, mert a szinkronizációnál használt kulcs. Érték nem lett beállítva.',

View File

@@ -894,7 +894,6 @@ Quando è associata a un trigger, ad ogni azione è assegnato un numero "ordine"
'UI:FailedToApplyStimuli' => 'L\'azione non è riuscita.',
'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Modifica %2$d oggetti della classe %3$s~~',
'UI:CaseLogTypeYourTextHere' => 'Digitare il tuo testo qui:',
'UI:CaseLog:DateFormat' => 'A-m-g H:m:s',
'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:~~',
'UI:CaseLog:InitialValue' => 'Valore iniziale:',
'UI:AttemptingToSetASlaveAttribute_Name' => 'Il campo %1$s on è scrivibile, perché è comandato dalla sincronizzazione dei dati. Valore non impostato.',

View File

@@ -839,7 +839,6 @@ Dict::Add('JA JP', 'Japanese', '日本語', array(
'UI:FailedToApplyStimuli' => 'アクションは失敗しました。',
'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: クラス%3$sの%2$dオブジェクトを修正',
'UI:CaseLogTypeYourTextHere' => 'テキストを入力ください:',
'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s',
'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:',
'UI:CaseLog:InitialValue' => '初期値:',
'UI:AttemptingToSetASlaveAttribute_Name' => 'フィールド %1$s は、データの同期によってマスターリングされているため書き込み可能ではありません。値は設定されません。',

View File

@@ -1025,7 +1025,6 @@ Indien gekoppeld aan een Trigger, wordt aan elke actie een "orde" nummer gegeven
'UI:FailedToApplyStimuli' => 'De actie is mislukt.',
'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Bezig met het bewerken van %2$d objecten van klasse %3$s',
'UI:CaseLogTypeYourTextHere' => 'Typ uw text hier:',
'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s',
'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:',
'UI:CaseLog:InitialValue' => 'Initiële waarde:',
'UI:AttemptingToSetASlaveAttribute_Name' => 'Het veld %1$s is niet beschrijfbaar omdat het onderdeel is van de data synchronisatie. Waarde niet opgegeven',

View File

@@ -1016,7 +1016,6 @@ When associated with a trigger, each action is given an "order" number, specifyi
'UI:FailedToApplyStimuli' => 'A ação falhou.',
'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: modificando objetos %2$d da classe %3$s',
'UI:CaseLogTypeYourTextHere' => 'Digite seu texto aqui:',
'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s',
'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:',
'UI:CaseLog:InitialValue' => 'Valor inicial:',
'UI:AttemptingToSetASlaveAttribute_Name' => 'O campo %1$s não é editável, porque é originado pela sincronização de dados. Valor não definido.',

View File

@@ -1010,7 +1010,6 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
'UI:FailedToApplyStimuli' => 'The action has failed.~~',
'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Modifying %2$d objects of class %3$s~~',
'UI:CaseLogTypeYourTextHere' => 'Введите свой текст:',
'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s~~',
'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:~~',
'UI:CaseLog:InitialValue' => 'Initial value:~~',
'UI:AttemptingToSetASlaveAttribute_Name' => 'The field %1$s is not writable because it is mastered by the data synchronization. Value not set.~~',

View File

@@ -1041,7 +1041,6 @@ Tetikleme gerçekleştiriğinde işlemler tanımlanan sıra numarası ile gerçe
'UI:FailedToApplyStimuli' => 'The action has failed.~~',
'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Modifying %2$d objects of class %3$s~~',
'UI:CaseLogTypeYourTextHere' => 'Type your text here:~~',
'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s~~',
'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:~~',
'UI:CaseLog:InitialValue' => 'Initial value:~~',
'UI:AttemptingToSetASlaveAttribute_Name' => 'The field %1$s is not writable because it is mastered by the data synchronization. Value not set.~~',

View File

@@ -1040,7 +1040,6 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'UI:FailedToApplyStimuli' => 'The action has failed.~~',
'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Modifying %2$d objects of class %3$s~~',
'UI:CaseLogTypeYourTextHere' => 'Type your text here:~~',
'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s~~',
'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:~~',
'UI:CaseLog:InitialValue' => 'Initial value:~~',
'UI:AttemptingToSetASlaveAttribute_Name' => 'The field %1$s is not writable because it is mastered by the data synchronization. Value not set.~~',

File diff suppressed because one or more lines are too long

2269
js/jquery-ui-timepicker-addon.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -209,6 +209,11 @@ try
$bAdvanced = utils::ReadParam('advanced', 0);
$sEncoding = utils::ReadParam('encoding', 'UTF-8');
$sSynchroScope = utils::ReadParam('synchro_scope', '', false, 'raw_data');
$sDateTimeFormat = utils::ReadParam('date_time_format', 'default');
$sCustomDateTimeFormat = utils::ReadParam('custom_date_time_format', AttributeDateTime::GetFormat(), false, 'raw_data');
$sChosenDateFormat = ($sDateTimeFormat == 'default') ? AttributeDateTime::GetFormat() : $sCustomDateTimeFormat;
if (!empty($sSynchroScope))
{
$oSearch = DBObjectSearch::FromOQL($sSynchroScope);
@@ -319,7 +324,7 @@ try
array_keys($aSearchKeys),
empty($sSynchroScope) ? null : $sSynchroScope,
$aSynchroUpdate,
null, // date format
$sChosenDateFormat, // date format
true // localize
);
$oBulk->SetReportHtml();
@@ -498,6 +503,8 @@ try
$oPage->add('<input type="hidden" name="advanced" value="'.$bAdvanced.'"/>');
$oPage->add('<input type="hidden" name="encoding" value="'.$sEncoding.'"/>');
$oPage->add('<input type="hidden" name="synchro_scope" value="'.$sSynchroScope.'"/>');
$oPage->add('<input type="hidden" name="date_time_format" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>');
$oPage->add('<input type="hidden" name="custom_date_time_format" value="'.htmlentities($sCustomDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>');
if (!empty($sSynchroScope))
{
foreach($aSynchroUpdate as $sKey => $value)
@@ -658,7 +665,7 @@ function DoSubmit(bConfirm)
created: $sCreated,
modified: $sModified,
unchanged: $sUnchanged
}
},
type: 'donut'
},
legend: {
@@ -754,6 +761,8 @@ EOF
$sClassName = utils::ReadParam('class_name', '', false, 'class');
$bAdvanced = utils::ReadParam('advanced', 0);
$sEncoding = utils::ReadParam('encoding', 'UTF-8');
$sDateTimeFormat = utils::ReadParam('date_time_format', 'default');
$sCustomDateTimeFormat = utils::ReadParam('custom_date_time_format', AttributeDateTime::GetFormat(), false, 'raw_data');
$sSynchroScope = utils::ReadParam('synchro_scope', '', false, 'raw_data');
if (!empty($sSynchroScope))
@@ -788,6 +797,8 @@ EOF
$oPage->add('<input type="hidden" name="csvdata" value="'.htmlentities($sCSVData, ENT_QUOTES, 'UTF-8').'"/>');
$oPage->add('<input type="hidden" name="encoding" value="'.$sEncoding.'">');
$oPage->add('<input type="hidden" name="synchro_scope" value="'.$sSynchroScope.'">');
$oPage->add('<input type="hidden" name="date_time_format" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>');
$oPage->add('<input type="hidden" name="custom_date_time_format" value="'.htmlentities($sCustomDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>');
if (!empty($sSynchroScope))
{
foreach($aSynchroUpdate as $sKey => $value)
@@ -1092,6 +1103,8 @@ EOF
$bAdvanced = utils::ReadParam('advanced', 0);
$aFieldsMapping = utils::ReadParam('field', array(), false, 'raw_data');
$aSearchFields = utils::ReadParam('search_field', array(), false, 'field_name');
$sDateTimeFormat = utils::ReadParam('date_time_format', 'default');
$sCustomDateTimeFormat = utils::ReadParam('custom_date_time_format', AttributeDateTime::GetFormat(), false, 'raw_data');
// Create a truncated version of the data used for the fast preview
// Take about 20 lines of data... knowing that some lines may contain carriage returns
@@ -1134,7 +1147,7 @@ EOF
$oPage->add('<h2>'.Dict::S('UI:Title:CSVImportStep2').'</h2>');
$oPage->add('<div class="wizContainer">');
$oPage->add('<table><tr><td style="vertical-align:top;padding-right:50px;">');
$oPage->add('<table><tr><td style="vertical-align:top;padding-right:30px;">');
$oPage->add('<form enctype="multipart/form-data" id="wizForm" method="post" id="csv_options">');
$oPage->add('<h3>'.Dict::S('UI:CSVImport:SeparatorCharacter').'</h3>');
$oPage->add('<p><input type="radio" name="separator" value="," onClick="DoPreview()"'.IsChecked($sSeparator, ',').'/> '.Dict::S('UI:CSVImport:SeparatorComma+').'<br/>');
@@ -1142,16 +1155,20 @@ EOF
$oPage->add('<input type="radio" name="separator" value="tab" onClick="DoPreview()"'.IsChecked($sSeparator, "\t").'/> '.Dict::S('UI:CSVImport:SeparatorTab+').'<br/>');
$oPage->add('<input type="radio" name="separator" value="other" onClick="DoPreview()"'.IsChecked($sOtherSeparator, '', true).'/> '.Dict::S('UI:CSVImport:SeparatorOther').' <input type="text" size="3" maxlength="1" name="other_separator" id="other_separator" value="'.$sOtherSeparator.'" onClick="DoPreview()"/>');
$oPage->add('</p>');
$oPage->add('</td><td style="vertical-align:top;padding-right:50px;">');
$oPage->add('</td><td style="vertical-align:top;padding-right:30px;">');
$oPage->add('<h3>'.Dict::S('UI:CSVImport:TextQualifierCharacter').'</h3>');
$oPage->add('<p><input type="radio" name="text_qualifier" value="&#34;" onClick="DoPreview()"'.IsChecked($sTextQualifier, '"').'/> '.Dict::S('UI:CSVImport:QualifierDoubleQuote+').'<br/>');
$oPage->add('<input type="radio" name="text_qualifier" value="&#39;" onClick="DoPreview()"'.IsChecked($sTextQualifier, "'").'/> '.Dict::S('UI:CSVImport:QualifierSimpleQuote+').'<br/>');
$oPage->add('<input type="radio" name="text_qualifier" value="other" onClick="DoPreview()"'.IsChecked($sOtherTextQualifier, '', true).'/> '.Dict::S('UI:CSVImport:QualifierOther').' <input type="text" size="3" maxlength="1" name="other_qualifier" value="'.htmlentities($sOtherTextQualifier, ENT_QUOTES, 'UTF-8').'" onChange="DoPreview()"/>');
$oPage->add('</p>');
$oPage->add('</td><td style="vertical-align:top;">');
$oPage->add('</td><td style="vertical-align:top;padding-right:30px;">');
$oPage->add('<h3>'.Dict::S('UI:CSVImport:CommentsAndHeader').'</h3>');
$oPage->add('<p><input type="checkbox" name="header_line" id="box_header" value="1" onClick="DoPreview()"'.IsChecked($bHeaderLine, 1).'/> '.Dict::S('UI:CSVImport:TreatFirstLineAsHeader').'<p>');
$oPage->add('<p><input type="checkbox" name="box_skiplines" value="1" id="box_skiplines" onClick="DoPreview()"'.IsChecked($bBoxSkipLines, 1).'/> '.Dict::Format('UI:CSVImport:Skip_N_LinesAtTheBeginning', '<input type="text" size=2 name="nb_skipped_lines" id="nb_skipped_lines" onChange="DoPreview()" value="'.$iSkippedLines.'">').'<p>');
$oPage->add('</td><td style="vertical-align:top;">');
$oPage->add('<h3>'.Dict::S('UI:CSVImport:DateAndTimeFormats').'</h3>');
$oPage->add('<p><input type="radio" name="date_time_format" id="radio_date_time_std" value="default"'.IsChecked($sDateTimeFormat, 'default').'/> '.Dict::Format('UI:CSVImport:DefaultDateTimeFormat_Format_Example', htmlentities(AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8'), date(AttributeDateTime::GetFormat())).'<p>');
$oPage->add('<p><input type="radio" name="date_time_format" id="radio_date_time_custom" value="custom"'.IsChecked($sDateTimeFormat, 'custom').'/> '.Dict::Format('UI:CSVImport:CustomDateTimeFormat', '<input type="text" size="15" name="custom_date_time_format" id="custom_date_time_format" title="" value="'.htmlentities($sCustomDateTimeFormat, ENT_QUOTES, 'UTF-8').'">').'<p>');
$oPage->add('</td></tr></table>');
$oPage->add('<input type="hidden" name="csvdata_truncated" id="csvdata_truncated" value="'.htmlentities($sCSVDataTruncated, ENT_QUOTES, 'UTF-8').'"/>');
$oPage->add('<input type="hidden" name="csvdata" id="csvdata" value="'.htmlentities($sUTF8Data, ENT_QUOTES, 'UTF-8').'"/>');
@@ -1240,7 +1257,13 @@ EOF
}
EOF
);
$oPage->add_ready_script('DoPreview();');
$sJSTooltip = json_encode('<div class="date_format_tooltip">'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'</div>');
$oPage->add_ready_script(
<<<EOF
DoPreview();
$('#custom_date_time_format').tooltip({content: function() { return $sJSTooltip; } });
EOF
);
}
/**
@@ -1274,6 +1297,9 @@ EOF
$sClassName = utils::ReadParam('class_name', '');
$bAdvanced = utils::ReadParam('advanced', 0);
$sEncoding = utils::ReadParam('encoding', '');
$sDateTimeFormat = utils::ReadParam('date_time_format', 'default');
$sCustomDateTimeFormat = utils::ReadParam('custom_date_time_format', AttributeDateTime::GetFormat(), false, 'raw_data');
if ($sEncoding == '')
{
$sEncoding = MetaModel::GetConfig()->Get('csv_file_default_charset');
@@ -1335,6 +1361,8 @@ EOF
'<input type="hidden" name="operation" value="csv_data"/>'.
'<input type="hidden" name="separator" value="'.htmlentities($sSeparator, ENT_QUOTES, 'UTF-8').'"/>'.
'<input type="hidden" name="text_qualifier" value="'.htmlentities($sTextQualifier, ENT_QUOTES, 'UTF-8').'"/>'.
'<input type="hidden" name="date_time_format" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>'.
'<input type="hidden" name="custom_date_time_format" value="'.htmlentities($sCustomDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>'.
'<input type="hidden" name="header_line" value="'.$bHeaderLine.'"/>'.
'<input type="hidden" name="nb_skipped_lines" value="'.utils::ReadParam('nb_skipped_lines', '0').'"/>'.
'<input type="hidden" name="box_skiplines" value="'.utils::ReadParam('box_skiplines', '0').'"/>'.

View File

@@ -1798,4 +1798,91 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.</pre>]]></text>
</license>
<license>
<product>jQuery Timepicker addon</product>
<author>Trent Richardson</author>
<license_type>MIT</license_type>
<text><![CDATA[<pre>
Copyright (c) 2009 Trent Richardson, http://trentrichardson.com/Impromptu/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
</pre>]]></text>
</license>
<license>
<product>D3 js</product>
<author>Mike Bostock</author>
<license_type>BSD</license_type>
<text><![CDATA[<pre>
Copyright (c) 2010-2016, Michael Bostock
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* The name Michael Bostock may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>]]></text>
</license>
<license>
<product>C3 js</product>
<author>Masayuki Tanaka</author>
<license_type>MIT</license_type>
<text><![CDATA[<pre>
The MIT License (MIT)
Copyright (c) 2013 Masayuki Tanaka
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
</pre>]]></text>
</license>
</licenses>

View File

@@ -97,7 +97,7 @@ $aPageParams = array
'mandatory' => false,
'modes' => 'http,cli',
'default' => '',
'description' => 'Input date format (used both for dates and datetimes) - Examples: %Y-%m-%d, %d/%m/%Y (Europe) - no transformation is applied if the argument is omitted',
'description' => 'Input date format (used both for dates and datetimes) - Examples: Y-m-d, d/m/Y (Europe) - no transformation is applied if the argument is omitted. (note: old format specification using %Y %m %d is also supported for backward compatibility)',
),
'separator' => array
(
@@ -217,10 +217,10 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter)
function ChangeDateFormat($sProposedDate, $sDateFormat)
{
// Make sure this is a valid MySQL datetime
$iTime = utils::StringToTime($sProposedDate, $sDateFormat);
if ($iTime !== false)
$oDate = DateTime::createFromFormat($sDateFormat, $sProposedDate);
if ($oDate !== false)
{
$sDate = date('Y-m-d H:i:s', $iTime);
$sDate = $oDate->format(AttributeDateTime::GetInternalFormat());
return $sDate;
}
else
@@ -311,6 +311,10 @@ try
$sQualifier = ReadParam($oP, 'qualifier', 'raw_data');
$sCharSet = ReadParam($oP, 'charset', 'raw_data');
$sDateFormat = ReadParam($oP, 'date_format', 'raw_data');
if (strpos($sDateFormat, '%') !== false)
{
$sDateFormat = utils::DateTimeFormatToPHP($sDateFormat);
}
$sOutput = ReadParam($oP, 'output');
// $sReportLevel = ReadParam($oP, 'reportlevel');
$sSimulate = ReadParam($oP, 'simulate');

View File

@@ -1862,7 +1862,7 @@ class SynchroReplica extends DBObject implements iDisplay
{
$oStatLog->Inc($sStatsCode.'_updated');
}
$this->Set('info_last_modified', date('Y-m-d H:i:s'));
$this->Set('info_last_modified', date(AttributeDateTime::GetSQLFormat()));
}
else
{
@@ -1912,7 +1912,7 @@ class SynchroReplica extends DBObject implements iDisplay
$this->Set('status_dest_creator', true);
$this->Set('status_last_error', '');
$this->Set('status', 'synchronized');
$this->Set('info_creation_date', date('Y-m-d H:i:s'));
$this->Set('info_creation_date', date(AttributeDateTime::GetSQLFormat()));
$bCreated = true;
$oStatLog->AddTrace("Created (".implode(', ', $aValueTrace).")", $this);
@@ -1950,7 +1950,7 @@ class SynchroReplica extends DBObject implements iDisplay
}
$oDestObj->Set($sAttCode, $value);
}
$this->Set('info_last_modified', date('Y-m-d H:i:s'));
$this->Set('info_last_modified', date(AttributeDateTime::GetSQLFormat()));
$oDestObj->DBUpdateTracked($oChange);
$oStatLog->AddTrace("Replica marked as obsolete", $this);
$oStatLog->Inc('stats_nb_obj_obsoleted');

View File

@@ -4896,6 +4896,82 @@ class TestLinkSetRecording_1NAdd_Remove extends TestBizModel
}
}
class TestDateTimeFormats extends TestBizModel
{
static public function GetName() {return 'Check Date & Time formating and parsing';}
static public function GetDescription() {return 'Check the formating and parsing of dates for various formats';}
public function DoExecute()
{
$bRet = true;
$aTestFormats = array(
'French (short)' => 'd/m/Y H:i:s',
'French (short - no seconds)' => 'd/m/Y H:i',
'French (long)' => 'd/m/Y H\\h i\\m\\i\\n s\\s',
'English US' => 'm/d/Y H:i:s',
'English US (12 hours)' => 'm/d/Y h:i:s a',
'English US (12 hours, short)' => 'n/j/Y g:i:s a',
'English UK' => 'd/m/Y H:i:s',
'German' => 'd.m.Y H:i:s',
'SQL' => 'Y-m-d H:i:s',
);
// Valid date and times, all tests should pass
$aTestDates = array('2015-01-01 00:00:00', '2015-12-31 23:59:00', '2016-01-01 08:21:00', '2016-02-28 12:30:00', '2016-02-29 16:47:00', /*'2016-02-29 14:30:17'*/);
foreach($aTestFormats as $sDesc => $sFormat)
{
$this->ReportSuccess("Test of the '$sDesc' format: '$sFormat':");
AttributeDateTime::SetFormat($sFormat);
foreach($aTestDates as $sTestDate)
{
$oDate = new DateTime($sTestDate);
$sFormattedDate = AttributeDateTime::Format($oDate, AttributeDateTime::GetFormat());
$sParsedDate = AttributeDateTime::Parse($sFormattedDate, AttributeDateTime::GetFormat());
$sPattern = AttributeDateTime::GetRegExpr();
$bParseOk = ($sParsedDate == $sTestDate);
if (!$bParseOk)
{
$this->ReportError('Parsed ('.$sFormattedDate.') date different from initial date (difference of '.((int)$oParsedDate->format('U')- (int)$oDate->format('U')).'s)');
$bRet = false;
}
$bValidateOk = preg_match('/'.$sPattern.'/', $sFormattedDate);
if (!$bValidateOk)
{
$this->ReportError('Formatted date ('.$sFormattedDate.') does not match the validation pattern ('.$sPattern.')');
$bRet = false;
}
$this->ReportSuccess("Formatted date: $sFormattedDate - Parsing: ".($bParseOk ? 'Ok' : '<b>KO</b>')." - Validation: ".($bValidateOk ? 'Ok' : '<b>KO</b>'));
}
echo "</p>\n";
}
// Invalid date & time strings, all regexpr validation should fail
$aInvalidTestDates = array(
'SQL' => array('2015-13-01 00:00:00', '2015-12-51 23:59:00', '2016-01-01 +08:21:00', '2016-02-28 24:30:00', '2016-02-29 16:67:88'),
'French (short)' => array('01/01/20150 00:00:00', '01/01/20150 00:00:00', '01/13/2015 00:00:00', '01/01/2015 40:00:00', '01/01/2015 00:99:00'),
'English US (12 hours)' => array('13/01/2015 12:00:00 am', '12/33/2015 12:00:00 am', '12/23/215 12:00:00 am', '05/04/2016 16:00:00 am', '05/04/2016 10:00:00 ap'),
);
foreach($aInvalidTestDates as $sFormatName => $aDatesToParse)
{
$sFormat = $aTestFormats[$sFormatName];
AttributeDateTime::SetFormat($sFormat);
$this->ReportSuccess("Test of the '$sFormatName' format: '$sFormat':");
foreach($aDatesToParse as $sDate)
{
$sPattern = AttributeDateTime::GetRegExpr();
$bValidateOk = preg_match('/'.$sPattern.'/', $sDate);
if ($bValidateOk)
{
$this->ReportError('Formatted date ('.$sFormattedDate.') matches the validation pattern ('.$sPattern.') whereas it should not!');
$bRet = false;
}
$this->ReportSuccess("Formatted date: $sDate - Validation: ".($bValidateOk ? '<b>KO</n>' : 'rejected, Ok.'));
}
}
return $bRet;
}
}
class TestExecActions extends TestBizModel
{
static public function GetName()

View File

@@ -96,7 +96,7 @@ $aPageParams = array
'mandatory' => false,
'modes' => 'http,cli',
'default' => '',
'description' => 'Input date format (used both for dates and datetimes) - Examples: %Y-%m-%d, %d/%m/%Y (Europe) - no transformation is applied if the argument is omitted',
'description' => 'Input date format (used both for dates and datetimes) - Examples: Y-m-d H:i:s, d/m/Y H:i:s (Europe) - no transformation is applied if the argument is omitted. (note: old format specification using %Y %m %d is also supported for backward compatibility)',
),
'separator' => array
(
@@ -287,6 +287,10 @@ try
$sQualifier = ReadParam($oP, 'qualifier', 'raw_data');
$sCharSet = ReadParam($oP, 'charset', 'raw_data');
$sDateFormat = ReadParam($oP, 'date_format', 'raw_data');
if (strpos($sDateFormat, '%') !== false)
{
$sDateFormat = utils::DateTimeFormatToPHP($sDateFormat);
}
$sOutput = ReadParam($oP, 'output', 'string');
$sReconcKeys = ReadParam($oP, 'reconciliationkeys', 'raw_data');
$sSimulate = ReadParam($oP, 'simulate');