From 489be44b90b409ca14cdc6cf163bde0077abb77f Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 21 Dec 2011 13:45:07 +0000 Subject: [PATCH] #489 Data synchro: reintegrated the latest improvements from trunk. SVN:1.2[1740] --- application/utils.inc.php | 116 ++- core/config.class.inc.php | 8 + core/dbobject.class.php | 4 +- core/kpi.class.inc.php | 10 + core/metamodel.class.php | 5 +- dictionaries/de.dictionary.itop.core.php | 2 +- dictionaries/dictionary.itop.core.php | 5 +- dictionaries/fr.dictionary.itop.core.php | 2 +- dictionaries/hu.dictionary.itop.core.php | 2 +- dictionaries/it.dictionary.itop.core.php | 964 +++++++++---------- dictionaries/ja.dictionary.itop.ui.php | 8 +- dictionaries/pt_br.dictionary.itop.core.php | 2 +- synchro/priv_sync_chunk.php | 135 +++ synchro/synchro_exec.php | 5 +- synchro/synchro_import.php | 10 +- synchro/synchrodatasource.class.inc.php | 990 +++++++++++++------- test/testlist.inc.php | 4 +- 17 files changed, 1448 insertions(+), 824 deletions(-) create mode 100644 synchro/priv_sync_chunk.php diff --git a/application/utils.inc.php b/application/utils.inc.php index bdd7fce90..a91996b3f 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -46,6 +46,7 @@ class utils // Parameters loaded from a file, parameters of the page/command line still have precedence private static $m_aParamsFromFile = null; + private static $m_aParamSource = array(); protected static function LoadParamFile($sParamFile) { @@ -82,6 +83,7 @@ class utils $sParam = $aMatches[1]; $value = trim($aMatches[2]); self::$m_aParamsFromFile[$sParam] = $value; + self::$m_aParamSource[$sParam] = $sParamFile; } } } @@ -99,6 +101,25 @@ class utils } } + /** + * Return the source file from which the parameter has been found, + * usefull when it comes to pass user credential to a process executed + * in the background + * @param $sName Parameter name + * @return The file name if any, or null + */ + public static function GetParamSourceFile($sName) + { + if (array_key_exists($sName, self::$m_aParamSource)) + { + return self::$m_aParamSource[$sName]; + } + else + { + return null; + } + } + public static function IsModeCLI() { $sSAPIName = php_sapi_name(); @@ -152,10 +173,18 @@ class utils public static function Sanitize($value, $defaultValue, $sSanitizationFilter) { - $retValue = self::Sanitize_Internal($value, $sSanitizationFilter); - if ($retValue === false) + if ($value === $defaultValue) { - $retValue = $defaultValue; + // Preserve the real default value (can be used to detect missing mandatory parameters) + $retValue = $value; + } + else + { + $retValue = self::Sanitize_Internal($value, $sSanitizationFilter); + if ($retValue === false) + { + $retValue = $defaultValue; + } } return $retValue; } @@ -601,5 +630,86 @@ class utils } echo "

".print_r($aLightTrace, true)."

