diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php
index 05bf0833b..97f88356d 100644
--- a/application/cmdbabstract.class.inc.php
+++ b/application/cmdbabstract.class.inc.php
@@ -1068,6 +1068,22 @@ EOF
$aEventsList[] ='change';
$sHTMLValue = " {$sValidationField}";
break;
+
+ case 'Duration':
+ $aEventsList[] ='validate';
+ $aEventsList[] ='change';
+ $oPage->add_ready_script("$('#{$iId}_d').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });");
+ $oPage->add_ready_script("$('#{$iId}_h').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });");
+ $oPage->add_ready_script("$('#{$iId}_m').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });");
+ $oPage->add_ready_script("$('#{$iId}_s').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });");
+ $aVal = AttributeDuration::SplitDuration($value);
+ $sDays = "";
+ $sHours = "";
+ $sMinutes = "";
+ $sSeconds = "";
+ $sHidden = "";
+ $sHTMLValue = Dict::Format('UI:DurationForm_Days_Hours_Minutes_Seconds', $sDays, $sHours, $sMinutes, $sSeconds).$sHidden." ".$sValidationField;
+ break;
case 'Password':
$aEventsList[] ='validate';
@@ -1170,7 +1186,7 @@ EOF
}
break;
}
- $sPattern = addslashes($oAttDef->GetValidationPattern()); //'^([0-9]+)$';
+ $sPattern = addslashes($oAttDef->GetValidationPattern()); //'^([0-9]+)$';
if (!empty($aEventsList))
{
$sNullValue = $oAttDef->GetNullValue();
@@ -1768,6 +1784,24 @@ EOF
$this->Set($sAttCode, $rawValue);
}
}
+ elseif ($oAttDef->GetEditClass() == 'Duration')
+ {
+ $rawValue = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null);
+ if (!is_array($rawValue))
+ {
+ $iValue = null;
+ }
+ else
+ {
+ $iValue = (((24*$rawValue['d'])+$rawValue['h'])*60 +$rawValue['m'])*60 + $rawValue['s'];
+ }
+ $this->Set($sAttCode, $iValue);
+ $previousValue = $this->Get($sAttCode);
+ if ($previousValue !== $iValue)
+ {
+ $this->Set($sAttCode, $iValue);
+ }
+ }
else
{
$rawValue = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null);
diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php
index 13f31769f..be8de781f 100644
--- a/core/attributedef.class.inc.php
+++ b/core/attributedef.class.inc.php
@@ -1619,6 +1619,82 @@ class AttributeDateTime extends AttributeDBField
}
}
+/**
+ * Store a duration as a number of seconds
+ *
+ * @package iTopORM
+ */
+class AttributeDuration extends AttributeInteger
+{
+ public function GetEditClass() {return "Duration";}
+ protected function GetSQLCol() {return "INT(11) UNSIGNED";}
+
+ public function GetNullValue() {return '0';}
+ public function GetDefaultValue()
+ {
+ return 0;
+ }
+
+ public function MakeRealValue($proposedValue)
+ {
+ if (is_null($proposedValue)) return null;
+ if (!is_numeric($proposedValue)) return null;
+ if ( ((int)$proposedValue) < 0) return null;
+
+ return (int)$proposedValue;
+ }
+
+ public function ScalarToSQL($value)
+ {
+ if (is_null($value))
+ {
+ return null;
+ }
+ return $value;
+ }
+
+ public function GetAsHTML($value)
+ {
+ return Str::pure2html(self::FormatDuration($value));
+ }
+
+ static function FormatDuration($duration)
+ {
+ $aDuration = self::SplitDuration($duration);
+ $sResult = '';
+
+ if ($duration < 60)
+ {
+ // Less than 1 min
+ $sResult = Dict::Format('Core:Duration_Seconds', $aDuration['seconds']);
+ }
+ else if ($duration < 3600)
+ {
+ // less than 1 hour, display it in minutes/seconds
+ $sResult = Dict::Format('Core:Duration_Minutes_Seconds', $aDuration['minutes'], $aDuration['seconds']);
+ }
+ else if ($duration < 86400)
+ {
+ // Less than 1 day, display it in hours/minutes/seconds
+ $sResult = Dict::Format('Core:Duration_Hours_Minutes_Seconds', $aDuration['hours'], $aDuration['minutes'], $aDuration['seconds']);
+ }
+ else
+ {
+ // more than 1 day, display it in days/hours/minutes/seconds
+ $sResult = Dict::Format('Core:Duration_Days_Hours_Minutes_Seconds', $aDuration['days'], $aDuration['hours'], $aDuration['minutes'], $aDuration['seconds']);
+ }
+ return $sResult;
+ }
+
+ static function SplitDuration($duration)
+ {
+ $days = floor($duration / 86400);
+ $hours = floor(($duration - (86400*$days)) / 3600);
+ $minutes = floor(($duration - (86400*$days + 3600*$hours)) / 60);
+ $seconds = ($duration % 60); // modulo
+ return array( 'days' => $days, 'hours' => $hours, 'minutes' => $minutes, 'seconds' => $seconds );
+ }
+}
/**
* Map a date+time column to an attribute
*
diff --git a/core/dbobject.class.php b/core/dbobject.class.php
index ae7bb4fe5..97f696d2d 100644
--- a/core/dbobject.class.php
+++ b/core/dbobject.class.php
@@ -349,7 +349,12 @@ abstract class DBObject
// #@# non-scalar attributes.... handle that differently
$this->Reload();
}
- return $this->m_aCurrValues[$sAttCode];
+ $value = $this->m_aCurrValues[$sAttCode];
+ if ($value instanceof DBObjectSet)
+ {
+ $value->Rewind();
+ }
+ return $value;
}
public function GetOriginal($sAttCode)
diff --git a/dictionaries/dictionary.itop.core.php b/dictionaries/dictionary.itop.core.php
index d53457694..5e9d7a2a3 100644
--- a/dictionaries/dictionary.itop.core.php
+++ b/dictionaries/dictionary.itop.core.php
@@ -537,5 +537,19 @@ Dict::Add('EN US', 'English', 'English', array(
'Core:SynchroLogTitle' => '%1$s - %2$s',
'Core:Synchro:Nb_Replica' => 'Replica processed: %1$s',
'Core:Synchro:Nb_Class:Objects' => '%1$s: %2$s',
+ 'Class:SynchroDataSource/Error:AtLeastOneReconciliationKeyMustBeSpecified' => 'At Least one reconciliation key must be specified.',
+ 'Class:SynchroDataSource/Error:DeleteRetentionDurationMustBeSpecified' => 'A delete retention period must be specified, since objects are to be deleted after being marked as obsolete',
+ 'Class:SynchroDataSource/Error:DeletePolicyUpdateMustBeSpecified' => 'Obsolete objects are to be updated, but no update is specified.',
));
+
+//
+// Attribute Duration
+//
+Dict::Add('EN US', 'English', 'English', array(
+ 'Core:Duration_Seconds' => '%1$ds',
+ 'Core:Duration_Minutes_Seconds' =>'%1$dmin %2$ds',
+ 'Core:Duration_Hours_Minutes_Seconds' => '%1$dh %2$dmin %3$ds',
+ 'Core:Duration_Days_Hours_Minutes_Seconds' => '%1$sd %2$dh %3$dmin %4$ds',
+));
+
?>
diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php
index ef6643d24..d57801380 100644
--- a/dictionaries/dictionary.itop.ui.php
+++ b/dictionaries/dictionary.itop.ui.php
@@ -400,7 +400,7 @@ Dict::Add('EN US', 'English', 'English', array(
'UI:HistoryTab' => 'History',
'UI:NotificationsTab' => 'Notifications',
'UI:History:BulkImports' => 'History',
- 'UI:History:BulkImports+' => 'List of CSV imports (last first)',
+ 'UI:History:BulkImports+' => 'List of CSV imports (latest import first)',
'UI:History:BulkImportDetails' => 'Changes resulting from the CSV import performed on %1$s (by %2$s)',
'UI:History:Date' => 'Date',
'UI:History:Date+' => 'Date of the change',
@@ -879,9 +879,7 @@ When associated with a trigger, each action is given an "order" number, specifyi
'Portal:AddAttachment' => ' Add Attachment ',
'Portal:RemoveAttachment' => ' Remove Attachment ',
'Portal:Attachment_No_To_Ticket_Name' => 'Attachment #%1$d to %2$s (%3$s)',
- 'Enum:Undefined' => 'Undefined',
+ 'Enum:Undefined' => 'Undefined',
+ 'UI:DurationForm_Days_Hours_Minutes_Seconds' => '%1$s Days %2$s Hours %3$s Minutes %4$s Seconds',
));
-
-
-
?>
diff --git a/js/forms-json-utils.js b/js/forms-json-utils.js
index f8f939637..d7c5cedf7 100644
--- a/js/forms-json-utils.js
+++ b/js/forms-json-utils.js
@@ -292,6 +292,20 @@ function ValidatePasswordField(id, sFormId)
return true;
}
+// Manage a 'duration' field
+function UpdateDuration(iId)
+{
+ var iDays = parseInt($('#'+iId+'_d').val(), 10);
+ var iHours = parseInt($('#'+iId+'_h').val(), 10);
+ var iMinutes = parseInt($('#'+iId+'_m').val(), 10);
+ var iSeconds = parseInt($('#'+iId+'_s').val(), 10);
+
+ var iDuration = (((iDays*24)+ iHours)*60+ iMinutes)*60 + iSeconds;
+ $('#'+iId).val(iDuration);
+ $('#'+iId).trigger('change');
+ return true;
+}
+
// Called when filling an autocomplete field
function OnAutoComplete(id, event, data, formatted)
{
diff --git a/synchro/synchrodatasource.class.inc.php b/synchro/synchrodatasource.class.inc.php
index aa126e321..c5d888449 100644
--- a/synchro/synchrodatasource.class.inc.php
+++ b/synchro/synchrodatasource.class.inc.php
@@ -57,8 +57,8 @@ class SynchroDataSource extends cmdbAbstractObject
//MetaModel::Init_AddAttribute(new AttributeDateTime("last_synchro_date", array("allowed_values"=>null, "sql"=>"last_synchro_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
- // Format: '1 hour', '2 weeks', '3 hoursABCDEF'... Cf DateTime->Modify()
- MetaModel::Init_AddAttribute(new AttributeString("full_load_periodicity", array("allowed_values"=>null, "sql"=>"full_load_periodicity", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+ // Format: seconds (int)
+ MetaModel::Init_AddAttribute(new AttributeDuration("full_load_periodicity", array("allowed_values"=>null, "sql"=>"full_load_periodicity", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
// MetaModel::Init_AddAttribute(new AttributeString("reconciliation_list", array("allowed_values"=>null, "sql"=>"reconciliation_list", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("reconciliation_policy", array("allowed_values"=>new ValueSetEnum('use_primary_key,use_attributes'), "sql"=>"reconciliation_policy", "default_value"=>"use_attributes", "is_null_allowed"=>false, "depends_on"=>array())));
@@ -69,8 +69,8 @@ class SynchroDataSource extends cmdbAbstractObject
MetaModel::Init_AddAttribute(new AttributeEnum("delete_policy", array("allowed_values"=>new ValueSetEnum('ignore,delete,update,update_then_delete'), "sql"=>"delete_policy", "default_value"=>"ignore", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("delete_policy_update", array("allowed_values"=>null, "sql"=>"delete_policy_update", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
- // Format: '1 hour', '2 weeks', '3 hoursABCDEF'... Cf DateTime->Modify()
- MetaModel::Init_AddAttribute(new AttributeString("delete_policy_retention", array("allowed_values"=>null, "sql"=>"delete_policy_retention", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
+ // Format: seconds (unsigned int)
+ MetaModel::Init_AddAttribute(new AttributeDuration("delete_policy_retention", array("allowed_values"=>null, "sql"=>"delete_policy_retention", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLinkedSet("attribute_list", array("linked_class"=>"SynchroAttribute", "ext_key_to_me"=>"sync_source_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLinkedSet("status_list", array("linked_class"=>"SynchroLog", "ext_key_to_me"=>"sync_source_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array())));
@@ -90,6 +90,7 @@ class SynchroDataSource extends cmdbAbstractObject
$oPage->SetCurrentTab(Dict::S('Core:SynchroAttributes'));
$oAttributeSet = $this->Get('attribute_list');
$aAttributes = array();
+
while($oAttribute = $oAttributeSet->Fetch())
{
$aAttributes[$oAttribute->Get('attcode')] = $oAttribute;
@@ -351,6 +352,47 @@ EOF
}
$this->Set('attribute_list', $oAttributeSet);
}
+
+ /*
+ * Overload the standard behavior
+ */
+ public function DoCheckToWrite()
+ {
+ parent::DoCheckToWrite();
+
+ // Check that there is at least one reconciliation key defined
+ if ($this->Get('reconciliation_policy') == 'use_attributes')
+ {
+ $oSet = $this->Get('attribute_list');
+ $oSynchroAttributeList = $oSet->ToArray();
+ $bReconciliationKey = false;
+ foreach($oSynchroAttributeList as $oSynchroAttribute)
+ {
+ if ($oSynchroAttribute->Get('reconcile') == 1)
+ {
+ $bReconciliationKey = true; // At least one key is defined
+ }
+ }
+ if (!$bReconciliationKey)
+ {
+ $this->m_aCheckIssues[] = Dict::Format('Class:SynchroDataSource/Error:AtLeastOneReconciliationKeyMustBeSpecified');
+ }
+ }
+
+ // If 'update_then_delete' is specified there must be a delete_retention_period
+ if (($this->Get('delete_policy') == 'update_then_delete') && ($this->Get('delete_policy_retention') == 0))
+ {
+ $this->m_aCheckIssues[] = Dict::Format('Class:SynchroDataSource/Error:DeleteRetentionDurationMustBeSpecified');
+ }
+
+ // If update is specified, then something to update must be defined
+ if ((($this->Get('delete_policy') == 'update_then_delete') || ($this->Get('delete_policy') == 'update'))
+ && ($this->Get('delete_policy_update') == ''))
+ {
+ $this->m_aCheckIssues[] = Dict::Format('Class:SynchroDataSource/Error:DeletePolicyUpdateMustBeSpecified');
+ }
+ }
+
public function GetTargetClass()
{
return $this->Get('scope_class');
@@ -562,22 +604,11 @@ EOF
{
// No previous import known, use the full_load_periodicity value... and the current date
$oLastFullLoadStartDate = new DateTime(); // Now
- // TO DO: how do we support localization here ??
- $sLoadPeriodicity = trim($this->Get('full_load_periodicity'));
- if (strlen($sLoadPeriodicity) > 0)
+ $iLoadPeriodicity = $this->Get('full_load_periodicity'); // Duration in seconds
+ if ($iLoadPeriodicity > 0)
{
- $sInterval = '-'.$sLoadPeriodicity;
- // Note: the PHP doc states that Modify return FALSE in case of error
- // but, this is actually NOT the case
- // Therefore, I do compare before and after, considering that the
- // format is incorrect when the datetime remains unchanged
- $sBefore = $oLastFullLoadStartDate->Format('Y-m-d H:i:s');
+ $sInterval = "-$iLoadPeriodicity seconds";
$oLastFullLoadStartDate->Modify($sInterval);
- $sAfter = $oLastFullLoadStartDate->Format('Y-m-d H:i:s');
- if ($sBefore == $sAfter)
- {
- throw new SynchroExceptionNotStarted("Data exchange: Wrong interval specification", array('interval' => $sInterval, 'source_id' => $this->GetKey()));
- }
}
}
$sLimitDate = $oLastFullLoadStartDate->Format('Y-m-d H:i:s');
@@ -689,21 +720,11 @@ EOF
if ($sDeletePolicy == 'update_then_delete')
{
$oDeletionDate = $oLastFullLoadStartDate;
- $sDeleteRetention = trim($this->Get('delete_policy_retention')); // MUST NOT BE NULL
- if (strlen($sDeleteRetention) > 0)
+ $iDeleteRetention = $this->Get('delete_policy_retention'); // Duration in seconds
+ if ($iDeleteRetention > 0)
{
- $sInterval = '-'.$sDeleteRetention;
- // Note: the PHP doc states that Modify return FALSE in case of error
- // but, this is actually NOT the case
- // Therefore, I do compare before and after, considering that the
- // format is incorrect when the datetime remains unchanged
- $sBefore = $oDeletionDate->Format('Y-m-d H:i:s');
+ $sInterval = "-$iDeleteRetention seconds";
$oDeletionDate->Modify($sInterval);
- $sAfter = $oDeletionDate->Format('Y-m-d H:i:s');
- if ($sBefore == $sAfter)
- {
- throw new SynchroExceptionNotStarted("Data exchange: Wrong interval specification", array('interval' => $sInterval, 'source_id' => $this->GetKey()));
- }
}
$sDeletionDate = $oDeletionDate->Format('Y-m-d H:i:s');
$aTraces[] = "Deletion date: $sDeletionDate";
@@ -982,7 +1003,7 @@ class SynchroReplica extends DBObject
MetaModel::Init_AddAttribute(new AttributeExternalKey("sync_source_id", array("targetclass"=>"SynchroDataSource", "jointype"=> "", "allowed_values"=>null, "sql"=>"sync_source_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("dest_id", array("allowed_values"=>null, "sql"=>"dest_id", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array())));
- MetaModel::Init_AddAttribute(new AttributeClass("dest_class", array("class_category"=>"bizmodel", "more_values"=>"", "sql"=>"dest_class", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
+ MetaModel::Init_AddAttribute(new AttributeClass("dest_class", array("class_category"=>"bizmodel", "more_values"=>"", "sql"=>"dest_class", "default_value"=>'Organization', "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeDateTime("status_last_seen", array("allowed_values"=>null, "sql"=>"status_last_seen", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('new,synchronized,modified,orphan,obsolete'), "sql"=>"status", "default_value"=>"new", "is_null_allowed"=>false, "depends_on"=>array())));