From f3f2eb5c797ec40d331798e134b3cd73b5bfd4ea Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 1 Mar 2011 09:01:44 +0000 Subject: [PATCH] Synchro Data Sources Implementation on going... SVN:trunk[1100] --- application/cmdbabstract.class.inc.php | 36 ++++++++++- core/attributedef.class.inc.php | 76 ++++++++++++++++++++++ core/dbobject.class.php | 7 +- dictionaries/dictionary.itop.core.php | 14 ++++ dictionaries/dictionary.itop.ui.php | 8 +-- js/forms-json-utils.js | 14 ++++ synchro/synchrodatasource.class.inc.php | 85 +++++++++++++++---------- 7 files changed, 201 insertions(+), 39 deletions(-) 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())));