\n"; } + + /** + * Execute the given iTop PHP script, passing it the current credentials + * Only CLI mode is supported, because of the need to hand the credentials over to the next process + * Throws an exception if the execution fails or could not be attempted (config issue) + * @param string $sScript Name and relative path to the file (relative to the iTop root dir) + * @param hash $aArguments Associative array of 'arg' => 'value' + * @return array(iCode, array(output lines)) + */ + /** + */ + static function ExecITopScript($sScriptName, $aArguments) + { + $aDisabled = explode(', ', ini_get('disable_functions')); + if (in_array('exec', $aDisabled)) + { + throw new Exception("The PHP exec() function has been disabled on this server"); + } + + $sPHPExec = trim(MetaModel::GetConfig()->Get('php_path')); + if (strlen($sPHPExec) == 0) + { + throw new Exception("The path to php must not be empty. Please set a value for 'php_path' in your configuration file."); + } + + $sAuthUser = self::ReadParam('auth_user', '', 'raw_data'); + $sAuthPwd = self::ReadParam('auth_pwd', '', 'raw_data'); + $sParamFile = self::GetParamSourceFile('auth_user'); + if (is_null($sParamFile)) + { + $aArguments['auth_user'] = $sAuthUser; + $aArguments['auth_pwd'] = $sAuthPwd; + } + else + { + $aArguments['param_file'] = $sParamFile; + } + + $aArgs = array(); + foreach($aArguments as $sName => $value) + { + // Note: See comment from the 23-Apr-2004 03:30 in the PHP documentation + // It suggests to rely on pctnl_* function instead of using escapeshellargs + $aArgs[] = "--$sName=".escapeshellarg($value); + } + $sArgs = implode(' ', $aArgs); + + $sScript = realpath(APPROOT.$sScriptName); + if (!file_exists($sScript)) + { + throw new Exception("Could not find the script file '$sScriptName' from the directory '".APPROOT."'"); + } + + $sCommand = '"'.$sPHPExec.'" '.escapeshellarg($sScript).' -- '.$sArgs; + + if (version_compare(phpversion(), '5.3.0', '<')) + { + if (substr(PHP_OS,0,3) == 'WIN') + { + // Under Windows, and for PHP 5.2.x, the whole command has to be quoted + // Cf PHP doc: http://php.net/manual/fr/function.exec.php, comment from the 27-Dec-2010 + $sCommand = '"'.$sCommand.'"'; + } + } + + $sLastLine = exec($sCommand, $aOutput, $iRes); + if ($iRes == 1) + { + throw new Exception(Dict::S('Core:ExecProcess:Code1')." - ".$sCommand); + } + elseif ($iRes == 255) + { + $sErrors = implode("\n", $aOutput); + throw new Exception(Dict::S('Core:ExecProcess:Code255')." - ".$sCommand.":\n".$sErrors); + } + + //$aOutput[] = $sCommand; + return array($iRes, $aOutput); + } + + } ?> diff --git a/core/config.class.inc.php b/core/config.class.inc.php index c3731ab71..fda3f9c98 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -124,6 +124,14 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ), + 'php_path' => array( + 'type' => 'string', + 'description' => 'Path to the php executable in CLI mode', + 'default' => 'php', + 'value' => 'php', + 'source_of_value' => '', + 'show_in_conf_sample' => true, + ), 'session_name' => array( 'type' => 'string', 'description' => 'The name of the cookie used to store the PHP session id', diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 9d144d860..961ab96d5 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -932,7 +932,7 @@ abstract class DBObject $oDeletionPlan->AddToDelete($oReplica, DEL_SILENT); - if ($oDataSource->GetKey() == SynchroDataSource::GetCurrentTaskId()) + if ($oDataSource->GetKey() == SynchroExecution::GetCurrentTaskId()) { // The current task has the right to delete the object continue; @@ -1862,7 +1862,7 @@ abstract class DBObject $oSet = $this->GetMasterReplica(); while($aData = $oSet->FetchAssoc()) { - if ($aData['datasource']->GetKey() == SynchroDataSource::GetCurrentTaskId()) + if ($aData['datasource']->GetKey() == SynchroExecution::GetCurrentTaskId()) { // Ignore the current task (check to write => ok) continue; diff --git a/core/kpi.class.inc.php b/core/kpi.class.inc.php index a9c129941..eed8e380f 100644 --- a/core/kpi.class.inc.php +++ b/core/kpi.class.inc.php @@ -191,6 +191,16 @@ class ExecutionKPI return $output[1] * 1024; } } + + static public function memory_get_peak_usage($bRealUsage = false) + { + if (function_exists('memory_get_peak_usage')) + { + return memory_get_peak_usage($bRealUsage); + } + // PHP > 5.2.1 - this verb depends on a compilation option + return 0; + } } class ApplicationStartupKPI extends ExecutionKPI diff --git a/core/metamodel.class.php b/core/metamodel.class.php index ae40074b5..bca844a07 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1999,7 +1999,10 @@ if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass])) $aOrderSpec = array(); foreach ($aOrderBy as $sFieldAlias => $bAscending) { - MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sFieldAlias, self::GetAttributesList($oFilter->GetFirstJoinedClass())); + if ($sFieldAlias != 'id') + { + MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sFieldAlias, self::GetAttributesList($oFilter->GetFirstJoinedClass())); + } if (!is_bool($bAscending)) { throw new CoreException("Wrong direction in ORDER BY spec, found '$bAscending' and expecting a boolean value"); diff --git a/dictionaries/de.dictionary.itop.core.php b/dictionaries/de.dictionary.itop.core.php index bbf4fbada..4b6f86b1e 100644 --- a/dictionaries/de.dictionary.itop.core.php +++ b/dictionaries/de.dictionary.itop.core.php @@ -537,7 +537,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'Core:Synchro:History' => 'Verlauf der Synchronisation', 'Core:Synchro:NeverRun' => 'Synchronisation noch nicht erfolgt. Kein Protokoll verfügbar.', 'Core:Synchro:SynchroEndedOn_Date' => 'Die letzte Synchronisation endete um %1$s.', - 'Core:Synchro:SynchroRunningStartedOn_Date' => 'Die Synchronisation, die um $1$s gestartet wurde, läuft noch ...', + 'Core:Synchro:SynchroRunningStartedOn_Date' => 'Die Synchronisation, die um %1$s gestartet wurde, läuft noch ...', 'Menu:DataSources' => 'Datenquellen für die Synchronisation', 'Menu:DataSources+' => 'Alle Datenquellen für die Synchronisation', 'Core:Synchro:label_repl_ignored' => 'Ignoriert (%1$s)', diff --git a/dictionaries/dictionary.itop.core.php b/dictionaries/dictionary.itop.core.php index adc9d23b9..160eacdc6 100644 --- a/dictionaries/dictionary.itop.core.php +++ b/dictionaries/dictionary.itop.core.php @@ -587,7 +587,7 @@ Dict::Add('EN US', 'English', 'English', array( 'Core:Synchro:History' => 'Synchronization History', 'Core:Synchro:NeverRun' => 'This synchro was never run. No log yet.', 'Core:Synchro:SynchroEndedOn_Date' => 'The latest synchronization ended on %1$s.', - 'Core:Synchro:SynchroRunningStartedOn_Date' => 'The synchronization started on $1$s is still running...', + 'Core:Synchro:SynchroRunningStartedOn_Date' => 'The synchronization started on %1$s is still running...', 'Menu:DataSources' => 'Synchronization Data Sources', 'Menu:DataSources+' => 'All Synchronization Data Sources', 'Core:Synchro:label_repl_ignored' => 'Ignored (%1$s)', @@ -639,6 +639,7 @@ Dict::Add('EN US', 'English', 'English', array( 'Core:SyncDataSourceObsolete' => 'The data source is marked as obsolete. Operation cancelled.', 'Core:SyncDataSourceAccessRestriction' => 'Only adminstrators or the user specified in the data source can execute this operation. Operation cancelled.', 'Core:SyncTooManyMissingReplicas' => 'All records have been untouched for some time (all of the objects could be deleted). Please check that the process that writes into the synchronization table is still running. Operation cancelled.', + 'Core:SyncSplitModeCLIOnly' => 'The synchronization can be executed in chunks only if run in mode CLI', 'Core:Synchro:ListReplicas_AllReplicas_Errors_Warnings' => '%1$s replicas, %2$s error(s), %3$s warning(s).', 'Core:SynchroReplica:TargetObject' => 'Synchronized Object: %1$s', 'Class:AsyncSendEmail' => 'Email (asynchronous)', @@ -733,6 +734,8 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:appUserPreferences' => 'User Preferences', 'Class:appUserPreferences/Attribute:userid' => 'User', 'Class:appUserPreferences/Attribute:preferences' => 'Prefs', + 'Core:ExecProcess:Code1' => 'Wrong command or command finished with errors (e.g. wrong script name)', + 'Core:ExecProcess:Code255' => 'PHP Error (parsing, or runtime)', )); // diff --git a/dictionaries/fr.dictionary.itop.core.php b/dictionaries/fr.dictionary.itop.core.php index 3569a38d0..02e8e1668 100644 --- a/dictionaries/fr.dictionary.itop.core.php +++ b/dictionaries/fr.dictionary.itop.core.php @@ -556,7 +556,7 @@ Opérateurs :
'Core:Synchro:History' => 'Historique de synchronisation', 'Core:Synchro:NeverRun' => 'Aucun historique, la synchronisation n\'a pas encore fonctionné', 'Core:Synchro:SynchroEndedOn_Date' => 'La dernière synchronisation s\'est terminée à: %1$s.', - 'Core:Synchro:SynchroRunningStartedOn_Date' => 'Synchronisation en cours (début à $1$s)', + 'Core:Synchro:SynchroRunningStartedOn_Date' => 'Synchronisation en cours (début à %1$s)', 'Menu:DataSources' => 'Synchronisation', 'Menu:DataSources+' => '', 'Core:Synchro:label_repl_ignored' => 'Ignorés (%1$s)', diff --git a/dictionaries/hu.dictionary.itop.core.php b/dictionaries/hu.dictionary.itop.core.php index 50d881d9d..cd32e21ae 100755 --- a/dictionaries/hu.dictionary.itop.core.php +++ b/dictionaries/hu.dictionary.itop.core.php @@ -422,7 +422,7 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array( 'Core:Synchro:History' => 'Szinkronizáció történet', 'Core:Synchro:NeverRun' => 'Ez a szinkronizáció még soha nem futott. Nincs még napló bejegyzés.', 'Core:Synchro:SynchroEndedOn_Date' => 'Az utolsó szinkronizáció lefutásának időpontja: %1$s.', - 'Core:Synchro:SynchroRunningStartedOn_Date' => 'Az szinkronizáció elindut $1$s, de még fut.', + 'Core:Synchro:SynchroRunningStartedOn_Date' => 'Az szinkronizáció elindut %1$s, de még fut.', 'Menu:DataSources' => 'Szinkronizált adatforrások', 'Menu:DataSources+' => '', 'Core:Synchro:label_repl_ignored' => 'Figyelmen kívül hagyott (%1$s)', diff --git a/dictionaries/it.dictionary.itop.core.php b/dictionaries/it.dictionary.itop.core.php index 2d9fc4d94..7a9026145 100644 --- a/dictionaries/it.dictionary.itop.core.php +++ b/dictionaries/it.dictionary.itop.core.php @@ -1,485 +1,485 @@ - * @author Romain Quetiez * @author Denis Flaven - - * @licence http://www.opensource.org/licenses/gpl-3.0.html LGPL - */ - -Dict::Add('IT IT', 'Italian', 'Italiano', array( - 'Class:ActionEmail' => 'E-mail di notifica', - 'Class:ActionEmail+' => '', - 'Class:ActionEmail/Attribute:test_recipient' => 'Test destinatario', - 'Class:ActionEmail/Attribute:test_recipient+' => '', - 'Class:ActionEmail/Attribute:from' => 'Da', - 'Class:ActionEmail/Attribute:from+' => '', - 'Class:ActionEmail/Attribute:reply_to' => 'Rispondi A', - 'Class:ActionEmail/Attribute:reply_to+' => '', - 'Class:ActionEmail/Attribute:to' => 'A', - 'Class:ActionEmail/Attribute:to+' => '', - 'Class:ActionEmail/Attribute:cc' => 'Cc', - 'Class:ActionEmail/Attribute:cc+' => '', - 'Class:ActionEmail/Attribute:bcc' => 'BCC', - 'Class:ActionEmail/Attribute:bcc+' => '', - 'Class:ActionEmail/Attribute:subject' => 'Oggetto', - 'Class:ActionEmail/Attribute:subject+' => '', - 'Class:ActionEmail/Attribute:body' => 'corpo', - 'Class:ActionEmail/Attribute:body+' => '', - 'Class:ActionEmail/Attribute:importance' => 'priorità', - 'Class:ActionEmail/Attribute:importance+' => '', - 'Class:ActionEmail/Attribute:importance/Value:high' => 'alta', - 'Class:ActionEmail/Attribute:importance/Value:high+' => '', - 'Class:ActionEmail/Attribute:importance/Value:low' => 'bassa', - 'Class:ActionEmail/Attribute:importance/Value:low+' => '', - 'Class:ActionEmail/Attribute:importance/Value:normal' => 'normake', - 'Class:ActionEmail/Attribute:importance/Value:normal+' => '', - 'Class:TriggerOnStateEnter' => 'Trigger (sull\'entrare in uno stato)', - 'Class:TriggerOnStateEnter+' => '', - 'Class:TriggerOnStateLeave' => 'Trigger (sul lasciare uno stato)~~', - 'Class:TriggerOnStateLeave+' => '', - 'Class:TriggerOnObjectCreate' => 'Trigger (sulla creazione di un oggetto)~~', - 'Class:TriggerOnObjectCreate+' => '', - 'Class:lnkTriggerAction' => 'Azione/Trigger~~', - 'Class:lnkTriggerAction+' => '', - 'Class:lnkTriggerAction/Attribute:action_id' => 'Azione', - 'Class:lnkTriggerAction/Attribute:action_id+' => '', - 'Class:lnkTriggerAction/Attribute:trigger_id' => 'Trigger', - 'Class:lnkTriggerAction/Attribute:trigger_id+' => '', - 'Class:lnkTriggerAction/Attribute:order' => 'Ordine', - 'Class:lnkTriggerAction/Attribute:order+' => '', - 'Class:AsyncSendEmail' => 'Email (asincrona)', - 'Class:AsyncSendEmail/Attribute:to' => 'A', - 'Class:AsyncSendEmail/Attribute:subject' => 'Oggetto', - 'Class:AsyncSendEmail/Attribute:body' => 'Corpo', - 'Class:AsyncSendEmail/Attribute:header' => 'Intestazione', - 'Class:CMDBChange' => 'Cambio', - 'Class:CMDBChange+' => '', - 'Class:CMDBChange/Attribute:date' => 'data', - 'Class:CMDBChange/Attribute:date+' => '', - 'Class:CMDBChange/Attribute:userinfo' => 'info varie', - 'Class:CMDBChange/Attribute:userinfo+' => '', - 'Class:CMDBChangeOp' => 'Operazione di Cambio', - 'Class:CMDBChangeOp+' => '', - 'Class:CMDBChangeOp/Attribute:change' => 'cambio', - 'Class:CMDBChangeOp/Attribute:change+' => '', - 'Class:CMDBChangeOp/Attribute:objclass' => 'oggetto della classe', - 'Class:CMDBChangeOp/Attribute:objclass+' => '', - 'Class:CMDBChangeOp/Attribute:objkey' => 'oggetto id', - 'Class:CMDBChangeOp/Attribute:objkey+' => '', - 'Class:CMDBChangeOp/Attribute:finalclass' => 'tipo', - 'Class:CMDBChangeOp/Attribute:finalclass+' => '', - 'Class:CMDBChangeOpCreate' => 'creazione dell\'oggetto', - 'Class:CMDBChangeOpCreate+' => '', - 'Class:CMDBChangeOpDelete' => 'cancellazione dell\'oggetto', - 'Class:CMDBChangeOpDelete+' => '', - 'Class:CMDBChangeOpSetAttribute' => 'cambio dell\'oggetto', - 'Class:CMDBChangeOpSetAttribute+' => '', - 'Class:CMDBChangeOpSetAttribute/Attribute:attcode' => 'Attributo', - 'Class:CMDBChangeOpSetAttribute/Attribute:attcode+' => '', - 'Class:CMDBChangeOpSetAttributeScalar' => 'cambio della proprietà', - 'Class:CMDBChangeOpSetAttributeScalar+' => '', - 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue' => 'Valore precedente', - 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue+' => '', - 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue' => 'Valore Nuovo', - 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue+' => '', - 'Class:CMDBChangeOpSetAttributeBlob' => 'modifica i dati', - 'Class:CMDBChangeOpSetAttributeBlob+' => '', - 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata' => 'Dati precedenti', - 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata+' => '', - 'Class:CMDBChangeOpSetAttributeOneWayPassword' => 'Password criptata', - 'Class:CMDBChangeOpSetAttributeOneWayPassword/Attribute:prev_pwd' => 'Valore Precedente', - 'Class:CMDBChangeOpSetAttributeEncrypted' => 'Encrypted Field~~', - 'Class:CMDBChangeOpSetAttributeEncrypted/Attribute:prevstring' => 'Valore Precedente', - 'Class:CMDBChangeOpSetAttributeText' => 'modifica testo', - 'Class:CMDBChangeOpSetAttributeText+' => '', - 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata' => 'Dati precendenti', - 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata+' => '', - 'Class:CMDBChangeOpSetAttributeCaseLog' => 'Log dei casi', - 'Class:CMDBChangeOpSetAttributeCaseLog/Attribute:lastentry' => 'Ultima entrata', - 'Class:Event' => 'Log dell\'evento', - 'Class:Event+' => '', - 'Class:Event/Attribute:message' => 'Messaggio', - 'Class:Event/Attribute:message+' => '', - 'Class:Event/Attribute:date' => 'Data', - 'Class:Event/Attribute:date+' => '', - 'Class:Event/Attribute:userinfo' => 'Info Utente', - 'Class:Event/Attribute:userinfo+' => '', - 'Class:Event/Attribute:finalclass' => 'Tipo', - 'Class:Event/Attribute:finalclass+' => '', - 'Class:EventNotification' => 'Notifica dell\'evento', - 'Class:EventNotification+' => '', - 'Class:EventNotification/Attribute:trigger_id' => 'Trigger', - 'Class:EventNotification/Attribute:trigger_id+' => '', - 'Class:EventNotification/Attribute:action_id' => 'utente', - 'Class:EventNotification/Attribute:action_id+' => '', - 'Class:EventNotification/Attribute:object_id' => 'Oggetto id', - 'Class:EventNotification/Attribute:object_id+' => '', - 'Class:EventNotificationEmail' => 'Email caso di emissione', - 'Class:EventNotificationEmail+' => '', - 'Class:EventNotificationEmail/Attribute:to' => 'A', - 'Class:EventNotificationEmail/Attribute:to+' => '', - 'Class:EventNotificationEmail/Attribute:cc' => 'CC', - 'Class:EventNotificationEmail/Attribute:cc+' => '', - 'Class:EventNotificationEmail/Attribute:bcc' => 'BCC', - 'Class:EventNotificationEmail/Attribute:bcc+' => '', - 'Class:EventNotificationEmail/Attribute:from' => 'Da', - 'Class:EventNotificationEmail/Attribute:from+' => '', - 'Class:EventNotificationEmail/Attribute:subject' => 'Oggeto', - 'Class:EventNotificationEmail/Attribute:subject+' => '', - 'Class:EventNotificationEmail/Attribute:body' => 'Corpo', - 'Class:EventNotificationEmail/Attribute:body+' => '', - 'Class:EventIssue' => 'Evento Problema', - 'Class:EventIssue+' => '', - 'Class:EventIssue/Attribute:issue' => 'Problema', - 'Class:EventIssue/Attribute:issue+' => '', - 'Class:EventIssue/Attribute:impact' => 'Impatto', - 'Class:EventIssue/Attribute:impact+' => '', - 'Class:EventIssue/Attribute:page' => 'Pagina', - 'Class:EventIssue/Attribute:page+' => '', - 'Class:EventIssue/Attribute:arguments_post' => 'Argomenti inviati', - 'Class:EventIssue/Attribute:arguments_post+' => '', - 'Class:EventIssue/Attribute:arguments_get' => 'Argomenti URL', - 'Class:EventIssue/Attribute:arguments_get+' => '', - 'Class:EventIssue/Attribute:callstack' => 'Callstack', - 'Class:EventIssue/Attribute:callstack+' => '', - 'Class:EventIssue/Attribute:data' => 'Dati', - 'Class:EventIssue/Attribute:data+' => '', - 'Class:EventWebService' => 'Evento Servizio Web', - 'Class:EventWebService+' => '', - 'Class:EventWebService/Attribute:verb' => 'Verbo', - 'Class:EventWebService/Attribute:verb+' => '', - 'Class:EventWebService/Attribute:result' => 'Resulto', - 'Class:EventWebService/Attribute:result+' => '', - 'Class:EventWebService/Attribute:log_info' => 'Log delle info', - 'Class:EventWebService/Attribute:log_info+' => '', - 'Class:EventWebService/Attribute:log_warning' => 'Log dei warning', - 'Class:EventWebService/Attribute:log_warning+' => '', - 'Class:EventWebService/Attribute:log_error' => 'Log degli errori', - 'Class:EventWebService/Attribute:log_error+' => '', - 'Class:EventWebService/Attribute:data' => 'Dati', - 'Class:EventWebService/Attribute:data+' => '', - 'Class:EventLoginUsage' => 'Login di utilizzo', - 'Class:EventLoginUsage+' => '', - 'Class:EventLoginUsage/Attribute:user_id' => 'Login', - 'Class:EventLoginUsage/Attribute:user_id+' => '', - 'Class:SynchroDataSource' => 'Sorgente di sincronizzazione dei dati', - 'Class:SynchroDataSource/Attribute:name' => 'Nome', - 'Class:SynchroDataSource/Attribute:name+' => '', - 'Class:SynchroDataSource/Attribute:description' => 'Descrizione', - 'Class:SynchroDataSource/Attribute:status' => 'Stato', - 'Class:SynchroDataSource/Attribute:status/Value:implementation' => 'Implementazione', - 'Class:SynchroDataSource/Attribute:status/Value:obsolete' => 'Obsoleto', - 'Class:SynchroDataSource/Attribute:status/Value:production' => 'Produzione', - 'Class:SynchroDataSource/Attribute:user_id' => 'Utente', - 'Class:SynchroDataSource/Attribute:scope_class' => 'Classe Target', - 'Class:SynchroDataSource/Attribute:scope_restriction' => 'Campo di applicazione restrizione', - 'Class:SynchroDataSource/Attribute:full_load_periodicity' => 'Intervallo a pieno carico', - 'Class:SynchroDataSource/Attribute:full_load_periodicity+' => '', - 'Class:SynchroDataSource/Attribute:reconciliation_policy' => 'Policy di riconciliazione', - 'Class:SynchroDataSource/Attribute:reconciliation_policy/Value:use_attributes' => 'Usa gli attributi', - 'Class:SynchroDataSource/Attribute:reconciliation_policy/Value:use_primary_key' => 'Usa il campo chiave primaria', - 'Class:SynchroDataSource/Attribute:action_on_zero' => 'Azione su zero~~', - 'Class:SynchroDataSource/Attribute:action_on_zero+' => '', - 'Class:SynchroDataSource/Attribute:action_on_zero/Value:create' => 'Crea', - 'Class:SynchroDataSource/Attribute:action_on_zero/Value:error' => 'Errore', - 'Class:SynchroDataSource/Attribute:action_on_one' => 'Azione su uno', - 'Class:SynchroDataSource/Attribute:action_on_one+' => '', - 'Class:SynchroDataSource/Attribute:action_on_one/Value:error' => 'Error', - 'Class:SynchroDataSource/Attribute:action_on_one/Value:update' => 'Aggiorna', - 'Class:SynchroDataSource/Attribute:action_on_multiple' => 'Azione su molti', - 'Class:SynchroDataSource/Attribute:action_on_multiple+' => '', - 'Class:SynchroDataSource/Attribute:action_on_multiple/Value:create' => 'Crea', - 'Class:SynchroDataSource/Attribute:action_on_multiple/Value:error' => 'Errore', - 'Class:SynchroDataSource/Attribute:action_on_multiple/Value:take_first' => 'Considera il primo (random?)', - 'Class:SynchroDataSource/Attribute:delete_policy' => 'Policy di cancellazioen', - 'Class:SynchroDataSource/Attribute:delete_policy/Value:delete' => 'Cancella', - 'Class:SynchroDataSource/Attribute:delete_policy/Value:ignore' => 'Ignora', - 'Class:SynchroDataSource/Attribute:delete_policy/Value:update' => 'Aggiorna', - 'Class:SynchroDataSource/Attribute:delete_policy/Value:update_then_delete' => 'Aggiorna e poi Cancella', - 'Class:SynchroDataSource/Attribute:delete_policy_update' => 'Regole per l\'aggiornamento', - 'Class:SynchroDataSource/Attribute:delete_policy_update+' => '', - 'Class:SynchroDataSource/Attribute:delete_policy_retention' => 'Durata di conservazione', - 'Class:SynchroDataSource/Attribute:delete_policy_retention+' => '', - 'Class:SynchroDataSource/Attribute:attribute_list' => 'Lista degli attributi', - 'Class:SynchroDataSource/Attribute:user_delete_policy' => 'utenti autorizzati', - 'Class:SynchroDataSource/Attribute:user_delete_policy+' => '', - 'Class:SynchroDataSource/Attribute:user_delete_policy/Value:administrators' => 'Solo Amministratore', - 'Class:SynchroDataSource/Attribute:user_delete_policy/Value:everybody' => 'Tutti sono autorizzati a cancellare questi oggetti', - 'Class:SynchroDataSource/Attribute:user_delete_policy/Value:nobody' => 'Nessuno', - 'Class:SynchroDataSource/Attribute:url_icon' => 'Icona di collegamento ipertestuale', - 'Class:SynchroDataSource/Attribute:url_icon+' => '', - 'Class:SynchroDataSource/Attribute:url_application' => 'Collegamento ipertestuale all\'applicazione', - 'Class:SynchroDataSource/Attribute:url_application+' => '', - 'Class:SynchroAttribute' => 'Attributo di sincronizzazione', - 'Class:SynchroAttribute/Attribute:sync_source_id' => 'Sorgente sincronizzazione dati', - 'Class:SynchroAttribute/Attribute:attcode' => 'Codice attributo', - 'Class:SynchroAttribute/Attribute:update' => 'Aggiorna', - 'Class:SynchroAttribute/Attribute:reconcile' => 'Rincocilia', - 'Class:SynchroAttribute/Attribute:update_policy' => 'Policy di aggiornamento', - 'Class:SynchroAttribute/Attribute:update_policy/Value:master_locked' => 'Bloccato', - 'Class:SynchroAttribute/Attribute:update_policy/Value:master_unlocked' => 'Sbloccato', - 'Class:SynchroAttribute/Attribute:update_policy/Value:write_if_empty' => 'Inizializza se vuoto', - 'Class:SynchroAttribute/Attribute:finalclass' => 'Classe', - 'Class:SynchroAttExtKey' => 'Attributo di sincronizzazione (ExtKey)', - 'Class:SynchroAttExtKey/Attribute:reconciliation_attcode' => 'Attributo di riconciliazione', - 'Class:SynchroAttLinkSet' => 'Attributo di sincronizzazione (Linkset)', - 'Class:SynchroAttLinkSet/Attribute:row_separator' => 'Separatore di righe', - 'Class:SynchroAttLinkSet/Attribute:attribute_separator' => 'Separatore di attributi', - 'Class:SynchroLog' => 'Log di sincronizzazione', - 'Class:SynchroLog/Attribute:sync_source_id' => 'Sorgente di sincronizzazione dati', - 'Class:SynchroLog/Attribute:start_date' => 'Data di inizio', - 'Class:SynchroLog/Attribute:end_date' => 'Data di fine', - 'Class:SynchroLog/Attribute:status' => 'Stato', - 'Class:SynchroLog/Attribute:status/Value:completed' => 'Completo', - 'Class:SynchroLog/Attribute:status/Value:error' => 'Errore', - 'Class:SynchroLog/Attribute:status/Value:running' => 'Ancora in esecuzione', - 'Class:SynchroLog/Attribute:stats_nb_replica_seen' => 'Nb replica viste', - 'Class:SynchroLog/Attribute:stats_nb_replica_total' => 'Nb replica totale', - 'Class:SynchroLog/Attribute:stats_nb_obj_deleted' => 'Nb oggetti cancellati', - 'Class:SynchroLog/Attribute:stats_nb_obj_deleted_errors' => 'Nb di errore durante la cancellazione', - 'Class:SynchroLog/Attribute:stats_nb_obj_obsoleted' => 'Nb oggetti obsoleti', - 'Class:SynchroLog/Attribute:stats_nb_obj_obsoleted_errors' => 'Nb di errori mentre obsoleta', - 'Class:SynchroLog/Attribute:stats_nb_obj_created' => 'Nb oggetti creati', - 'Class:SynchroLog/Attribute:stats_nb_obj_created_errors' => 'Nb di errori durante la creazione', - 'Class:SynchroLog/Attribute:stats_nb_obj_updated' => 'Nb oggetti aggiornati', - 'Class:SynchroLog/Attribute:stats_nb_obj_updated_errors' => 'Nb di errori durante l\'aggiornamento', - 'Class:SynchroLog/Attribute:stats_nb_replica_reconciled_errors' => 'Nb di errori durante la riconcilazione', - 'Class:SynchroLog/Attribute:stats_nb_replica_disappeared_no_action' => 'Nb repliche scomparse', - 'Class:SynchroLog/Attribute:stats_nb_obj_new_updated' => 'Nb oggetti aggiornati', - 'Class:SynchroLog/Attribute:stats_nb_obj_new_unchanged' => 'Nb oggetti non modificati', - 'Class:SynchroLog/Attribute:last_error' => 'Untimo errore', - 'Class:SynchroLog/Attribute:traces' => 'Tracce', - 'Class:SynchroReplica' => 'Replica sincronizzazione', - 'Class:SynchroReplica/Attribute:sync_source_id' => 'Sorgente di sincronizzazione dati', - 'Class:SynchroReplica/Attribute:dest_id' => 'Oggetto di destinazione (ID)~~', - 'Class:SynchroReplica/Attribute:dest_class' => 'Tipo di destinazione', - 'Class:SynchroReplica/Attribute:status_last_seen' => 'Ultimo visto', - 'Class:SynchroReplica/Attribute:status' => 'Stato', - 'Class:SynchroReplica/Attribute:status/Value:modified' => 'Modificato', - 'Class:SynchroReplica/Attribute:status/Value:new' => 'Nuovo', - 'Class:SynchroReplica/Attribute:status/Value:obsolete' => 'Obsoleto', - 'Class:SynchroReplica/Attribute:status/Value:orphan' => 'Orfano', - 'Class:SynchroReplica/Attribute:status/Value:synchronized' => 'Sincronizzato', - 'Class:SynchroReplica/Attribute:status_dest_creator' => 'Oggetto Creato ?', - 'Class:SynchroReplica/Attribute:status_last_error' => 'Ultimo Errore', - 'Class:SynchroReplica/Attribute:info_creation_date' => 'Data di creazione', - 'Class:SynchroReplica/Attribute:info_last_modified' => 'Data ultima modifica', - 'Class:appUserPreferences' => 'Preferenze utente', - 'Class:appUserPreferences/Attribute:userid' => 'Utente', - 'Class:appUserPreferences/Attribute:preferences' => 'Preferenze', - 'Core:AttributeLinkedSet' => 'Array di oggetti', - 'Core:AttributeLinkedSet+' => '', - 'Core:AttributeLinkedSetIndirect' => 'Array di oggetti (N-N)', - 'Core:AttributeLinkedSetIndirect+' => '', - 'Core:AttributeInteger' => 'Integero', - 'Core:AttributeInteger+' => '', - 'Core:AttributeDecimal' => 'Decimale', - 'Core:AttributeDecimal+' => '', - 'Core:AttributeBoolean' => 'Booleano', - 'Core:AttributeBoolean+' => '', - 'Core:AttributeString' => 'Stringa', - 'Core:AttributeString+' => '', - 'Core:AttributeClass' => 'Classe', - 'Core:AttributeClass+' => '', - 'Core:AttributeApplicationLanguage' => 'Linguaggio Utente', - 'Core:AttributeApplicationLanguage+' => '', - 'Core:AttributeFinalClass' => 'Classe (auto)', - 'Core:AttributeFinalClass+' => '', - 'Core:AttributePassword' => 'Password', - 'Core:AttributePassword+' => '', - 'Core:AttributeEncryptedString' => 'Stringa criptata', - 'Core:AttributeEncryptedString+' => '', - 'Core:AttributeText' => 'Testo', - 'Core:AttributeText+' => '', - 'Core:AttributeHTML' => 'HTML', - 'Core:AttributeHTML+' => '', - 'Core:AttributeEmailAddress' => 'Indirizzo Email', - 'Core:AttributeEmailAddress+' => '', - 'Core:AttributeIPAddress' => 'Indirizzo IP', - 'Core:AttributeIPAddress+' => '', - 'Core:AttributeOQL' => 'OQL', - 'Core:AttributeOQL+' => '', - 'Core:AttributeEnum' => 'Enum', - 'Core:AttributeEnum+' => '', - 'Core:AttributeTemplateString' => 'Stringa Template', - 'Core:AttributeTemplateString+' => '', - 'Core:AttributeTemplateText' => 'Testo Template', - 'Core:AttributeTemplateText+' => '', - 'Core:AttributeTemplateHTML' => 'HTML Template', - 'Core:AttributeTemplateHTML+' => '', - 'Core:AttributeDateTime' => 'Data/ora', - 'Core:AttributeDateTime+' => '', - 'Core:AttributeDate' => 'Data', - 'Core:AttributeDate+' => '', - 'Core:AttributeDeadline' => 'Scadenza', - 'Core:AttributeDeadline+' => '', - 'Core:AttributeExternalKey' => 'Chiave esterna', - 'Core:AttributeExternalKey+' => '', - 'Core:AttributeExternalField' => 'Campo esterno', - 'Core:AttributeExternalField+' => '', - 'Core:AttributeURL' => 'URL', - 'Core:AttributeURL+' => '', - 'Core:AttributeBlob' => 'Blob', - 'Core:AttributeBlob+' => '', - 'Core:AttributeOneWayPassword' => 'Password unidierzionale', - 'Core:AttributeOneWayPassword+' => '', - 'Core:AttributeTable' => 'Tabella', - 'Core:AttributeTable+' => '', - 'Core:AttributePropertySet' => 'Proprietà', - 'Core:AttributePropertySet+' => '', - 'Class:CMDBChangeOp/Attribute:date' => 'data', - 'Class:CMDBChangeOp/Attribute:date+' => '', - 'Class:CMDBChangeOp/Attribute:userinfo' => 'utente', - 'Class:CMDBChangeOp/Attribute:userinfo+' => '', - 'Change:ObjectCreated' => 'Oggetto creato', - 'Change:ObjectDeleted' => 'Oggetto cancellato', - 'Change:ObjectModified' => 'Object modificato', - 'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s imposatato a %2$s (valore precendente: %3$s)', - 'Change:AttName_SetTo' => '%1$s impostato a %2$s~~', - 'Change:Text_AppendedTo_AttName' => '%1$s allegato a %2$s~~', - 'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s moficato, valore precendente: %2$s', - 'Change:AttName_Changed' => '%1$s modificato', - 'Change:AttName_EntryAdded' => '%1$s modificato, nuova entrata aggiunta.', - 'Class:EventLoginUsage/Attribute:contact_name' => 'Nome Utente', - 'Class:EventLoginUsage/Attribute:contact_name+' => '', - 'Class:EventLoginUsage/Attribute:contact_email' => 'Email Utente', - 'Class:EventLoginUsage/Attribute:contact_email+' => '', - 'Class:Action' => 'Azione personalizzata', - 'Class:Action+' => '', - 'Class:Action/Attribute:name' => 'Nome', - 'Class:Action/Attribute:name+' => '', - 'Class:Action/Attribute:description' => 'Descrizione', - 'Class:Action/Attribute:description+' => '', - 'Class:Action/Attribute:status' => 'Stato', - 'Class:Action/Attribute:status+' => '', - 'Class:Action/Attribute:status/Value:test' => 'In fase di test', - 'Class:Action/Attribute:status/Value:test+' => '', - 'Class:Action/Attribute:status/Value:enabled' => 'In produzione', - 'Class:Action/Attribute:status/Value:enabled+' => '', - 'Class:Action/Attribute:status/Value:disabled' => 'Inattivo', - 'Class:Action/Attribute:status/Value:disabled+' => '', - 'Class:Action/Attribute:trigger_list' => 'Trigger Correlati', - 'Class:Action/Attribute:trigger_list+' => '', - 'Class:Action/Attribute:finalclass' => 'Tipo', - 'Class:Action/Attribute:finalclass+' => '', - 'Class:ActionNotification' => 'Notifica', - 'Class:ActionNotification+' => '', - 'Class:Trigger' => 'Trigger', - 'Class:Trigger+' => '', - 'Class:Trigger/Attribute:description' => 'Descrizione', - 'Class:Trigger/Attribute:description+' => '', - 'Class:Trigger/Attribute:action_list' => 'Azioni Triggerate', - 'Class:Trigger/Attribute:action_list+' => '', - 'Class:Trigger/Attribute:finalclass' => 'Tipo', - 'Class:Trigger/Attribute:finalclass+' => '', - 'Class:TriggerOnObject' => 'Trigger (classe dipendente)', - 'Class:TriggerOnObject+' => '', - 'Class:TriggerOnObject/Attribute:target_class' => 'Classe Target', - 'Class:TriggerOnObject/Attribute:target_class+' => '', - 'Class:TriggerOnStateChange' => 'Trigger (sul cambio di stato)', - 'Class:TriggerOnStateChange+' => '', - 'Class:TriggerOnStateChange/Attribute:state' => 'Stato', - 'Class:TriggerOnStateChange/Attribute:state+' => '', - 'Class:lnkTriggerAction/Attribute:action_name' => 'Azione', - 'Class:lnkTriggerAction/Attribute:action_name+' => '', - 'Class:lnkTriggerAction/Attribute:trigger_name' => 'Trigger', - 'Class:lnkTriggerAction/Attribute:trigger_name+' => '', - 'Class:SynchroDataSource/Attribute:delete_policy/Value:never' => 'Nessuno', - 'Class:SynchroDataSource/Attribute:delete_policy/Value:depends' => 'Solo Amministratore', - 'Class:SynchroDataSource/Attribute:delete_policy/Value:always' => 'Tutti gli utenti autorizzati', - 'SynchroDataSource:Description' => 'Descrizione', - 'SynchroDataSource:Reconciliation' => 'Ricerca & riconciliazione', - 'SynchroDataSource:Deletion' => 'Regole di cancellazione', - 'SynchroDataSource:Status' => 'Stato', - 'SynchroDataSource:Information' => 'Informazione', - 'SynchroDataSource:Definition' => 'Definizione', - 'Core:SynchroAttributes' => 'Attributi', - 'Core:SynchroStatus' => 'Stato', - 'Core:Synchro:ErrorsLabel' => 'Errori', - 'Core:Synchro:CreatedLabel' => 'Creato', - 'Core:Synchro:ModifiedLabel' => 'Modificato', - 'Core:Synchro:UnchangedLabel' => 'Non modificato', - 'Core:Synchro:ReconciledErrorsLabel' => 'Errori', - 'Core:Synchro:ReconciledLabel' => 'Reconciliato', - 'Core:Synchro:ReconciledNewLabel' => 'Creato', - 'Core:SynchroReconcile:Yes' => 'Si', - 'Core:SynchroReconcile:No' => 'No', - 'Core:SynchroUpdate:Yes' => 'Si', - 'Core:SynchroUpdate:No' => 'No', - 'Core:Synchro:LastestStatus' => 'Ultimo stato', - 'Core:Synchro:History' => 'Storia della sincronizzazione', - 'Core:Synchro:NeverRun' => 'Questa sincronizzazione non è mai stata eseguita. Nessun Log ancora...', - 'Core:Synchro:SynchroEndedOn_Date' => 'L\'ultima sincronizzazione si è conclusa il %1$s.~~', - 'Core:Synchro:SynchroRunningStartedOn_Date' => 'La sincronizzazione è iniziata il $1$s è ancora in esecuzione...~~', - 'Menu:DataSources' => 'Sorgente di sincronizzazione dei dati', - 'Menu:DataSources+' => '', - 'Core:Synchro:label_repl_ignored' => 'Ignorato(%1$s)', - 'Core:Synchro:label_repl_disappeared' => 'Scomparso (%1$s)', - 'Core:Synchro:label_repl_existing' => 'Esistente (%1$s)', - 'Core:Synchro:label_repl_new' => 'Nuovo (%1$s)~~', - 'Core:Synchro:label_obj_deleted' => 'Cancellato (%1$s)', - 'Core:Synchro:label_obj_obsoleted' => 'Obsoleto (%1$s)', - 'Core:Synchro:label_obj_disappeared_errors' => 'Errori (%1$s)', - 'Core:Synchro:label_obj_disappeared_no_action' => 'Nessuna Azione (%1$s)', - 'Core:Synchro:label_obj_unchanged' => 'Non modificato(%1$s)', - 'Core:Synchro:label_obj_updated' => 'Aggiornato (%1$s)', - 'Core:Synchro:label_obj_updated_errors' => 'Errori (%1$s)', - 'Core:Synchro:label_obj_new_unchanged' => 'Non modificato (%1$s)', - 'Core:Synchro:label_obj_new_updated' => 'Aggiornato (%1$s)', - 'Core:Synchro:label_obj_created' => 'Creato (%1$s)', - 'Core:Synchro:label_obj_new_errors' => 'Errori (%1$s)', - 'Core:SynchroLogTitle' => '%1$s - %2$s~~', - 'Core:Synchro:Nb_Replica' => 'Replica processata: %1$s', - 'Core:Synchro:Nb_Class:Objects' => '%1$s: %2$s', - 'Class:SynchroDataSource/Error:AtLeastOneReconciliationKeyMustBeSpecified' => 'Almeno una chiave riconciliazione deve essere specificata, o la policy di conciliazione deve essere quella di utilizzare la chiave primaria', - 'Class:SynchroDataSource/Error:DeleteRetentionDurationMustBeSpecified' => 'Deve essere specificato un periodo di conservazione di cancellazione , dato che gli oggetti devono essere eliminati dopo essere contrassegnati come obsoleti ', - 'Class:SynchroDataSource/Error:DeletePolicyUpdateMustBeSpecified' => 'Oggetti obsoleti devono essere aggiornati, ma nessun aggiornamento è specificato', - 'Core:SynchroReplica:PublicData' => 'Dati Pubblici', - 'Core:SynchroReplica:PrivateDetails' => 'Dettagli Privati', - 'Core:SynchroReplica:BackToDataSource' => 'Torna indietro alla sorgente di sincronizzazione dei dati: %1$s~~', - 'Core:SynchroReplica:ListOfReplicas' => 'Lista della Replica', - 'Core:SynchroAttExtKey:ReconciliationById' => 'id (Chiave Primaria)', - 'Core:SynchroAtt:attcode' => 'Attributo', - 'Core:SynchroAtt:attcode+' => '', - 'Core:SynchroAtt:reconciliation' => 'Riconciliazione ?~~', - 'Core:SynchroAtt:reconciliation+' => '', - 'Core:SynchroAtt:update' => 'Aggiornamento ?~~', - 'Core:SynchroAtt:update+' => '', - 'Core:SynchroAtt:update_policy' => 'Policy di aggiornamento', - 'Core:SynchroAtt:update_policy+' => '', - 'Core:SynchroAtt:reconciliation_attcode' => 'Chiave di riconciliazione', - 'Core:SynchroAtt:reconciliation_attcode+' => '', - 'Core:SyncDataExchangeComment' => '(Scambio dati)', - 'Core:Synchro:ListOfDataSources' => 'Lista delle sorgenti di dati:', - 'Core:Synchro:LastSynchro' => 'Ultima sincronizzazione:', - 'Core:Synchro:ThisObjectIsSynchronized' => 'Questo oggetto è sincronizzato con una sorgente esterna di dati', - 'Core:Synchro:TheObjectWasCreatedBy_Source' => 'L\'oggetti è stato creato da una sorgente esterna di dati %1$s~~', - 'Core:Synchro:TheObjectCanBeDeletedBy_Source' => 'L\'oggetti può essere cancellato da una sorgente esterna di dati %1$s~~', - 'Core:Synchro:TheObjectCannotBeDeletedByUser_Source' => 'Tu non puoi cancellare l\'oggetto perché è di proprietà della sorgente dati esterna %1$s~~', - 'TitleSynchroExecution' => 'Esecuzione della sincronizzazione', - 'Class:SynchroDataSource:DataTable' => 'Tabella del database: %1$s', - 'Core:SyncDataSourceObsolete' => 'La fonte dei dati è contrassegnata come obsoleta. Operazione annullata', - 'Core:SyncDataSourceAccessRestriction' => 'Solo amministratori o l\'utente specificato nella fonte dei dati può eseguire questa operazione. Operazione annullata', - 'Core:SyncTooManyMissingReplicas' => 'Tutte le repliche sono mancanti dall\'importazione. Hai eseguito realmente l\'importazione? Operazione annullata', - 'Core:Duration_Seconds' => '%1$ds', - 'Core:Duration_Minutes_Seconds' => '%1$dmin %2$ds', - 'Core:Duration_Hours_Minutes_Seconds' => '%1$dh %2$dmin %3$sec~~', - 'Core:Duration_Days_Hours_Minutes_Seconds' => '%1$sg %2$dh %3$dmin %4$ds~~', -)); -?> + + * @licence http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +Dict::Add('IT IT', 'Italian', 'Italiano', array( + 'Class:ActionEmail' => 'E-mail di notifica', + 'Class:ActionEmail+' => '', + 'Class:ActionEmail/Attribute:test_recipient' => 'Test destinatario', + 'Class:ActionEmail/Attribute:test_recipient+' => '', + 'Class:ActionEmail/Attribute:from' => 'Da', + 'Class:ActionEmail/Attribute:from+' => '', + 'Class:ActionEmail/Attribute:reply_to' => 'Rispondi A', + 'Class:ActionEmail/Attribute:reply_to+' => '', + 'Class:ActionEmail/Attribute:to' => 'A', + 'Class:ActionEmail/Attribute:to+' => '', + 'Class:ActionEmail/Attribute:cc' => 'Cc', + 'Class:ActionEmail/Attribute:cc+' => '', + 'Class:ActionEmail/Attribute:bcc' => 'BCC', + 'Class:ActionEmail/Attribute:bcc+' => '', + 'Class:ActionEmail/Attribute:subject' => 'Oggetto', + 'Class:ActionEmail/Attribute:subject+' => '', + 'Class:ActionEmail/Attribute:body' => 'corpo', + 'Class:ActionEmail/Attribute:body+' => '', + 'Class:ActionEmail/Attribute:importance' => 'priorità', + 'Class:ActionEmail/Attribute:importance+' => '', + 'Class:ActionEmail/Attribute:importance/Value:high' => 'alta', + 'Class:ActionEmail/Attribute:importance/Value:high+' => '', + 'Class:ActionEmail/Attribute:importance/Value:low' => 'bassa', + 'Class:ActionEmail/Attribute:importance/Value:low+' => '', + 'Class:ActionEmail/Attribute:importance/Value:normal' => 'normake', + 'Class:ActionEmail/Attribute:importance/Value:normal+' => '', + 'Class:TriggerOnStateEnter' => 'Trigger (sull\'entrare in uno stato)', + 'Class:TriggerOnStateEnter+' => '', + 'Class:TriggerOnStateLeave' => 'Trigger (sul lasciare uno stato)~~', + 'Class:TriggerOnStateLeave+' => '', + 'Class:TriggerOnObjectCreate' => 'Trigger (sulla creazione di un oggetto)~~', + 'Class:TriggerOnObjectCreate+' => '', + 'Class:lnkTriggerAction' => 'Azione/Trigger~~', + 'Class:lnkTriggerAction+' => '', + 'Class:lnkTriggerAction/Attribute:action_id' => 'Azione', + 'Class:lnkTriggerAction/Attribute:action_id+' => '', + 'Class:lnkTriggerAction/Attribute:trigger_id' => 'Trigger', + 'Class:lnkTriggerAction/Attribute:trigger_id+' => '', + 'Class:lnkTriggerAction/Attribute:order' => 'Ordine', + 'Class:lnkTriggerAction/Attribute:order+' => '', + 'Class:AsyncSendEmail' => 'Email (asincrona)', + 'Class:AsyncSendEmail/Attribute:to' => 'A', + 'Class:AsyncSendEmail/Attribute:subject' => 'Oggetto', + 'Class:AsyncSendEmail/Attribute:body' => 'Corpo', + 'Class:AsyncSendEmail/Attribute:header' => 'Intestazione', + 'Class:CMDBChange' => 'Cambio', + 'Class:CMDBChange+' => '', + 'Class:CMDBChange/Attribute:date' => 'data', + 'Class:CMDBChange/Attribute:date+' => '', + 'Class:CMDBChange/Attribute:userinfo' => 'info varie', + 'Class:CMDBChange/Attribute:userinfo+' => '', + 'Class:CMDBChangeOp' => 'Operazione di Cambio', + 'Class:CMDBChangeOp+' => '', + 'Class:CMDBChangeOp/Attribute:change' => 'cambio', + 'Class:CMDBChangeOp/Attribute:change+' => '', + 'Class:CMDBChangeOp/Attribute:objclass' => 'oggetto della classe', + 'Class:CMDBChangeOp/Attribute:objclass+' => '', + 'Class:CMDBChangeOp/Attribute:objkey' => 'oggetto id', + 'Class:CMDBChangeOp/Attribute:objkey+' => '', + 'Class:CMDBChangeOp/Attribute:finalclass' => 'tipo', + 'Class:CMDBChangeOp/Attribute:finalclass+' => '', + 'Class:CMDBChangeOpCreate' => 'creazione dell\'oggetto', + 'Class:CMDBChangeOpCreate+' => '', + 'Class:CMDBChangeOpDelete' => 'cancellazione dell\'oggetto', + 'Class:CMDBChangeOpDelete+' => '', + 'Class:CMDBChangeOpSetAttribute' => 'cambio dell\'oggetto', + 'Class:CMDBChangeOpSetAttribute+' => '', + 'Class:CMDBChangeOpSetAttribute/Attribute:attcode' => 'Attributo', + 'Class:CMDBChangeOpSetAttribute/Attribute:attcode+' => '', + 'Class:CMDBChangeOpSetAttributeScalar' => 'cambio della proprietà', + 'Class:CMDBChangeOpSetAttributeScalar+' => '', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue' => 'Valore precedente', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue+' => '', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue' => 'Valore Nuovo', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue+' => '', + 'Class:CMDBChangeOpSetAttributeBlob' => 'modifica i dati', + 'Class:CMDBChangeOpSetAttributeBlob+' => '', + 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata' => 'Dati precedenti', + 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata+' => '', + 'Class:CMDBChangeOpSetAttributeOneWayPassword' => 'Password criptata', + 'Class:CMDBChangeOpSetAttributeOneWayPassword/Attribute:prev_pwd' => 'Valore Precedente', + 'Class:CMDBChangeOpSetAttributeEncrypted' => 'Encrypted Field~~', + 'Class:CMDBChangeOpSetAttributeEncrypted/Attribute:prevstring' => 'Valore Precedente', + 'Class:CMDBChangeOpSetAttributeText' => 'modifica testo', + 'Class:CMDBChangeOpSetAttributeText+' => '', + 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata' => 'Dati precendenti', + 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata+' => '', + 'Class:CMDBChangeOpSetAttributeCaseLog' => 'Log dei casi', + 'Class:CMDBChangeOpSetAttributeCaseLog/Attribute:lastentry' => 'Ultima entrata', + 'Class:Event' => 'Log dell\'evento', + 'Class:Event+' => '', + 'Class:Event/Attribute:message' => 'Messaggio', + 'Class:Event/Attribute:message+' => '', + 'Class:Event/Attribute:date' => 'Data', + 'Class:Event/Attribute:date+' => '', + 'Class:Event/Attribute:userinfo' => 'Info Utente', + 'Class:Event/Attribute:userinfo+' => '', + 'Class:Event/Attribute:finalclass' => 'Tipo', + 'Class:Event/Attribute:finalclass+' => '', + 'Class:EventNotification' => 'Notifica dell\'evento', + 'Class:EventNotification+' => '', + 'Class:EventNotification/Attribute:trigger_id' => 'Trigger', + 'Class:EventNotification/Attribute:trigger_id+' => '', + 'Class:EventNotification/Attribute:action_id' => 'utente', + 'Class:EventNotification/Attribute:action_id+' => '', + 'Class:EventNotification/Attribute:object_id' => 'Oggetto id', + 'Class:EventNotification/Attribute:object_id+' => '', + 'Class:EventNotificationEmail' => 'Email caso di emissione', + 'Class:EventNotificationEmail+' => '', + 'Class:EventNotificationEmail/Attribute:to' => 'A', + 'Class:EventNotificationEmail/Attribute:to+' => '', + 'Class:EventNotificationEmail/Attribute:cc' => 'CC', + 'Class:EventNotificationEmail/Attribute:cc+' => '', + 'Class:EventNotificationEmail/Attribute:bcc' => 'BCC', + 'Class:EventNotificationEmail/Attribute:bcc+' => '', + 'Class:EventNotificationEmail/Attribute:from' => 'Da', + 'Class:EventNotificationEmail/Attribute:from+' => '', + 'Class:EventNotificationEmail/Attribute:subject' => 'Oggeto', + 'Class:EventNotificationEmail/Attribute:subject+' => '', + 'Class:EventNotificationEmail/Attribute:body' => 'Corpo', + 'Class:EventNotificationEmail/Attribute:body+' => '', + 'Class:EventIssue' => 'Evento Problema', + 'Class:EventIssue+' => '', + 'Class:EventIssue/Attribute:issue' => 'Problema', + 'Class:EventIssue/Attribute:issue+' => '', + 'Class:EventIssue/Attribute:impact' => 'Impatto', + 'Class:EventIssue/Attribute:impact+' => '', + 'Class:EventIssue/Attribute:page' => 'Pagina', + 'Class:EventIssue/Attribute:page+' => '', + 'Class:EventIssue/Attribute:arguments_post' => 'Argomenti inviati', + 'Class:EventIssue/Attribute:arguments_post+' => '', + 'Class:EventIssue/Attribute:arguments_get' => 'Argomenti URL', + 'Class:EventIssue/Attribute:arguments_get+' => '', + 'Class:EventIssue/Attribute:callstack' => 'Callstack', + 'Class:EventIssue/Attribute:callstack+' => '', + 'Class:EventIssue/Attribute:data' => 'Dati', + 'Class:EventIssue/Attribute:data+' => '', + 'Class:EventWebService' => 'Evento Servizio Web', + 'Class:EventWebService+' => '', + 'Class:EventWebService/Attribute:verb' => 'Verbo', + 'Class:EventWebService/Attribute:verb+' => '', + 'Class:EventWebService/Attribute:result' => 'Resulto', + 'Class:EventWebService/Attribute:result+' => '', + 'Class:EventWebService/Attribute:log_info' => 'Log delle info', + 'Class:EventWebService/Attribute:log_info+' => '', + 'Class:EventWebService/Attribute:log_warning' => 'Log dei warning', + 'Class:EventWebService/Attribute:log_warning+' => '', + 'Class:EventWebService/Attribute:log_error' => 'Log degli errori', + 'Class:EventWebService/Attribute:log_error+' => '', + 'Class:EventWebService/Attribute:data' => 'Dati', + 'Class:EventWebService/Attribute:data+' => '', + 'Class:EventLoginUsage' => 'Login di utilizzo', + 'Class:EventLoginUsage+' => '', + 'Class:EventLoginUsage/Attribute:user_id' => 'Login', + 'Class:EventLoginUsage/Attribute:user_id+' => '', + 'Class:SynchroDataSource' => 'Sorgente di sincronizzazione dei dati', + 'Class:SynchroDataSource/Attribute:name' => 'Nome', + 'Class:SynchroDataSource/Attribute:name+' => '', + 'Class:SynchroDataSource/Attribute:description' => 'Descrizione', + 'Class:SynchroDataSource/Attribute:status' => 'Stato', + 'Class:SynchroDataSource/Attribute:status/Value:implementation' => 'Implementazione', + 'Class:SynchroDataSource/Attribute:status/Value:obsolete' => 'Obsoleto', + 'Class:SynchroDataSource/Attribute:status/Value:production' => 'Produzione', + 'Class:SynchroDataSource/Attribute:user_id' => 'Utente', + 'Class:SynchroDataSource/Attribute:scope_class' => 'Classe Target', + 'Class:SynchroDataSource/Attribute:scope_restriction' => 'Campo di applicazione restrizione', + 'Class:SynchroDataSource/Attribute:full_load_periodicity' => 'Intervallo a pieno carico', + 'Class:SynchroDataSource/Attribute:full_load_periodicity+' => '', + 'Class:SynchroDataSource/Attribute:reconciliation_policy' => 'Policy di riconciliazione', + 'Class:SynchroDataSource/Attribute:reconciliation_policy/Value:use_attributes' => 'Usa gli attributi', + 'Class:SynchroDataSource/Attribute:reconciliation_policy/Value:use_primary_key' => 'Usa il campo chiave primaria', + 'Class:SynchroDataSource/Attribute:action_on_zero' => 'Azione su zero~~', + 'Class:SynchroDataSource/Attribute:action_on_zero+' => '', + 'Class:SynchroDataSource/Attribute:action_on_zero/Value:create' => 'Crea', + 'Class:SynchroDataSource/Attribute:action_on_zero/Value:error' => 'Errore', + 'Class:SynchroDataSource/Attribute:action_on_one' => 'Azione su uno', + 'Class:SynchroDataSource/Attribute:action_on_one+' => '', + 'Class:SynchroDataSource/Attribute:action_on_one/Value:error' => 'Error', + 'Class:SynchroDataSource/Attribute:action_on_one/Value:update' => 'Aggiorna', + 'Class:SynchroDataSource/Attribute:action_on_multiple' => 'Azione su molti', + 'Class:SynchroDataSource/Attribute:action_on_multiple+' => '', + 'Class:SynchroDataSource/Attribute:action_on_multiple/Value:create' => 'Crea', + 'Class:SynchroDataSource/Attribute:action_on_multiple/Value:error' => 'Errore', + 'Class:SynchroDataSource/Attribute:action_on_multiple/Value:take_first' => 'Considera il primo (random?)', + 'Class:SynchroDataSource/Attribute:delete_policy' => 'Policy di cancellazioen', + 'Class:SynchroDataSource/Attribute:delete_policy/Value:delete' => 'Cancella', + 'Class:SynchroDataSource/Attribute:delete_policy/Value:ignore' => 'Ignora', + 'Class:SynchroDataSource/Attribute:delete_policy/Value:update' => 'Aggiorna', + 'Class:SynchroDataSource/Attribute:delete_policy/Value:update_then_delete' => 'Aggiorna e poi Cancella', + 'Class:SynchroDataSource/Attribute:delete_policy_update' => 'Regole per l\'aggiornamento', + 'Class:SynchroDataSource/Attribute:delete_policy_update+' => '', + 'Class:SynchroDataSource/Attribute:delete_policy_retention' => 'Durata di conservazione', + 'Class:SynchroDataSource/Attribute:delete_policy_retention+' => '', + 'Class:SynchroDataSource/Attribute:attribute_list' => 'Lista degli attributi', + 'Class:SynchroDataSource/Attribute:user_delete_policy' => 'utenti autorizzati', + 'Class:SynchroDataSource/Attribute:user_delete_policy+' => '', + 'Class:SynchroDataSource/Attribute:user_delete_policy/Value:administrators' => 'Solo Amministratore', + 'Class:SynchroDataSource/Attribute:user_delete_policy/Value:everybody' => 'Tutti sono autorizzati a cancellare questi oggetti', + 'Class:SynchroDataSource/Attribute:user_delete_policy/Value:nobody' => 'Nessuno', + 'Class:SynchroDataSource/Attribute:url_icon' => 'Icona di collegamento ipertestuale', + 'Class:SynchroDataSource/Attribute:url_icon+' => '', + 'Class:SynchroDataSource/Attribute:url_application' => 'Collegamento ipertestuale all\'applicazione', + 'Class:SynchroDataSource/Attribute:url_application+' => '', + 'Class:SynchroAttribute' => 'Attributo di sincronizzazione', + 'Class:SynchroAttribute/Attribute:sync_source_id' => 'Sorgente sincronizzazione dati', + 'Class:SynchroAttribute/Attribute:attcode' => 'Codice attributo', + 'Class:SynchroAttribute/Attribute:update' => 'Aggiorna', + 'Class:SynchroAttribute/Attribute:reconcile' => 'Rincocilia', + 'Class:SynchroAttribute/Attribute:update_policy' => 'Policy di aggiornamento', + 'Class:SynchroAttribute/Attribute:update_policy/Value:master_locked' => 'Bloccato', + 'Class:SynchroAttribute/Attribute:update_policy/Value:master_unlocked' => 'Sbloccato', + 'Class:SynchroAttribute/Attribute:update_policy/Value:write_if_empty' => 'Inizializza se vuoto', + 'Class:SynchroAttribute/Attribute:finalclass' => 'Classe', + 'Class:SynchroAttExtKey' => 'Attributo di sincronizzazione (ExtKey)', + 'Class:SynchroAttExtKey/Attribute:reconciliation_attcode' => 'Attributo di riconciliazione', + 'Class:SynchroAttLinkSet' => 'Attributo di sincronizzazione (Linkset)', + 'Class:SynchroAttLinkSet/Attribute:row_separator' => 'Separatore di righe', + 'Class:SynchroAttLinkSet/Attribute:attribute_separator' => 'Separatore di attributi', + 'Class:SynchroLog' => 'Log di sincronizzazione', + 'Class:SynchroLog/Attribute:sync_source_id' => 'Sorgente di sincronizzazione dati', + 'Class:SynchroLog/Attribute:start_date' => 'Data di inizio', + 'Class:SynchroLog/Attribute:end_date' => 'Data di fine', + 'Class:SynchroLog/Attribute:status' => 'Stato', + 'Class:SynchroLog/Attribute:status/Value:completed' => 'Completo', + 'Class:SynchroLog/Attribute:status/Value:error' => 'Errore', + 'Class:SynchroLog/Attribute:status/Value:running' => 'Ancora in esecuzione', + 'Class:SynchroLog/Attribute:stats_nb_replica_seen' => 'Nb replica viste', + 'Class:SynchroLog/Attribute:stats_nb_replica_total' => 'Nb replica totale', + 'Class:SynchroLog/Attribute:stats_nb_obj_deleted' => 'Nb oggetti cancellati', + 'Class:SynchroLog/Attribute:stats_nb_obj_deleted_errors' => 'Nb di errore durante la cancellazione', + 'Class:SynchroLog/Attribute:stats_nb_obj_obsoleted' => 'Nb oggetti obsoleti', + 'Class:SynchroLog/Attribute:stats_nb_obj_obsoleted_errors' => 'Nb di errori mentre obsoleta', + 'Class:SynchroLog/Attribute:stats_nb_obj_created' => 'Nb oggetti creati', + 'Class:SynchroLog/Attribute:stats_nb_obj_created_errors' => 'Nb di errori durante la creazione', + 'Class:SynchroLog/Attribute:stats_nb_obj_updated' => 'Nb oggetti aggiornati', + 'Class:SynchroLog/Attribute:stats_nb_obj_updated_errors' => 'Nb di errori durante l\'aggiornamento', + 'Class:SynchroLog/Attribute:stats_nb_replica_reconciled_errors' => 'Nb di errori durante la riconcilazione', + 'Class:SynchroLog/Attribute:stats_nb_replica_disappeared_no_action' => 'Nb repliche scomparse', + 'Class:SynchroLog/Attribute:stats_nb_obj_new_updated' => 'Nb oggetti aggiornati', + 'Class:SynchroLog/Attribute:stats_nb_obj_new_unchanged' => 'Nb oggetti non modificati', + 'Class:SynchroLog/Attribute:last_error' => 'Untimo errore', + 'Class:SynchroLog/Attribute:traces' => 'Tracce', + 'Class:SynchroReplica' => 'Replica sincronizzazione', + 'Class:SynchroReplica/Attribute:sync_source_id' => 'Sorgente di sincronizzazione dati', + 'Class:SynchroReplica/Attribute:dest_id' => 'Oggetto di destinazione (ID)~~', + 'Class:SynchroReplica/Attribute:dest_class' => 'Tipo di destinazione', + 'Class:SynchroReplica/Attribute:status_last_seen' => 'Ultimo visto', + 'Class:SynchroReplica/Attribute:status' => 'Stato', + 'Class:SynchroReplica/Attribute:status/Value:modified' => 'Modificato', + 'Class:SynchroReplica/Attribute:status/Value:new' => 'Nuovo', + 'Class:SynchroReplica/Attribute:status/Value:obsolete' => 'Obsoleto', + 'Class:SynchroReplica/Attribute:status/Value:orphan' => 'Orfano', + 'Class:SynchroReplica/Attribute:status/Value:synchronized' => 'Sincronizzato', + 'Class:SynchroReplica/Attribute:status_dest_creator' => 'Oggetto Creato ?', + 'Class:SynchroReplica/Attribute:status_last_error' => 'Ultimo Errore', + 'Class:SynchroReplica/Attribute:info_creation_date' => 'Data di creazione', + 'Class:SynchroReplica/Attribute:info_last_modified' => 'Data ultima modifica', + 'Class:appUserPreferences' => 'Preferenze utente', + 'Class:appUserPreferences/Attribute:userid' => 'Utente', + 'Class:appUserPreferences/Attribute:preferences' => 'Preferenze', + 'Core:AttributeLinkedSet' => 'Array di oggetti', + 'Core:AttributeLinkedSet+' => '', + 'Core:AttributeLinkedSetIndirect' => 'Array di oggetti (N-N)', + 'Core:AttributeLinkedSetIndirect+' => '', + 'Core:AttributeInteger' => 'Integero', + 'Core:AttributeInteger+' => '', + 'Core:AttributeDecimal' => 'Decimale', + 'Core:AttributeDecimal+' => '', + 'Core:AttributeBoolean' => 'Booleano', + 'Core:AttributeBoolean+' => '', + 'Core:AttributeString' => 'Stringa', + 'Core:AttributeString+' => '', + 'Core:AttributeClass' => 'Classe', + 'Core:AttributeClass+' => '', + 'Core:AttributeApplicationLanguage' => 'Linguaggio Utente', + 'Core:AttributeApplicationLanguage+' => '', + 'Core:AttributeFinalClass' => 'Classe (auto)', + 'Core:AttributeFinalClass+' => '', + 'Core:AttributePassword' => 'Password', + 'Core:AttributePassword+' => '', + 'Core:AttributeEncryptedString' => 'Stringa criptata', + 'Core:AttributeEncryptedString+' => '', + 'Core:AttributeText' => 'Testo', + 'Core:AttributeText+' => '', + 'Core:AttributeHTML' => 'HTML', + 'Core:AttributeHTML+' => '', + 'Core:AttributeEmailAddress' => 'Indirizzo Email', + 'Core:AttributeEmailAddress+' => '', + 'Core:AttributeIPAddress' => 'Indirizzo IP', + 'Core:AttributeIPAddress+' => '', + 'Core:AttributeOQL' => 'OQL', + 'Core:AttributeOQL+' => '', + 'Core:AttributeEnum' => 'Enum', + 'Core:AttributeEnum+' => '', + 'Core:AttributeTemplateString' => 'Stringa Template', + 'Core:AttributeTemplateString+' => '', + 'Core:AttributeTemplateText' => 'Testo Template', + 'Core:AttributeTemplateText+' => '', + 'Core:AttributeTemplateHTML' => 'HTML Template', + 'Core:AttributeTemplateHTML+' => '', + 'Core:AttributeDateTime' => 'Data/ora', + 'Core:AttributeDateTime+' => '', + 'Core:AttributeDate' => 'Data', + 'Core:AttributeDate+' => '', + 'Core:AttributeDeadline' => 'Scadenza', + 'Core:AttributeDeadline+' => '', + 'Core:AttributeExternalKey' => 'Chiave esterna', + 'Core:AttributeExternalKey+' => '', + 'Core:AttributeExternalField' => 'Campo esterno', + 'Core:AttributeExternalField+' => '', + 'Core:AttributeURL' => 'URL', + 'Core:AttributeURL+' => '', + 'Core:AttributeBlob' => 'Blob', + 'Core:AttributeBlob+' => '', + 'Core:AttributeOneWayPassword' => 'Password unidierzionale', + 'Core:AttributeOneWayPassword+' => '', + 'Core:AttributeTable' => 'Tabella', + 'Core:AttributeTable+' => '', + 'Core:AttributePropertySet' => 'Proprietà', + 'Core:AttributePropertySet+' => '', + 'Class:CMDBChangeOp/Attribute:date' => 'data', + 'Class:CMDBChangeOp/Attribute:date+' => '', + 'Class:CMDBChangeOp/Attribute:userinfo' => 'utente', + 'Class:CMDBChangeOp/Attribute:userinfo+' => '', + 'Change:ObjectCreated' => 'Oggetto creato', + 'Change:ObjectDeleted' => 'Oggetto cancellato', + 'Change:ObjectModified' => 'Object modificato', + 'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s imposatato a %2$s (valore precendente: %3$s)', + 'Change:AttName_SetTo' => '%1$s impostato a %2$s~~', + 'Change:Text_AppendedTo_AttName' => '%1$s allegato a %2$s~~', + 'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s moficato, valore precendente: %2$s', + 'Change:AttName_Changed' => '%1$s modificato', + 'Change:AttName_EntryAdded' => '%1$s modificato, nuova entrata aggiunta.', + 'Class:EventLoginUsage/Attribute:contact_name' => 'Nome Utente', + 'Class:EventLoginUsage/Attribute:contact_name+' => '', + 'Class:EventLoginUsage/Attribute:contact_email' => 'Email Utente', + 'Class:EventLoginUsage/Attribute:contact_email+' => '', + 'Class:Action' => 'Azione personalizzata', + 'Class:Action+' => '', + 'Class:Action/Attribute:name' => 'Nome', + 'Class:Action/Attribute:name+' => '', + 'Class:Action/Attribute:description' => 'Descrizione', + 'Class:Action/Attribute:description+' => '', + 'Class:Action/Attribute:status' => 'Stato', + 'Class:Action/Attribute:status+' => '', + 'Class:Action/Attribute:status/Value:test' => 'In fase di test', + 'Class:Action/Attribute:status/Value:test+' => '', + 'Class:Action/Attribute:status/Value:enabled' => 'In produzione', + 'Class:Action/Attribute:status/Value:enabled+' => '', + 'Class:Action/Attribute:status/Value:disabled' => 'Inattivo', + 'Class:Action/Attribute:status/Value:disabled+' => '', + 'Class:Action/Attribute:trigger_list' => 'Trigger Correlati', + 'Class:Action/Attribute:trigger_list+' => '', + 'Class:Action/Attribute:finalclass' => 'Tipo', + 'Class:Action/Attribute:finalclass+' => '', + 'Class:ActionNotification' => 'Notifica', + 'Class:ActionNotification+' => '', + 'Class:Trigger' => 'Trigger', + 'Class:Trigger+' => '', + 'Class:Trigger/Attribute:description' => 'Descrizione', + 'Class:Trigger/Attribute:description+' => '', + 'Class:Trigger/Attribute:action_list' => 'Azioni Triggerate', + 'Class:Trigger/Attribute:action_list+' => '', + 'Class:Trigger/Attribute:finalclass' => 'Tipo', + 'Class:Trigger/Attribute:finalclass+' => '', + 'Class:TriggerOnObject' => 'Trigger (classe dipendente)', + 'Class:TriggerOnObject+' => '', + 'Class:TriggerOnObject/Attribute:target_class' => 'Classe Target', + 'Class:TriggerOnObject/Attribute:target_class+' => '', + 'Class:TriggerOnStateChange' => 'Trigger (sul cambio di stato)', + 'Class:TriggerOnStateChange+' => '', + 'Class:TriggerOnStateChange/Attribute:state' => 'Stato', + 'Class:TriggerOnStateChange/Attribute:state+' => '', + 'Class:lnkTriggerAction/Attribute:action_name' => 'Azione', + 'Class:lnkTriggerAction/Attribute:action_name+' => '', + 'Class:lnkTriggerAction/Attribute:trigger_name' => 'Trigger', + 'Class:lnkTriggerAction/Attribute:trigger_name+' => '', + 'Class:SynchroDataSource/Attribute:delete_policy/Value:never' => 'Nessuno', + 'Class:SynchroDataSource/Attribute:delete_policy/Value:depends' => 'Solo Amministratore', + 'Class:SynchroDataSource/Attribute:delete_policy/Value:always' => 'Tutti gli utenti autorizzati', + 'SynchroDataSource:Description' => 'Descrizione', + 'SynchroDataSource:Reconciliation' => 'Ricerca & riconciliazione', + 'SynchroDataSource:Deletion' => 'Regole di cancellazione', + 'SynchroDataSource:Status' => 'Stato', + 'SynchroDataSource:Information' => 'Informazione', + 'SynchroDataSource:Definition' => 'Definizione', + 'Core:SynchroAttributes' => 'Attributi', + 'Core:SynchroStatus' => 'Stato', + 'Core:Synchro:ErrorsLabel' => 'Errori', + 'Core:Synchro:CreatedLabel' => 'Creato', + 'Core:Synchro:ModifiedLabel' => 'Modificato', + 'Core:Synchro:UnchangedLabel' => 'Non modificato', + 'Core:Synchro:ReconciledErrorsLabel' => 'Errori', + 'Core:Synchro:ReconciledLabel' => 'Reconciliato', + 'Core:Synchro:ReconciledNewLabel' => 'Creato', + 'Core:SynchroReconcile:Yes' => 'Si', + 'Core:SynchroReconcile:No' => 'No', + 'Core:SynchroUpdate:Yes' => 'Si', + 'Core:SynchroUpdate:No' => 'No', + 'Core:Synchro:LastestStatus' => 'Ultimo stato', + 'Core:Synchro:History' => 'Storia della sincronizzazione', + 'Core:Synchro:NeverRun' => 'Questa sincronizzazione non è mai stata eseguita. Nessun Log ancora...', + 'Core:Synchro:SynchroEndedOn_Date' => 'L\'ultima sincronizzazione si è conclusa il %1$s.~~', + 'Core:Synchro:SynchroRunningStartedOn_Date' => 'La sincronizzazione è iniziata il %1$s è ancora in esecuzione...~~', + 'Menu:DataSources' => 'Sorgente di sincronizzazione dei dati', + 'Menu:DataSources+' => '', + 'Core:Synchro:label_repl_ignored' => 'Ignorato(%1$s)', + 'Core:Synchro:label_repl_disappeared' => 'Scomparso (%1$s)', + 'Core:Synchro:label_repl_existing' => 'Esistente (%1$s)', + 'Core:Synchro:label_repl_new' => 'Nuovo (%1$s)~~', + 'Core:Synchro:label_obj_deleted' => 'Cancellato (%1$s)', + 'Core:Synchro:label_obj_obsoleted' => 'Obsoleto (%1$s)', + 'Core:Synchro:label_obj_disappeared_errors' => 'Errori (%1$s)', + 'Core:Synchro:label_obj_disappeared_no_action' => 'Nessuna Azione (%1$s)', + 'Core:Synchro:label_obj_unchanged' => 'Non modificato(%1$s)', + 'Core:Synchro:label_obj_updated' => 'Aggiornato (%1$s)', + 'Core:Synchro:label_obj_updated_errors' => 'Errori (%1$s)', + 'Core:Synchro:label_obj_new_unchanged' => 'Non modificato (%1$s)', + 'Core:Synchro:label_obj_new_updated' => 'Aggiornato (%1$s)', + 'Core:Synchro:label_obj_created' => 'Creato (%1$s)', + 'Core:Synchro:label_obj_new_errors' => 'Errori (%1$s)', + 'Core:SynchroLogTitle' => '%1$s - %2$s~~', + 'Core:Synchro:Nb_Replica' => 'Replica processata: %1$s', + 'Core:Synchro:Nb_Class:Objects' => '%1$s: %2$s', + 'Class:SynchroDataSource/Error:AtLeastOneReconciliationKeyMustBeSpecified' => 'Almeno una chiave riconciliazione deve essere specificata, o la policy di conciliazione deve essere quella di utilizzare la chiave primaria', + 'Class:SynchroDataSource/Error:DeleteRetentionDurationMustBeSpecified' => 'Deve essere specificato un periodo di conservazione di cancellazione , dato che gli oggetti devono essere eliminati dopo essere contrassegnati come obsoleti ', + 'Class:SynchroDataSource/Error:DeletePolicyUpdateMustBeSpecified' => 'Oggetti obsoleti devono essere aggiornati, ma nessun aggiornamento è specificato', + 'Core:SynchroReplica:PublicData' => 'Dati Pubblici', + 'Core:SynchroReplica:PrivateDetails' => 'Dettagli Privati', + 'Core:SynchroReplica:BackToDataSource' => 'Torna indietro alla sorgente di sincronizzazione dei dati: %1$s~~', + 'Core:SynchroReplica:ListOfReplicas' => 'Lista della Replica', + 'Core:SynchroAttExtKey:ReconciliationById' => 'id (Chiave Primaria)', + 'Core:SynchroAtt:attcode' => 'Attributo', + 'Core:SynchroAtt:attcode+' => '', + 'Core:SynchroAtt:reconciliation' => 'Riconciliazione ?~~', + 'Core:SynchroAtt:reconciliation+' => '', + 'Core:SynchroAtt:update' => 'Aggiornamento ?~~', + 'Core:SynchroAtt:update+' => '', + 'Core:SynchroAtt:update_policy' => 'Policy di aggiornamento', + 'Core:SynchroAtt:update_policy+' => '', + 'Core:SynchroAtt:reconciliation_attcode' => 'Chiave di riconciliazione', + 'Core:SynchroAtt:reconciliation_attcode+' => '', + 'Core:SyncDataExchangeComment' => '(Scambio dati)', + 'Core:Synchro:ListOfDataSources' => 'Lista delle sorgenti di dati:', + 'Core:Synchro:LastSynchro' => 'Ultima sincronizzazione:', + 'Core:Synchro:ThisObjectIsSynchronized' => 'Questo oggetto è sincronizzato con una sorgente esterna di dati', + 'Core:Synchro:TheObjectWasCreatedBy_Source' => 'L\'oggetti è stato creato da una sorgente esterna di dati %1$s~~', + 'Core:Synchro:TheObjectCanBeDeletedBy_Source' => 'L\'oggetti può essere cancellato da una sorgente esterna di dati %1$s~~', + 'Core:Synchro:TheObjectCannotBeDeletedByUser_Source' => 'Tu non puoi cancellare l\'oggetto perché è di proprietà della sorgente dati esterna %1$s~~', + 'TitleSynchroExecution' => 'Esecuzione della sincronizzazione', + 'Class:SynchroDataSource:DataTable' => 'Tabella del database: %1$s', + 'Core:SyncDataSourceObsolete' => 'La fonte dei dati è contrassegnata come obsoleta. Operazione annullata', + 'Core:SyncDataSourceAccessRestriction' => 'Solo amministratori o l\'utente specificato nella fonte dei dati può eseguire questa operazione. Operazione annullata', + 'Core:SyncTooManyMissingReplicas' => 'Tutte le repliche sono mancanti dall\'importazione. Hai eseguito realmente l\'importazione? Operazione annullata', + 'Core:Duration_Seconds' => '%1$ds', + 'Core:Duration_Minutes_Seconds' => '%1$dmin %2$ds', + 'Core:Duration_Hours_Minutes_Seconds' => '%1$dh %2$dmin %3$sec~~', + 'Core:Duration_Days_Hours_Minutes_Seconds' => '%1$sg %2$dh %3$dmin %4$ds~~', +)); +?> diff --git a/dictionaries/ja.dictionary.itop.ui.php b/dictionaries/ja.dictionary.itop.ui.php index 31705c133..fbebc17f0 100644 --- a/dictionaries/ja.dictionary.itop.ui.php +++ b/dictionaries/ja.dictionary.itop.ui.php @@ -588,7 +588,7 @@ Dict::Add('JA JP', 'Japanese', '日本語', array( 'UI:Schema:Triggers' => 'トリガ', //'Triggers', 'UI:Schema:Relation_Code_Description' => 'リレーション %1$s (%2$s)', //'Relation %1$s (%2$s)', 'UI:Schema:RelationDown_Description' => '下へ: %1$s', //'Down: %1$s', - 'UI:Schema:RelationUp_Description' => '上へ: $1$s', //'Up: %1$s', + 'UI:Schema:RelationUp_Description' => '上へ: %1$s', //'Up: %1$s', 'UI:Schema:RelationPropagates' => '%1$s: %2$d レベルへ伝播、クエリ:%3$s', //'%1$s: propagate to %2$d levels, query: %3$s', 'UI:Schema:RelationDoesNotPropagate' => '%1$s: 伝播しない (%2$d レベル), クエリ: %3$s', //'%1$s: does not propagates (%2$d levels), query: %3$s', 'UI:Schema:Class_ReferencingClasses_From_By' => '%1$s は%2$s クラスから %3$s フィールドにより参照されている', //'%1$s is referenced by the class %2$s via the field %3$s', @@ -613,7 +613,7 @@ Dict::Add('JA JP', 'Japanese', '日本語', array( 'UI:Link_Class_Attributes' => '%1$s 属性', //'%1$s attributes', 'UI:SelectAllToggle+' => '全部を選択 / 全部を非選択', //'Select All / Deselect All', 'UI:AddObjectsOf_Class_LinkedWith_Class_Instance' => '%2$s にリンクされた%1$sオブジェクトを追加:%3$s', //'Add %1$s objects linked with %2$s: %3$s', - 'UI:AddObjectsOf_Class_LinkedWith_Class' => '$1$s オブジェクトを%2$sとのリンクに追加', //'Add %1$s objects to link with the %2$s', + 'UI:AddObjectsOf_Class_LinkedWith_Class' => '%1$s オブジェクトを%2$sとのリンクに追加', //'Add %1$s objects to link with the %2$s', 'UI:ManageObjectsOf_Class_LinkedWith_Class_Instance' => '%2$s とりんくされた%1$sオブジェクトを管理する: %3$s', //'Manage %1$s objects linked with %2$s: %3$s', 'UI:AddLinkedObjectsOf_Class' => '%1$s を追加...', //'Add %1$ss...', 'UI:RemoveLinkedObjectsOf_Class' => '選択したオブジェクトを除外', //'Remove selected objects', @@ -860,8 +860,8 @@ Dict::Add('JA JP', 'Japanese', '日本語', array( 'UI:iTopVersion:Long' => 'iTopバージョン%1$s-%2$s, %3$sビルド', // 'iTop version %1$s-%2$s built on %3$s', 'UI:PropertiesTab' => 'プロパティ', // 'Properties', - 'UI:OpenDocumentInNewWindow_' => '新規ウィンドウで本ドキュメント: $1$sを開く', // 'Open this document in a new window: %1$s', - 'UI:DownloadDocument_' => '本ドキュメント: $1$sをダウンロードする', // 'Download this document: %1$s', + 'UI:OpenDocumentInNewWindow_' => '新規ウィンドウで本ドキュメント: %1$sを開く', // 'Open this document in a new window: %1$s', + 'UI:DownloadDocument_' => '本ドキュメント: %1$sをダウンロードする', // 'Download this document: %1$s', 'UI:Document:NoPreview' => 'このタイプのドキュメントはプレビューできません。', // 'No preview is available for this type of document', 'UI:DeadlineMissedBy_duration' => '%1$s によって消去されました。', // 'Missed by %1$s', diff --git a/dictionaries/pt_br.dictionary.itop.core.php b/dictionaries/pt_br.dictionary.itop.core.php index cbfd78541..f3efe7107 100644 --- a/dictionaries/pt_br.dictionary.itop.core.php +++ b/dictionaries/pt_br.dictionary.itop.core.php @@ -429,7 +429,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Core:Synchro:History' => 'Histórico de sincronização', 'Core:Synchro:NeverRun' => 'Este sincronismo nunca foi executado. Sem registo ainda.', 'Core:Synchro:SynchroEndedOn_Date' => 'A última sincronização terminou em% 1 $ s.', - 'Core:Synchro:SynchroRunningStartedOn_Date' => 'A sincronização começou em $1$s e em execução ainda...', + 'Core:Synchro:SynchroRunningStartedOn_Date' => 'A sincronização começou em %1$s e em execução ainda...', 'Menu:DataSources' => 'Fontes de dados de sincronização', 'Menu:DataSources+' => '', 'Core:Synchro:label_repl_ignored' => 'Ignorado (%1$s)', diff --git a/synchro/priv_sync_chunk.php b/synchro/priv_sync_chunk.php new file mode 100644 index 000000000..132cd7d74 --- /dev/null +++ b/synchro/priv_sync_chunk.php @@ -0,0 +1,135 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__)); +require_once(__DIR__.'/../approot.inc.php'); +require_once(APPROOT.'/application/application.inc.php'); +require_once(APPROOT.'/application/webpage.class.inc.php'); +require_once(APPROOT.'/application/csvpage.class.inc.php'); +require_once(APPROOT.'/application/clipage.class.inc.php'); + +require_once(APPROOT.'/application/startup.inc.php'); + + +function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter = 'parameter') +{ + $sValue = utils::ReadParam($sParam, null, true /* Allow CLI */, $sSanitizationFilter); + if (is_null($sValue)) + { + $oP->p("ERROR: Missing argument '$sParam'\n"); + exit(29); + } + return trim($sValue); +} + +///////////////////////////////// +// Main program + +if (!utils::IsModeCLI()) +{ + $oP = new WebPage(Dict::S("TitleSynchroExecution")); + $oP->p("This page is used internally by iTop"); + $oP->output(); + exit -2; +} + +$oP = new CLIPage(Dict::S("TitleSynchroExecution")); + +try +{ + utils::UseParamFile(); +} +catch(Exception $e) +{ + $oP->p("Error: ".$e->GetMessage()); + $oP->output(); + exit -2; +} + +// Next steps: +// specific arguments: 'csvfile' +// +$sAuthUser = ReadMandatoryParam($oP, 'auth_user', 'raw_data'); +$sAuthPwd = ReadMandatoryParam($oP, 'auth_pwd', 'raw_data'); +if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) +{ + UserRights::Login($sAuthUser); // Login & set the user's language +} +else +{ + $oP->p("Access restricted or wrong credentials ('$sAuthUser')"); + $oP->output(); + exit -1; +} + +$iStepCount = ReadMandatoryParam($oP, 'step_count'); +$oP->p('Executing a partial synchro - step '.$iStepCount); + +$iSource = ReadMandatoryParam($oP, 'source'); + +$iStatLog = ReadMandatoryParam($oP, 'log'); +$iChange = ReadMandatoryParam($oP, 'change'); +$sLastFullLoad = ReadMandatoryParam($oP, 'last_full_load', 'raw_data'); +$iChunkSize = ReadMandatoryParam($oP, 'chunk'); + +$oP->p('Last full load: '.$sLastFullLoad); +$oP->p('Chunk size: '.$iChunkSize); +$oP->p('Source: '.$iSource); + +try +{ + $oSynchroDataSource = MetaModel::GetObject('SynchroDataSource', $iSource); + $oLog = MetaModel::GetObject('SynchroLog', $iStatLog); + $oChange = MetaModel::GetObject('CMDBChange', $iChange); + + if (strlen($sLastFullLoad) > 0) + { + $oLastFullLoad = new DateTime($sLastFullLoad); + $oSynchroExec = new SynchroExecution($oSynchroDataSource, $oLastFullLoad); + } + else + { + $oSynchroExec = new SynchroExecution($oSynchroDataSource); + } + if ($oSynchroExec->DoSynchronizeChunk($oLog, $oChange, $iChunkSize)) + { + // The last line MUST follow this convention + $oP->p("continue"); + } + else + { + // The last line MUST follow this convention + $oP->p("finished"); + } + $oP->output(); +} +catch(Exception $e) +{ + $oP->p("Error: ".$e->GetMessage()); + $oP->add($e->getTraceAsString()); + $oP->output(); + exit(28); +} +?> diff --git a/synchro/synchro_exec.php b/synchro/synchro_exec.php index ad732da3a..9d0025850 100644 --- a/synchro/synchro_exec.php +++ b/synchro/synchro_exec.php @@ -51,7 +51,7 @@ function UsageAndExit($oP) if ($bModeCLI) { $oP->p("USAGE:\n"); - $oP->p("php -q synchro_exec.php --auth_user= --auth_pwd= --data_sources=\n"); + $oP->p("php -q synchro_exec.php --auth_user= --auth_pwd= --data_sources= [max_chunk_size=]\n"); } else { @@ -147,7 +147,8 @@ foreach(explode(',', $sDataSourcesList) as $iSDS) } try { - $oStatLog = $oSynchroDataSource->Synchronize(null); + $oSynchroExec = new SynchroExecution($oSynchroDataSource); + $oStatLog = $oSynchroExec->Process(); if ($bSimulate) { CMDBSource::Query('ROLLBACK'); diff --git a/synchro/synchro_import.php b/synchro/synchro_import.php index e79ca665a..b4d8988b0 100644 --- a/synchro/synchro_import.php +++ b/synchro/synchro_import.php @@ -120,6 +120,13 @@ $aPageParams = array 'default' => 'summary', 'description' => '[retcode] to return the count of lines in error, [summary] to return a concise report, [details] to get a detailed report (each line listed)', ), + 'max_chunk_size' => array + ( + 'mandatory' => false, + 'modes' => 'cli', + 'default' => '0', + 'description' => 'Limit on the count of records that can be loaded at once while performing the synchronization', + ), /* 'reportlevel' => array ( @@ -613,7 +620,8 @@ try // if ($bSynchronize) { - $oStatLog = $oDataSource->Synchronize($oLoadStartDate); + $oSynchroExec = new SynchroExecution($oDataSource, $oLoadStartDate); + $oStatLog = $oSynchroExec->Process(); $oP->add_comment('Synchronization---'); $oP->add_comment('------------------'); if ($sOutput == 'details') diff --git a/synchro/synchrodatasource.class.inc.php b/synchro/synchrodatasource.class.inc.php index 5ba253b08..605a86961 100644 --- a/synchro/synchrodatasource.class.inc.php +++ b/synchro/synchrodatasource.class.inc.php @@ -98,19 +98,6 @@ class SynchroDataSource extends cmdbAbstractObject // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form } - public static $m_oCurrentTask = null; - public static function GetCurrentTaskId() - { - if (is_object(self::$m_oCurrentTask)) - { - return self::$m_oCurrentTask->GetKey(); - } - else - { - return null; - } - } - public function DisplayBareRelations(WebPage $oPage, $bEditMode = false) { if (!$this->IsNew()) @@ -301,6 +288,7 @@ class SynchroDataSource extends cmdbAbstractObject function UpdateSynoptics(id) { var aValues = aSynchroLog[id]; + if (aValues == undefined) return; for (var sKey in aValues) { @@ -913,7 +901,8 @@ EOF return $bFixNeeded; } - protected function SendNotification($sSubject, $sBody) + + public function SendNotification($sSubject, $sBody) { $iContact = $this->Get('notify_contact_id'); if ($iContact == 0) @@ -957,307 +946,6 @@ EOF } } - /** - * Perform a synchronization between the data stored in the replicas (&synchro_data_xxx_xx table) - * and the iTop objects. If the lastFullLoadStartDate is NOT specified then the full_load_periodicity - * is used to determine which records are obsolete. - * @param Hash $aTraces Debugs/Trace information, one or more entries per replica - * @param DateTime $oLastFullLoadStartDate Date of the last full load (start date/time), if known - * @return void - */ - public function Synchronize($oLastFullLoadStartDate = null) - { - // Create a change used for logging all the modifications/creations happening during the synchro - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $sUserString = CMDBChange::GetCurrentUserName(); - $oMyChange->Set("userinfo", $sUserString.' '.Dict::S('Core:SyncDataExchangeComment')); - $iChangeId = $oMyChange->DBInsert(); - - // Start logging this execution (stats + protection against reentrance) - // - $oStatLog = new SynchroLog(); - $oStatLog->Set('sync_source_id', $this->GetKey()); - $oStatLog->Set('start_date', time()); - $oStatLog->Set('status', 'running'); - $oStatLog->Set('stats_nb_replica_seen', 0); - $oStatLog->Set('stats_nb_replica_total', 0); - $oStatLog->Set('stats_nb_obj_deleted', 0); - $oStatLog->Set('stats_nb_obj_deleted_errors', 0); - $oStatLog->Set('stats_nb_obj_obsoleted', 0); - $oStatLog->Set('stats_nb_obj_obsoleted_errors', 0); - $oStatLog->Set('stats_nb_obj_created', 0); - $oStatLog->Set('stats_nb_obj_created_errors', 0); - $oStatLog->Set('stats_nb_obj_created_warnings', 0); - $oStatLog->Set('stats_nb_obj_updated', 0); - $oStatLog->Set('stats_nb_obj_updated_warnings', 0); - $oStatLog->Set('stats_nb_obj_updated_errors', 0); - $oStatLog->Set('stats_nb_obj_unchanged_warnings', 0); - // $oStatLog->Set('stats_nb_replica_reconciled', 0); - $oStatLog->Set('stats_nb_replica_reconciled_errors', 0); - $oStatLog->Set('stats_nb_replica_disappeared_no_action', 0); - $oStatLog->Set('stats_nb_obj_new_updated', 0); - $oStatLog->Set('stats_nb_obj_new_updated_warnings', 0); - $oStatLog->Set('stats_nb_obj_new_unchanged',0); - $oStatLog->Set('stats_nb_obj_new_unchanged_warnings',0); - - $sSelectTotal = "SELECT SynchroReplica WHERE sync_source_id = :source_id"; - $oSetTotal = new DBObjectSet(DBObjectSearch::FromOQL($sSelectTotal), array() /* order by*/, array('source_id' => $this->GetKey())); - $oStatLog->Set('stats_nb_replica_total', $oSetTotal->Count()); - - $oStatLog->DBInsertTracked($oMyChange); - - self::$m_oCurrentTask = $this; - try - { - $this->DoSynchronize($oLastFullLoadStartDate, $oMyChange, $oStatLog); - $oStatLog->Set('end_date', time()); - $oStatLog->Set('status', 'completed'); - $oStatLog->DBUpdateTracked($oMyChange); - - $iErrors = $oStatLog->GetErrorCount(); - if ($iErrors > 0) - { - $sIssuesOQL = "SELECT SynchroReplica WHERE sync_source_id=".$this->GetKey()." AND status_last_error!=''"; - $sAbsoluteUrl = utils::GetAbsoluteUrlAppRoot(); - $sIssuesURL = "{$sAbsoluteUrl}synchro/replica.php?operation=oql&datasource=".$this->GetKey()."&oql=".urlencode($sIssuesOQL); - $sSeeIssues = "

"; - - $sStatistics = "

Statistics

\n"; - $sStatistics .= "
    \n"; - $sStatistics .= "
  • ".$oStatLog->GetLabel('start_date').": ".$oStatLog->Get('start_date')."
  • \n"; - $sStatistics .= "
  • ".$oStatLog->GetLabel('end_date').": ".$oStatLog->Get('end_date')."
  • \n"; - $sStatistics .= "
  • ".$oStatLog->GetLabel('stats_nb_replica_seen').": ".$oStatLog->Get('stats_nb_replica_seen')."
  • \n"; - $sStatistics .= "
  • ".$oStatLog->GetLabel('stats_nb_replica_total').": ".$oStatLog->Get('stats_nb_replica_total')."
  • \n"; - $sStatistics .= "
  • ".$oStatLog->GetLabel('stats_nb_obj_deleted').": ".$oStatLog->Get('stats_nb_obj_deleted')."
  • \n"; - $sStatistics .= "
  • ".$oStatLog->GetLabel('stats_nb_obj_deleted_errors').": ".$oStatLog->Get('stats_nb_obj_deleted_errors')."
  • \n"; - $sStatistics .= "
  • ".$oStatLog->GetLabel('stats_nb_obj_obsoleted').": ".$oStatLog->Get('stats_nb_obj_obsoleted')."
  • \n"; - $sStatistics .= "
  • ".$oStatLog->GetLabel('stats_nb_obj_obsoleted_errors').": ".$oStatLog->Get('stats_nb_obj_obsoleted_errors')."
  • \n"; - $sStatistics .= "
  • ".$oStatLog->GetLabel('stats_nb_obj_created').": ".$oStatLog->Get('stats_nb_obj_created')." (".$oStatLog->Get('stats_nb_obj_created_warnings')." warnings)"."
  • \n"; - $sStatistics .= "
  • ".$oStatLog->GetLabel('stats_nb_obj_created_errors').": ".$oStatLog->Get('stats_nb_obj_created_errors')."
  • \n"; - $sStatistics .= "
  • ".$oStatLog->GetLabel('stats_nb_obj_updated').": ".$oStatLog->Get('stats_nb_obj_updated')." (".$oStatLog->Get('stats_nb_obj_updated_warnings')." warnings)"."
  • \n"; - $sStatistics .= "
  • ".$oStatLog->GetLabel('stats_nb_obj_updated_errors').": ".$oStatLog->Get('stats_nb_obj_updated_errors')."
  • \n"; - $sStatistics .= "
  • ".$oStatLog->GetLabel('stats_nb_replica_reconciled_errors').": ".$oStatLog->Get('stats_nb_replica_reconciled_errors')."
  • \n"; - $sStatistics .= "
  • ".$oStatLog->GetLabel('stats_nb_replica_disappeared_no_action').": ".$oStatLog->Get('stats_nb_replica_disappeared_no_action')."
  • \n"; - $sStatistics .= "
  • ".$oStatLog->GetLabel('stats_nb_obj_new_updated').": ".$oStatLog->Get('stats_nb_obj_new_updated')." (".$oStatLog->Get('stats_nb_obj_new_updated_warnings')." warnings)"."
  • \n"; - $sStatistics .= "
  • ".$oStatLog->GetLabel('stats_nb_obj_new_unchanged').": ".$oStatLog->Get('stats_nb_obj_new_unchanged')." (".$oStatLog->Get('stats_nb_obj_new_unchanged_warnings')." warnings)"."
  • \n"; - $sStatistics .= "
\n"; - - $this->SendNotification("errors ($iErrors)", "

The synchronization has been executed, $iErrors errors have been encountered. Click here to see the records being currently in error.

".$sStatistics); - } - else - { - //$this->SendNotification('success', '

The synchronization has been successfully executed.

'); - } - } - catch (SynchroExceptionNotStarted $e) - { - // Set information for reporting... but delete the object in DB - $oStatLog->Set('end_date', time()); - $oStatLog->Set('status', 'error'); - $oStatLog->Set('last_error', $e->getMessage()); - $oStatLog->DBDeleteTracked($oMyChange); - $this->SendNotification('fatal error', '

The synchronization could not start: \''.$e->getMessage().'\'

Please check its configuration

'); - } - catch (Exception $e) - { - $oStatLog->Set('end_date', time()); - $oStatLog->Set('status', 'error'); - $oStatLog->Set('last_error', $e->getMessage()); - $oStatLog->DBUpdateTracked($oMyChange); - $this->SendNotification('exception', '

The synchronization has been interrupted: \''.$e->getMessage().'\'

Please contact the application support team

'); - } - self::$m_oCurrentTask = null; - - return $oStatLog; - } - - protected function DoSynchronize($oLastFullLoadStartDate, $oMyChange, &$oStatLog) - { - if ($this->Get('status') == 'obsolete') - { - throw new SynchroExceptionNotStarted(Dict::S('Core:SyncDataSourceObsolete')); - } - if (!UserRights::IsAdministrator() && $this->Get('user_id') != UserRights::GetUserId()) - { - throw new SynchroExceptionNotStarted(Dict::S('Core:SyncDataSourceAccessRestriction')); - } - - // Get the list of SQL columns - $sClass = $this->GetTargetClass(); - $aAttCodesExpected = array(); - $aAttCodesToReconcile = array(); - $aAttCodesToUpdate = array(); - $sSelectAtt = "SELECT SynchroAttribute WHERE sync_source_id = :source_id AND (update = 1 OR reconcile = 1)"; - $oSetAtt = new DBObjectSet(DBObjectSearch::FromOQL($sSelectAtt), array() /* order by*/, array('source_id' => $this->GetKey()) /* aArgs */); - while ($oSyncAtt = $oSetAtt->Fetch()) - { - if ($oSyncAtt->Get('update')) - { - $aAttCodesToUpdate[$oSyncAtt->Get('attcode')] = $oSyncAtt; - } - if ($oSyncAtt->Get('reconcile')) - { - $aAttCodesToReconcile[$oSyncAtt->Get('attcode')] = $oSyncAtt; - } - $aAttCodesExpected[$oSyncAtt->Get('attcode')] = $oSyncAtt; - } - $aColumns = $this->GetSQLColumns(array_keys($aAttCodesExpected)); - $aExtDataFields = array_keys($aColumns); - $aExtDataFields[] = 'primary_key'; - $aExtDataSpec = array( - 'table' => $this->GetDataTable(), - 'join_key' => 'id', - 'fields' => $aExtDataFields - ); - - // Get the list of attributes, determine reconciliation keys and update targets - // - if ($this->Get('reconciliation_policy') == 'use_attributes') - { - $aReconciliationKeys = $aAttCodesToReconcile; - } - elseif ($this->Get('reconciliation_policy') == 'use_primary_key') - { - // Override the settings made at the attribute level ! - $aReconciliationKeys = array("primary_key" => null); - } - - $oStatLog->AddTrace("Update of: {".implode(', ', array_keys($aAttCodesToUpdate))."}"); - $oStatLog->AddTrace("Reconciliation on: {".implode(', ', array_keys($aReconciliationKeys))."}"); - - if (count($aAttCodesToUpdate) == 0) - { - $oStatLog->AddTrace("No attribute to update"); - throw new SynchroExceptionNotStarted('There is no attribute to update'); - } - if (count($aReconciliationKeys) == 0) - { - $oStatLog->AddTrace("No attribute for reconciliation"); - throw new SynchroExceptionNotStarted('No attribute for reconciliation'); - } - - $aAttributes = array(); - foreach($aAttCodesToUpdate as $sAttCode => $oSyncAtt) - { - $oAttDef = MetaModel::GetAttributeDef($this->GetTargetClass(), $sAttCode); - if ($oAttDef->IsWritable()) - { - $aAttributes[$sAttCode] = $oSyncAtt; - } - } - - // Count the replicas - $sSelectAll = "SELECT SynchroReplica WHERE sync_source_id = :source_id"; - $oSetAll = new DBObjectSet(DBObjectSearch::FromOQL($sSelectAll), array() /* order by*/, array('source_id' => $this->GetKey())); - $iCountAllReplicas = $oSetAll->Count(); - $oStatLog->Set('stats_nb_replica_total', $iCountAllReplicas); - - // Get all the replicas that were not seen in the last import and mark them as obsolete - if ($oLastFullLoadStartDate == null) - { - // No previous import known, use the full_load_periodicity value... and the current date - $oLastFullLoadStartDate = new DateTime(); // Now - $iLoadPeriodicity = $this->Get('full_load_periodicity'); // Duration in seconds - if ($iLoadPeriodicity > 0) - { - $sInterval = "-$iLoadPeriodicity seconds"; - $oLastFullLoadStartDate->Modify($sInterval); - } - else - { - $oLastFullLoadStartDate = new DateTime('1970-01-01'); - } - } - $sLimitDate = $oLastFullLoadStartDate->Format('Y-m-d H:i:s'); - $oStatLog->AddTrace("Limit Date: $sLimitDate"); - - $sDeletePolicy = $this->Get('delete_policy'); - - if ($sDeletePolicy != 'ignore') - { - $sSelectToObsolete = "SELECT SynchroReplica WHERE sync_source_id = :source_id AND status IN ('new', 'synchronized', 'modified', 'orphan') AND status_last_seen < :last_import"; - $oSetToObsolete = new DBObjectSet(DBObjectSearch::FromOQL($sSelectToObsolete), array() /* order by*/, array('source_id' => $this->GetKey(), 'last_import' => $sLimitDate)); - if (($iCountAllReplicas > 10) && ($iCountAllReplicas == $oSetToObsolete->Count())) - { - throw new SynchroExceptionNotStarted(Dict::S('Core:SyncTooManyMissingReplicas')); - } - while($oReplica = $oSetToObsolete->Fetch()) - { - switch ($sDeletePolicy) - { - case 'update': - case 'update_then_delete': - $oStatLog->AddTrace("Destination object to be updated", $oReplica); - $aToUpdate = array(); - $aToUpdateSpec = explode(';', $this->Get('delete_policy_update')); //ex: 'status:obsolete;description:stopped', - foreach($aToUpdateSpec as $sUpdateSpec) - { - $aUpdateSpec = explode(':', $sUpdateSpec); - if (count($aUpdateSpec) == 2) - { - $sAttCode = $aUpdateSpec[0]; - $sValue = $aUpdateSpec[1]; - $aToUpdate[$sAttCode] = $sValue; - } - } - $oReplica->Set('status_last_error', ''); - $oReplica->UpdateDestObject($aToUpdate, $oMyChange, $oStatLog); - if ($oReplica->Get('status_last_error') == '') - { - // Change the status of the replica IIF - $oReplica->Set('status', 'obsolete'); - } - $oReplica->DBUpdateTracked($oMyChange); - break; - - case 'delete': - default: - $oStatLog->AddTrace("Destination object to be DELETED", $oReplica); - $oReplica->DeleteDestObject($oMyChange, $oStatLog); - } - } - } // if ($sDeletePolicy != 'ignore' - - //Count "seen" objects - $sSelectSeen = "SELECT SynchroReplica WHERE sync_source_id = :source_id AND status IN ('new', 'synchronized', 'modified', 'orphan') AND status_last_seen >= :last_import"; - $oSetSeen = new DBObjectSet(DBObjectSearch::FromOQL($sSelectSeen), array() /* order by*/, array('source_id' => $this->GetKey(), 'last_import' => $sLimitDate)); - $oStatLog->Set('stats_nb_replica_seen', $oSetSeen->Count()); - - // Get all the replicas that are 'new' or modified or synchronized with a warning - // - $sSelectToSync = "SELECT SynchroReplica WHERE (status = 'new' OR status = 'modified' OR (status = 'synchronized' AND status_last_warning != '')) AND sync_source_id = :source_id AND status_last_seen >= :last_import"; - $oSetToSync = new DBObjectSet(DBObjectSearch::FromOQL($sSelectToSync), array() /* order by*/, array('source_id' => $this->GetKey(), 'last_import' => $sLimitDate) /* aArgs */, $aExtDataSpec, 0 /* limitCount */, 0 /* limitStart */); - - while($oReplica = $oSetToSync->Fetch()) - { - $oReplica->Synchro($this, $aReconciliationKeys, $aAttributes, $oMyChange, $oStatLog); - $oReplica->DBUpdateTracked($oMyChange); - } - - // Get all the replicas that are to be deleted - // - if ($sDeletePolicy == 'update_then_delete') - { - $oDeletionDate = $oLastFullLoadStartDate; - $iDeleteRetention = $this->Get('delete_policy_retention'); // Duration in seconds - if ($iDeleteRetention > 0) - { - $sInterval = "-$iDeleteRetention seconds"; - $oDeletionDate->Modify($sInterval); - } - $sDeletionDate = $oDeletionDate->Format('Y-m-d H:i:s'); - $oStatLog->AddTrace("Deletion date: $sDeletionDate"); - $sSelectToDelete = "SELECT SynchroReplica WHERE sync_source_id = :source_id AND status IN ('obsolete') AND status_last_seen < :last_import"; - $oSetToDelete = new DBObjectSet(DBObjectSearch::FromOQL($sSelectToDelete), array() /* order by*/, array('source_id' => $this->GetKey(), 'last_import' => $sDeletionDate)); - while($oReplica = $oSetToDelete->Fetch()) - { - $oStatLog->AddTrace("Destination object to be DELETED", $oReplica); - $oReplica->DeleteDestObject($oMyChange, $oStatLog); - } - } - } - /** * Get the list of attributes eligible to the synchronization */ @@ -1519,6 +1207,8 @@ class SynchroLog extends DBObject MetaModel::Init_AddAttribute(new AttributeDateTime("start_date", array("allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDateTime("end_date", array("allowed_values"=>null, "sql"=>"end_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('running,completed,error'), "sql"=>"status", "default_value"=>"running", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("status_curr_job", array("allowed_values"=>null, "sql"=>"status_curr_job", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("status_curr_pos", array("allowed_values"=>null, "sql"=>"status_curr_pos", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeInteger("stats_nb_replica_seen", array("allowed_values"=>null, "sql"=>"stats_nb_replica_seen", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeInteger("stats_nb_replica_total", array("allowed_values"=>null, "sql"=>"stats_nb_replica_total", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array()))); @@ -1545,6 +1235,8 @@ class SynchroLog extends DBObject MetaModel::Init_AddAttribute(new AttributeText("last_error", array("allowed_values"=>null, "sql"=>"last_error", "default_value"=>'', "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeLongText("traces", array("allowed_values"=>null, "sql"=>"traces", "default_value"=>'', "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("memory_usage_peak", array("allowed_values"=>null, "sql"=>"memory_usage_peak", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array()))); + // Display lists MetaModel::Init_SetZListItems('details', array('sync_source_id', 'start_date', 'end_date', 'status', 'stats_nb_replica_total', 'stats_nb_replica_seen', 'stats_nb_obj_created', /*'stats_nb_replica_reconciled',*/ 'stats_nb_obj_updated', 'stats_nb_obj_obsoleted', 'stats_nb_obj_deleted', 'stats_nb_obj_created_errors', 'stats_nb_replica_reconciled_errors', 'stats_nb_replica_disappeared_no_action', 'stats_nb_obj_updated_errors', 'stats_nb_obj_obsoleted_errors', 'stats_nb_obj_deleted_errors', 'stats_nb_obj_new_unchanged', 'stats_nb_obj_new_updated', 'traces')); // Attributes to be displayed for the complete details @@ -1618,14 +1310,26 @@ class SynchroLog extends DBObject return; } + $sPrevTrace = $this->Get('traces'); + $oAttDef = MetaModel::GetAttributeDef(get_class($this), 'traces'); $iMaxSize = $oAttDef->GetMaxSize(); - $sTrace = implode("\n", $this->m_aTraces); + if (strlen($sPrevTrace) > 0) + { + $sTrace = $sPrevTrace."\n".implode("\n", $this->m_aTraces); + } + else + { + $sTrace = implode("\n", $this->m_aTraces); + } if (strlen($sTrace) >= $iMaxSize) { $sTrace = substr($sTrace, 0, $iMaxSize - 255)."...\nTruncated (size: ".strlen($sTrace).')'; } $this->Set('traces', $sTrace); + + //DBUpdate may be called many times... the operation should not be repeated + $this->m_aTraces = array(); } protected function OnInsert() @@ -1637,6 +1341,8 @@ class SynchroLog extends DBObject protected function OnUpdate() { $this->TraceToText(); + $sMemPeak = max($this->Get('memory_usage_peak'), ExecutionKPI::memory_get_peak_usage()); + $this->Set('memory_usage_peak', $sMemPeak); parent::OnUpdate(); } } @@ -2281,13 +1987,16 @@ class SynchroReplica extends DBObject implements iDisplay } $oPage->Details($aDetails); $oPage->add(''); - $oDestObj = MetaModel::GetObject($this->Get('dest_class'), $this->Get('dest_id'), false); - if (is_object($oDestObj)) + if (strlen($this->Get('dest_class')) > 0) { - $oPage->add('
'); - $oPage->add(''.Dict::Format('Core:SynchroReplica:TargetObject', $oDestObj->GetHyperlink()).''); - $oDestObj->DisplayBareProperties($oPage, false, $sPrefix, $aExtraParams); - $oPage->add('
'); + $oDestObj = MetaModel::GetObject($this->Get('dest_class'), $this->Get('dest_id'), false); + if (is_object($oDestObj)) + { + $oPage->add('
'); + $oPage->add(''.Dict::Format('Core:SynchroReplica:TargetObject', $oDestObj->GetHyperlink()).''); + $oDestObj->DisplayBareProperties($oPage, false, $sPrefix, $aExtraParams); + $oPage->add('
'); + } } $oPage->add(''); $oPage->add('
'); @@ -2319,6 +2028,643 @@ class SynchroReplica extends DBObject implements iDisplay } } +/** + * Context of an ongoing synchronization + * Two usages: + * 1) Public usage: execute the synchronization + * $oSynchroExec = new SynchroExecution($oDataSource[, $iLastFullLoad]); + * $oSynchroExec->Process($iMaxChunkSize); + * + * 2) Internal usage: continue the synchronization (split into chunks, each performed in a separate process) + * This is implemented in the page priv_sync_chunk.php + * $oSynchroExec = SynchroExecution::Resume($oDataSource, $iLastFullLoad, $iSynchroLog, $iChange, $iMaxToProcess, $iJob, $iNextInJob); + * $oSynchroExec->Process() + */ +class SynchroExecution +{ + protected $m_oDataSource = null; + protected $m_oLastFullLoadStartDate = null; + + protected $m_oChange = null; + protected $m_oStatLog = null; + + // Context computed one for optimization and report inconsistencies ASAP + protected $m_aExtDataSpec = array(); + protected $m_aReconciliationKeys = array(); + protected $m_aAttributes = array(); + protected $m_iCountAllReplicas = 0; + + /** + * Constructor + * @param SynchroDataSource $oDataSource Synchronization task + * @param DateTime $oLastFullLoadStartDate Date of the last full load (start date/time), if known + * @return void + */ + public function __construct($oDataSource, $oLastFullLoadStartDate = null) + { + $this->m_oDataSource = $oDataSource; + $this->m_oLastFullLoadStartDate = $oLastFullLoadStartDate; + } + + /** + * Create the persistant information records, for the current synchronization + * In fact, those records ARE defining what is the "current" synchronization + */ + protected function PrepareLogs() + { + if (!is_null($this->m_oChange)) + { + return; + } + + // Create a change used for logging all the modifications/creations happening during the synchro + $this->m_oChange = MetaModel::NewObject("CMDBChange"); + $this->m_oChange->Set("date", time()); + $sUserString = CMDBChange::GetCurrentUserName(); + $this->m_oChange->Set("userinfo", $sUserString.' '.Dict::S('Core:SyncDataExchangeComment')); + $iChangeId = $this->m_oChange->DBInsert(); + + // Start logging this execution (stats + protection against reentrance) + // + $this->m_oStatLog = new SynchroLog(); + $this->m_oStatLog->Set('sync_source_id', $this->m_oDataSource->GetKey()); + $this->m_oStatLog->Set('start_date', time()); + $this->m_oStatLog->Set('status', 'running'); + $this->m_oStatLog->Set('stats_nb_replica_seen', 0); + $this->m_oStatLog->Set('stats_nb_replica_total', 0); + $this->m_oStatLog->Set('stats_nb_obj_deleted', 0); + $this->m_oStatLog->Set('stats_nb_obj_deleted_errors', 0); + $this->m_oStatLog->Set('stats_nb_obj_obsoleted', 0); + $this->m_oStatLog->Set('stats_nb_obj_obsoleted_errors', 0); + $this->m_oStatLog->Set('stats_nb_obj_created', 0); + $this->m_oStatLog->Set('stats_nb_obj_created_errors', 0); + $this->m_oStatLog->Set('stats_nb_obj_created_warnings', 0); + $this->m_oStatLog->Set('stats_nb_obj_updated', 0); + $this->m_oStatLog->Set('stats_nb_obj_updated_warnings', 0); + $this->m_oStatLog->Set('stats_nb_obj_updated_errors', 0); + $this->m_oStatLog->Set('stats_nb_obj_unchanged_warnings', 0); + // $this->m_oStatLog->Set('stats_nb_replica_reconciled', 0); + $this->m_oStatLog->Set('stats_nb_replica_reconciled_errors', 0); + $this->m_oStatLog->Set('stats_nb_replica_disappeared_no_action', 0); + $this->m_oStatLog->Set('stats_nb_obj_new_updated', 0); + $this->m_oStatLog->Set('stats_nb_obj_new_updated_warnings', 0); + $this->m_oStatLog->Set('stats_nb_obj_new_unchanged',0); + $this->m_oStatLog->Set('stats_nb_obj_new_unchanged_warnings',0); + + $sSelectTotal = "SELECT SynchroReplica WHERE sync_source_id = :source_id"; + $oSetTotal = new DBObjectSet(DBObjectSearch::FromOQL($sSelectTotal), array() /* order by*/, array('source_id' => $this->m_oDataSource->GetKey())); + $this->m_iCountAllReplicas = $oSetTotal->Count(); + $this->m_oStatLog->Set('stats_nb_replica_total', $this->m_iCountAllReplicas); + + $this->m_oStatLog->DBInsertTracked($this->m_oChange); + } + + /** + * Prevent against the reentrance... or allow the current task to do things forbidden by the others ! + */ + public static $m_oCurrentTask = null; + public static function GetCurrentTaskId() + { + if (is_object(self::$m_oCurrentTask)) + { + return self::$m_oCurrentTask->GetKey(); + } + else + { + return null; + } + } + + /** + * Prepare structures in memory, to speedup the processing of a given replica + */ + public function PrepareProcessing($bFirstPass = true) + { + if ($this->m_oDataSource->Get('status') == 'obsolete') + { + throw new SynchroExceptionNotStarted(Dict::S('Core:SyncDataSourceObsolete')); + } + if (!UserRights::IsAdministrator() && $this->m_oDataSource->Get('user_id') != UserRights::GetUserId()) + { + throw new SynchroExceptionNotStarted(Dict::S('Core:SyncDataSourceAccessRestriction')); + } + + // Get the list of SQL columns + $sClass = $this->m_oDataSource->GetTargetClass(); + $aAttCodesExpected = array(); + $aAttCodesToReconcile = array(); + $aAttCodesToUpdate = array(); + $sSelectAtt = "SELECT SynchroAttribute WHERE sync_source_id = :source_id AND (update = 1 OR reconcile = 1)"; + $oSetAtt = new DBObjectSet(DBObjectSearch::FromOQL($sSelectAtt), array() /* order by*/, array('source_id' => $this->m_oDataSource->GetKey()) /* aArgs */); + while ($oSyncAtt = $oSetAtt->Fetch()) + { + if ($oSyncAtt->Get('update')) + { + $aAttCodesToUpdate[$oSyncAtt->Get('attcode')] = $oSyncAtt; + } + if ($oSyncAtt->Get('reconcile')) + { + $aAttCodesToReconcile[$oSyncAtt->Get('attcode')] = $oSyncAtt; + } + $aAttCodesExpected[$oSyncAtt->Get('attcode')] = $oSyncAtt; + } + $aColumns = $this->m_oDataSource->GetSQLColumns(array_keys($aAttCodesExpected)); + $aExtDataFields = array_keys($aColumns); + $aExtDataFields[] = 'primary_key'; + + $this->m_aExtDataSpec = array( + 'table' => $this->m_oDataSource->GetDataTable(), + 'join_key' => 'id', + 'fields' => $aExtDataFields + ); + + // Get the list of attributes, determine reconciliation keys and update targets + // + if ($this->m_oDataSource->Get('reconciliation_policy') == 'use_attributes') + { + $this->m_aReconciliationKeys = $aAttCodesToReconcile; + } + elseif ($this->m_oDataSource->Get('reconciliation_policy') == 'use_primary_key') + { + // Override the settings made at the attribute level ! + $this->m_aReconciliationKeys = array("primary_key" => null); + } + + if ($bFirstPass) + { + $this->m_oStatLog->AddTrace("Update of: {".implode(', ', array_keys($aAttCodesToUpdate))."}"); + $this->m_oStatLog->AddTrace("Reconciliation on: {".implode(', ', array_keys($this->m_aReconciliationKeys))."}"); + } + + if (count($aAttCodesToUpdate) == 0) + { + $this->m_oStatLog->AddTrace("No attribute to update"); + throw new SynchroExceptionNotStarted('There is no attribute to update'); + } + if (count($this->m_aReconciliationKeys) == 0) + { + $this->m_oStatLog->AddTrace("No attribute for reconciliation"); + throw new SynchroExceptionNotStarted('No attribute for reconciliation'); + } + + $this->m_aAttributes = array(); + foreach($aAttCodesToUpdate as $sAttCode => $oSyncAtt) + { + $oAttDef = MetaModel::GetAttributeDef($this->m_oDataSource->GetTargetClass(), $sAttCode); + if ($oAttDef->IsWritable()) + { + $this->m_aAttributes[$sAttCode] = $oSyncAtt; + } + } + + // Compute and keep track of the limit date taken into account for obsoleting replicas + // + if ($this->m_oLastFullLoadStartDate == null) + { + // No previous import known, use the full_load_periodicity value... and the current date + $this->m_oLastFullLoadStartDate = new DateTime(); // Now + $iLoadPeriodicity = $this->m_oDataSource->Get('full_load_periodicity'); // Duration in seconds + if ($iLoadPeriodicity > 0) + { + $sInterval = "-$iLoadPeriodicity seconds"; + $this->m_oLastFullLoadStartDate->Modify($sInterval); + } + else + { + $this->m_oLastFullLoadStartDate = new DateTime('1970-01-01'); + } + } + if ($bFirstPass) + { + $this->m_oStatLog->AddTrace("Limit Date: ".$this->m_oLastFullLoadStartDate->Format('Y-m-d H:i:s')); + } + } + + + /** + * Perform a synchronization between the data stored in the replicas (&synchro_data_xxx_xx table) + * and the iTop objects. If the lastFullLoadStartDate is NOT specified then the full_load_periodicity + * is used to determine which records are obsolete. + * @return void + */ + public function Process() + { + $this->PrepareLogs(); + + self::$m_oCurrentTask = $this->m_oDataSource; + try + { + $this->DoSynchronize(); + + $this->m_oStatLog->Set('end_date', time()); + $this->m_oStatLog->Set('status', 'completed'); + $this->m_oStatLog->DBUpdateTracked($this->m_oChange); + + $iErrors = $this->m_oStatLog->GetErrorCount(); + if ($iErrors > 0) + { + $sIssuesOQL = "SELECT SynchroReplica WHERE sync_source_id=".$this->m_oDataSource->GetKey()." AND status_last_error!=''"; + $sAbsoluteUrl = utils::GetAbsoluteUrlAppRoot(); + $sIssuesURL = "{$sAbsoluteUrl}synchro/replica.php?operation=oql&datasource=".$this->m_oDataSource->GetKey()."&oql=".urlencode($sIssuesOQL); + $sSeeIssues = "

"; + + $sStatistics = "

Statistics

\n"; + $sStatistics .= "
    \n"; + $sStatistics .= "
  • ".$this->m_oStatLog->GetLabel('start_date').": ".$this->m_oStatLog->Get('start_date')."
  • \n"; + $sStatistics .= "
  • ".$this->m_oStatLog->GetLabel('end_date').": ".$this->m_oStatLog->Get('end_date')."
  • \n"; + $sStatistics .= "
  • ".$this->m_oStatLog->GetLabel('stats_nb_replica_seen').": ".$this->m_oStatLog->Get('stats_nb_replica_seen')."
  • \n"; + $sStatistics .= "
  • ".$this->m_oStatLog->GetLabel('stats_nb_replica_total').": ".$this->m_oStatLog->Get('stats_nb_replica_total')."
  • \n"; + $sStatistics .= "
  • ".$this->m_oStatLog->GetLabel('stats_nb_obj_deleted').": ".$this->m_oStatLog->Get('stats_nb_obj_deleted')."
  • \n"; + $sStatistics .= "
  • ".$this->m_oStatLog->GetLabel('stats_nb_obj_deleted_errors').": ".$this->m_oStatLog->Get('stats_nb_obj_deleted_errors')."
  • \n"; + $sStatistics .= "
  • ".$this->m_oStatLog->GetLabel('stats_nb_obj_obsoleted').": ".$this->m_oStatLog->Get('stats_nb_obj_obsoleted')."
  • \n"; + $sStatistics .= "
  • ".$this->m_oStatLog->GetLabel('stats_nb_obj_obsoleted_errors').": ".$this->m_oStatLog->Get('stats_nb_obj_obsoleted_errors')."
  • \n"; + $sStatistics .= "
  • ".$this->m_oStatLog->GetLabel('stats_nb_obj_created').": ".$this->m_oStatLog->Get('stats_nb_obj_created')." (".$this->m_oStatLog->Get('stats_nb_obj_created_warnings')." warnings)"."
  • \n"; + $sStatistics .= "
  • ".$this->m_oStatLog->GetLabel('stats_nb_obj_created_errors').": ".$this->m_oStatLog->Get('stats_nb_obj_created_errors')."
  • \n"; + $sStatistics .= "
  • ".$this->m_oStatLog->GetLabel('stats_nb_obj_updated').": ".$this->m_oStatLog->Get('stats_nb_obj_updated')." (".$this->m_oStatLog->Get('stats_nb_obj_updated_warnings')." warnings)"."
  • \n"; + $sStatistics .= "
  • ".$this->m_oStatLog->GetLabel('stats_nb_obj_updated_errors').": ".$this->m_oStatLog->Get('stats_nb_obj_updated_errors')."
  • \n"; + $sStatistics .= "
  • ".$this->m_oStatLog->GetLabel('stats_nb_replica_reconciled_errors').": ".$this->m_oStatLog->Get('stats_nb_replica_reconciled_errors')."
  • \n"; + $sStatistics .= "
  • ".$this->m_oStatLog->GetLabel('stats_nb_replica_disappeared_no_action').": ".$this->m_oStatLog->Get('stats_nb_replica_disappeared_no_action')."
  • \n"; + $sStatistics .= "
  • ".$this->m_oStatLog->GetLabel('stats_nb_obj_new_updated').": ".$this->m_oStatLog->Get('stats_nb_obj_new_updated')." (".$this->m_oStatLog->Get('stats_nb_obj_new_updated_warnings')." warnings)"."
  • \n"; + $sStatistics .= "
  • ".$this->m_oStatLog->GetLabel('stats_nb_obj_new_unchanged').": ".$this->m_oStatLog->Get('stats_nb_obj_new_unchanged')." (".$this->m_oStatLog->Get('stats_nb_obj_new_unchanged_warnings')." warnings)"."
  • \n"; + $sStatistics .= "
\n"; + + $this->m_oDataSource->SendNotification("errors ($iErrors)", "

The synchronization has been executed, $iErrors errors have been encountered. Click here to see the records being currently in error.

".$sStatistics); + } + else + { + //$this->m_oDataSource->SendNotification('success', '

The synchronization has been successfully executed.

'); + } + } + catch (SynchroExceptionNotStarted $e) + { + // Set information for reporting... but delete the object in DB + $this->m_oStatLog->Set('end_date', time()); + $this->m_oStatLog->Set('status', 'error'); + $this->m_oStatLog->Set('last_error', $e->getMessage()); + $this->m_oStatLog->DBDeleteTracked($this->m_oChange); + $this->m_oDataSource->SendNotification('fatal error', '

The synchronization could not start: \''.$e->getMessage().'\'

Please check its configuration

'); + } + catch (Exception $e) + { + $this->m_oStatLog->Set('end_date', time()); + $this->m_oStatLog->Set('status', 'error'); + $this->m_oStatLog->Set('last_error', $e->getMessage()); + $this->m_oStatLog->DBUpdateTracked($this->m_oChange); + $this->m_oDataSource->SendNotification('exception', '

The synchronization has been interrupted: \''.$e->getMessage().'\'

Please contact the application support team

'); + } + self::$m_oCurrentTask = null; + + return $this->m_oStatLog; + } + + /** + * Do the entire synchronization job + */ + protected function DoSynchronize() + { + $this->m_oStatLog->Set('status_curr_job', 1); + $this->m_oStatLog->Set('status_curr_pos', -1); + + $iMaxChunkSize = utils::ReadParam('max_chunk_size', 0, true /* allow CLI */); + if ($iMaxChunkSize > 0) + { + // Split the execution into several processes + // Each process will call DoSynchronizeChunk() + // The loop will end when a process does not reply "continue" on the last line of its output + if (!utils::IsModeCLI()) + { + throw new SynchroExceptionNotStarted(Dict::S('Core:SyncSplitModeCLIOnly')); + } + $aArguments = array(); + $aArguments['source'] = $this->m_oDataSource->GetKey(); + $aArguments['log'] = $this->m_oStatLog->GetKey(); + $aArguments['change'] = $this->m_oChange->GetKey(); + $aArguments['chunk'] = $iMaxChunkSize; + if ($this->m_oLastFullLoadStartDate) + { + $aArguments['last_full_load'] = $this->m_oLastFullLoadStartDate->Format('Y-m-d H:i:s'); + } + else + { + $aArguments['last_full_load'] = ''; + } + + $this->m_oStatLog->DBUpdate($this->m_oChange); + + $iStepCount = 0; + do + { + $aArguments['step_count'] = $iStepCount; + $iStepCount++; + + list ($iRes, $aOut) = utils::ExecITopScript('synchro/priv_sync_chunk.php', $aArguments); + + // Reload the log that has been modified by the processes + $this->m_oStatLog->Reload(); + + $sLastRes = strtolower(trim(end($aOut))); + switch($sLastRes) + { + case 'continue': + $bContinue = true; + break; + + case 'finished': + $bContinue = false; + break; + + default: + $this->m_oStatLog->AddTrace("The script did not reply with the expected keywords:"); + $aIndentedOut = array(); + foreach ($aOut as $sOut) + { + $aIndentedOut[] = "-> $sOut"; + $this->m_oStatLog->AddTrace(">>> $sOut"); + } + throw new Exception("Encountered an error in an underspinned process:\n".implode("\n", $aIndentedOut)); + } + } + while ($bContinue); + } + else + { + $this->PrepareProcessing(/* first pass */); + $this->DoJob1(); + $this->DoJob2(); + $this->DoJob3(); + } + } + + /** + * Do the synchronization job, limited to some amount of work + * This verb has been designed to be called from within a separate process + * @return true if the process has to be continued + */ + public function DoSynchronizeChunk($oLog, $oChange, $iMaxChunkSize) + { + // Initialize the structures... + self::$m_oCurrentTask = $this->m_oDataSource; + $this->m_oStatLog = $oLog; + $this->m_oChange = $oChange; + + // Prepare internal structures (not the first pass) + $this->PrepareProcessing(false); + + $iCurrJob = $this->m_oStatLog->Get('status_curr_job'); + $iCurrPos = $this->m_oStatLog->Get('status_curr_pos'); + + $this->m_oStatLog->AddTrace("Synchronizing chunk - curr_job:$iCurrJob, curr_pos:$iCurrPos, max_chunk_size:$iMaxChunkSize"); + + $bContinue = false; + switch ($iCurrJob) + { + case 1: + default: + $this->DoJob1($iMaxChunkSize, $iCurrPos); + $bContinue = true; + break; + + case 2: + $this->DoJob2($iMaxChunkSize, $iCurrPos); + $bContinue = true; + break; + + case 3: + $bContinue = $this->DoJob3($iMaxChunkSize, $iCurrPos); + break; + } + $this->m_oStatLog->DBUpdate($this->m_oChange); + self::$m_oCurrentTask = null; + return $bContinue; + } + + /** + * Do the synchronization job #1: Obsolete replica "untouched" for some time + * @param integer $iMaxReplica Limit the number of replicas to process + * @param integer $iCurrPos Current position where to resume the processing + * @return true if the process must be continued + */ + protected function DoJob1($iMaxReplica = null, $iCurrPos = -1) + { + $sLimitDate = $this->m_oLastFullLoadStartDate->Format('Y-m-d H:i:s'); + + // Get all the replicas that were not seen in the last import and mark them as obsolete + $sDeletePolicy = $this->m_oDataSource->Get('delete_policy'); + if ($sDeletePolicy != 'ignore') + { + $sSelectToObsolete = "SELECT SynchroReplica WHERE id > :curr_pos AND sync_source_id = :source_id AND status IN ('new', 'synchronized', 'modified', 'orphan') AND status_last_seen < :last_import"; + $oSetScope = new DBObjectSet(DBObjectSearch::FromOQL($sSelectToObsolete), array() /* order by*/, array('source_id' => $this->m_oDataSource->GetKey(), 'last_import' => $sLimitDate, 'curr_pos' => $iCurrPos)); + $iCountScope = $oSetScope->Count(); + if (($this->m_iCountAllReplicas > 10) && ($this->m_iCountAllReplicas == $iCountScope)) + { + throw new SynchroExceptionNotStarted(Dict::S('Core:SyncTooManyMissingReplicas')); + } + + if ($iMaxReplica) + { + // Consider a given subset, starting from replica iCurrPos, limited to the count of iMaxReplica + // The replica have to be ordered by id + $oSetToProcess = new DBObjectSet(DBObjectSearch::FromOQL($sSelectToObsolete), array('id'=>true) /* order by*/, array('source_id' => $this->m_oDataSource->GetKey(), 'last_import' => $sLimitDate, 'curr_pos' => $iCurrPos)); + $oSetToProcess->SetLimit($iMaxReplica); + } + else + { + $oSetToProcess = $oSetScope; + } + + $iLastReplicaProcessed = -1; + while($oReplica = $oSetToProcess->Fetch()) + { + $iLastReplicaProcessed = $oReplica->GetKey(); + switch ($sDeletePolicy) + { + case 'update': + case 'update_then_delete': + $this->m_oStatLog->AddTrace("Destination object to be updated", $oReplica); + $aToUpdate = array(); + $aToUpdateSpec = explode(';', $this->m_oDataSource->Get('delete_policy_update')); //ex: 'status:obsolete;description:stopped', + foreach($aToUpdateSpec as $sUpdateSpec) + { + $aUpdateSpec = explode(':', $sUpdateSpec); + if (count($aUpdateSpec) == 2) + { + $sAttCode = $aUpdateSpec[0]; + $sValue = $aUpdateSpec[1]; + $aToUpdate[$sAttCode] = $sValue; + } + } + $oReplica->Set('status_last_error', ''); + if ($oReplica->Get('dest_id') == '') + { + $oReplica->Set('status', 'obsolete'); + $this->m_oStatLog->Inc('stats_nb_replica_disappeared_no_action'); + } + else + { + $oReplica->UpdateDestObject($aToUpdate, $this->m_oChange, $this->m_oStatLog); + if ($oReplica->Get('status_last_error') == '') + { + // Change the status of the replica IIF + $oReplica->Set('status', 'obsolete'); + } + } + $oReplica->DBUpdateTracked($this->m_oChange); + break; + + case 'delete': + default: + $this->m_oStatLog->AddTrace("Destination object to be DELETED", $oReplica); + $oReplica->DeleteDestObject($this->m_oChange, $this->m_oStatLog); + } + } + if ($iMaxReplica) + { + if ($iMaxReplica < $iCountScope) + { + // Continue with this job! + $this->m_oStatLog->Set('status_curr_pos', $iLastReplicaProcessed); + return true; + } + } + } // if ($sDeletePolicy != 'ignore' + + //Count "seen" objects + $sSelectSeen = "SELECT SynchroReplica WHERE sync_source_id = :source_id AND status IN ('new', 'synchronized', 'modified', 'orphan') AND status_last_seen >= :last_import"; + $oSetSeen = new DBObjectSet(DBObjectSearch::FromOQL($sSelectSeen), array() /* order by*/, array('source_id' => $this->m_oDataSource->GetKey(), 'last_import' => $sLimitDate)); + $this->m_oStatLog->Set('stats_nb_replica_seen', $oSetSeen->Count()); + + + // Job complete! + $this->m_oStatLog->Set('status_curr_job', 2); + $this->m_oStatLog->Set('status_curr_pos', -1); + return false; + } + + /** + * Do the synchronization job #2: Create and modify object for new/modified replicas + * @param integer $iMaxReplica Limit the number of replicas to process + * @param integer $iCurrPos Current position where to resume the processing + * @return true if the process must be continued + */ + protected function DoJob2($iMaxReplica = null, $iCurrPos = -1) + { + $sLimitDate = $this->m_oLastFullLoadStartDate->Format('Y-m-d H:i:s'); + + // Get all the replicas that are 'new' or modified or synchronized with a warning + // + $sSelectToSync = "SELECT SynchroReplica WHERE id > :curr_pos AND (status = 'new' OR status = 'modified' OR (status = 'synchronized' AND status_last_warning != '')) AND sync_source_id = :source_id AND status_last_seen >= :last_import"; + $oSetScope = new DBObjectSet(DBObjectSearch::FromOQL($sSelectToSync), array() /* order by*/, array('source_id' => $this->m_oDataSource->GetKey(), 'last_import' => $sLimitDate, 'curr_pos' => $iCurrPos), $this->m_aExtDataSpec); + $iCountScope = $oSetScope->Count(); + + if ($iMaxReplica) + { + // Consider a given subset, starting from replica iCurrPos, limited to the count of iMaxReplica + // The replica have to be ordered by id + $oSetToProcess = new DBObjectSet(DBObjectSearch::FromOQL($sSelectToSync), array('id'=>true) /* order by*/, array('source_id' => $this->m_oDataSource->GetKey(), 'last_import' => $sLimitDate, 'curr_pos' => $iCurrPos), $this->m_aExtDataSpec); + $oSetToProcess->SetLimit($iMaxReplica); + } + else + { + $oSetToProcess = $oSetScope; + } + + $iLastReplicaProcessed = -1; + while($oReplica = $oSetToProcess->Fetch()) + { + $iLastReplicaProcessed = $oReplica->GetKey(); + $oReplica->Synchro($this->m_oDataSource, $this->m_aReconciliationKeys, $this->m_aAttributes, $this->m_oChange, $this->m_oStatLog); + $oReplica->DBUpdateTracked($this->m_oChange); + } + + if ($iMaxReplica) + { + if ($iMaxReplica < $iCountScope) + { + // Continue with this job! + $this->m_oStatLog->Set('status_curr_pos', $iLastReplicaProcessed); + return true; + } + } + + // Job complete! + $this->m_oStatLog->Set('status_curr_job', 3); + $this->m_oStatLog->Set('status_curr_pos', -1); + return false; + } + + /** + * Do the synchronization job #3: Delete replica depending on the obsolescence scheme + * @param integer $iMaxReplica Limit the number of replicas to process + * @param integer $iCurrPos Current position where to resume the processing + * @return true if the process must be continued + */ + protected function DoJob3($iMaxReplica = null, $iCurrPos = -1) + { + $sDeletePolicy = $this->m_oDataSource->Get('delete_policy'); + if ($sDeletePolicy != 'update_then_delete') + { + // Job complete! + $this->m_oStatLog->Set('status_curr_job', 0); + $this->m_oStatLog->Set('status_curr_pos', -1); + return false; + } + + $bFirstPass = ($iCurrPos == -1); + + // Get all the replicas that are to be deleted + // + $oDeletionDate = $this->m_oLastFullLoadStartDate; + $iDeleteRetention = $this->m_oDataSource->Get('delete_policy_retention'); // Duration in seconds + if ($iDeleteRetention > 0) + { + $sInterval = "-$iDeleteRetention seconds"; + $oDeletionDate->Modify($sInterval); + } + $sDeletionDate = $oDeletionDate->Format('Y-m-d H:i:s'); + if ($bFirstPass) + { + $this->m_oStatLog->AddTrace("Deletion date: $sDeletionDate"); + } + $sSelectToDelete = "SELECT SynchroReplica WHERE id > :curr_pos AND sync_source_id = :source_id AND status IN ('obsolete') AND status_last_seen < :last_import"; + $oSetScope = new DBObjectSet(DBObjectSearch::FromOQL($sSelectToDelete), array() /* order by*/, array('source_id' => $this->m_oDataSource->GetKey(), 'last_import' => $sDeletionDate, 'curr_pos' => $iCurrPos)); + $iCountScope = $oSetScope->Count(); + + if ($iMaxReplica) + { + // Consider a given subset, starting from replica iCurrPos, limited to the count of iMaxReplica + // The replica have to be ordered by id + $oSetToProcess = new DBObjectSet(DBObjectSearch::FromOQL($sSelectToDelete), array('id'=>true) /* order by*/, array('source_id' => $this->m_oDataSource->GetKey(), 'last_import' => $sDeletionDate, 'curr_pos' => $iCurrPos)); + $oSetToProcess->SetLimit($iMaxReplica); + } + else + { + $oSetToProcess = $oSetScope; + } + + $iLastReplicaProcessed = -1; + while($oReplica = $oSetToProcess->Fetch()) + { + $iLastReplicaProcessed = $oReplica->GetKey(); + $this->m_oStatLog->AddTrace("Destination object to be DELETED", $oReplica); + $oReplica->DeleteDestObject($this->m_oChange, $this->m_oStatLog); + } + + if ($iMaxReplica) + { + if ($iMaxReplica < $iCountScope) + { + // Continue with this job! + $this->m_oStatLog->Set('status_curr_pos', $iLastReplicaProcessed); + return true; + } + } + // Job complete! + $this->m_oStatLog->Set('status_curr_job', 0); + $this->m_oStatLog->Set('status_curr_pos', -1); + return false; + } +} + $oAdminMenu = new MenuGroup('AdminTools', 80 /* fRank */, 'SynchroDataSource', UR_ACTION_MODIFY, UR_ALLOWED_YES); new OQLMenuNode('DataSources', 'SELECT SynchroDataSource', $oAdminMenu->GetIndex(), 12 /* fRank */, true, 'SynchroDataSource', UR_ACTION_MODIFY, UR_ALLOWED_YES); // new OQLMenuNode('Replicas', 'SELECT SynchroReplica', $oAdminMenu->GetIndex(), 12 /* fRank */, true, 'SynchroReplica', UR_ACTION_MODIFY, UR_ALLOWED_YES); diff --git a/test/testlist.inc.php b/test/testlist.inc.php index 40df3ac7b..5bb73b486 100644 --- a/test/testlist.inc.php +++ b/test/testlist.inc.php @@ -2270,8 +2270,8 @@ class TestDataExchange extends TestBizModel ), ), ), - //); - //$aXXXXScenarios = array( +// ); +// $aXXXXScenarios = array( array( 'desc' => 'Update then delete with retention (to complete with manual testing) and reconciliation on org/name', 'login' => 'admin',