1<?php 2// Copyright (C) 2011-2017 Combodo SARL 3// 4// This file is part of iTop. 5// 6// iTop is free software; you can redistribute it and/or modify 7// it under the terms of the GNU Affero General Public License as published by 8// the Free Software Foundation, either version 3 of the License, or 9// (at your option) any later version. 10// 11// iTop is distributed in the hope that it will be useful, 12// but WITHOUT ANY WARRANTY; without even the implied warranty of 13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14// GNU Affero General Public License for more details. 15// 16// You should have received a copy of the GNU Affero General Public License 17// along with iTop. If not, see <http://www.gnu.org/licenses/> 18 19/** 20 * Data Exchange web service 21 * 22 * @copyright Copyright (C) 2010-2017 Combodo SARL 23 * @license http://opensource.org/licenses/AGPL-3.0 24 */ 25 26// 27// Known limitations 28// - reconciliation is made on the column primary_key 29// 30 31if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__)); 32require_once(__DIR__.'/../approot.inc.php'); 33require_once(APPROOT.'/application/application.inc.php'); 34require_once(APPROOT.'/application/webpage.class.inc.php'); 35require_once(APPROOT.'/application/csvpage.class.inc.php'); 36require_once(APPROOT.'/application/clipage.class.inc.php'); 37 38require_once(APPROOT.'/application/startup.inc.php'); 39 40class ExchangeException extends Exception 41{ 42} 43 44$aPageParams = array 45( 46 'auth_user' => array 47 ( 48 'mandatory' => true, 49 'modes' => 'cli', 50 'default' => null, 51 'description' => 'login (must have enough rights to create objects of the given class)', 52 ), 53 'auth_pwd' => array 54 ( 55 'mandatory' => true, 56 'modes' => 'cli', 57 'default' => null, 58 'description' => 'password', 59 ), 60 'data_source_id' => array 61 ( 62 'mandatory' => true, 63 'modes' => 'http,cli', 64 'default' => null, 65 'description' => 'Synchro data source id', 66 ), 67 'csvdata' => array 68 ( 69 'mandatory' => true, 70 'modes' => 'http', 71 'default' => null, 72 'description' => 'data', 73 ), 74 'csvfile' => array 75 ( 76 'mandatory' => true, 77 'modes' => 'cli', 78 'default' => '', 79 'description' => 'local data file, replaces csvdata if specified', 80 ), 81 'synchronize' => array 82 ( 83 'mandatory' => false, 84 'modes' => 'http,cli', 85 'default' => '1', 86 'description' => 'If set to 1, then the synchronization will be executed right after the data load', 87 ), 88 'charset' => array 89 ( 90 'mandatory' => false, 91 'modes' => 'http,cli', 92 'default' => 'UTF-8', 93 'description' => 'Character set encoding of the CSV data: UTF-8, ISO-8859-1, WINDOWS-1251, WINDOWS-1252, ISO-8859-15', 94 ), 95 'date_format' => array 96 ( 97 'mandatory' => false, 98 'modes' => 'http,cli', 99 'default' => 'Y-m-d H:i:s', 100 'description' => 'Input date format (used both for dates and datetimes) - Examples: Y-m-d, d/m/Y (Europe) - no transformation is applied if the argument is omitted. (note: old format specification using %Y %m %d is also supported for backward compatibility)', 101 ), 102 'separator' => array 103 ( 104 'mandatory' => false, 105 'modes' => 'http,cli', 106 'default' => ';', 107 'description' => 'column separator in CSV data (1 char, or \'tab\')', 108 ), 109 'qualifier' => array 110 ( 111 'mandatory' => false, 112 'modes' => 'http,cli', 113 'default' => '"', 114 'description' => 'test qualifier in CSV data', 115 ), 116 'output' => array 117 ( 118 'mandatory' => false, 119 'modes' => 'http,cli', 120 'default' => 'summary', 121 '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)', 122 ), 123 'max_chunk_size' => array 124 ( 125 'mandatory' => false, 126 'modes' => 'cli', 127 'default' => '0', 128 'description' => 'Limit on the count of records that can be loaded at once while performing the synchronization', 129 ), 130/* 131 'reportlevel' => array 132 ( 133 'mandatory' => false, 134 'modes' => 'http,cli', 135 'default' => 'errors|warnings|created|changed|unchanged', 136 'description' => 'combination of flags to limit the detailed output', 137 ), 138*/ 139 'simulate' => array 140 ( 141 'mandatory' => false, 142 'modes' => 'http,cli', 143 'default' => '0', 144 'description' => 'If set to 1, then the load will not be executed, but the expected report will be produced', 145 ), 146 'comment' => array 147 ( 148 'mandatory' => false, 149 'modes' => 'http,cli', 150 'default' => '', 151 'description' => 'Comment to be added into the change log', 152 ), 153 'no_stop_on_import_error' => array 154 ( 155 'mandatory' => false, 156 'modes' => 'http,cli', 157 'default' => '0', 158 'description' => 'Don\'t stop the import in case of SQL import error. By default the import will stop at the first error (and rollback all changes). If this flag is set to 1 the import will continue anyway', 159 ), 160); 161 162function UsageAndExit($oP) 163{ 164 global $aPageParams; 165 $bModeCLI = utils::IsModeCLI(); 166 167 $oP->p("USAGE:\n"); 168 foreach($aPageParams as $sParam => $aParamData) 169 { 170 $aModes = explode(',', $aParamData['modes']); 171 if ($bModeCLI) 172 { 173 if (in_array('cli', $aModes)) 174 { 175 $sDesc = $aParamData['description'].', '.($aParamData['mandatory'] ? 'mandatory' : 'optional, defaults to ['.$aParamData['default'].']'); 176 $oP->p("$sParam = $sDesc"); 177 } 178 } 179 else 180 { 181 if (in_array('http', $aModes)) 182 { 183 $sDesc = $aParamData['description'].', '.($aParamData['mandatory'] ? 'mandatory' : 'optional, defaults to ['.$aParamData['default'].']'); 184 $oP->p("$sParam = $sDesc"); 185 } 186 } 187 } 188 $oP->output(); 189 exit; 190} 191 192 193function ReadParam($oP, $sParam, $sSanitizationFilter = 'parameter') 194{ 195 global $aPageParams; 196 assert(isset($aPageParams[$sParam])); 197 assert(!$aPageParams[$sParam]['mandatory']); 198 $sValue = utils::ReadParam($sParam, $aPageParams[$sParam]['default'], true /* Allow CLI */, $sSanitizationFilter); 199 return trim($sValue); 200} 201 202function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) 203{ 204 global $aPageParams; 205 assert(isset($aPageParams[$sParam])); 206 assert($aPageParams[$sParam]['mandatory']); 207 208 $sValue = utils::ReadParam($sParam, null, true /* Allow CLI */, $sSanitizationFilter); 209 if (is_null($sValue)) 210 { 211 $oP->p("ERROR: Missing argument '$sParam'\n"); 212 UsageAndExit($oP); 213 } 214 return trim($sValue); 215} 216 217function ChangeDateFormat($sProposedDate, $sFormat, $bDateOnly) 218{ 219 if ($sProposedDate == '') 220 { 221 // An empty string means 'reset' 222 return ''; 223 } 224 // Convert to a valid MySQL datetime 225 $oFormat = new DateTimeFormat($sFormat); 226 $sRegExpr = $oFormat->ToRegExpr('/'); 227 if (!preg_match($sRegExpr, $sProposedDate)) 228 { 229 return false; 230 } 231 else 232 { 233 $oDate = $oFormat->Parse($sProposedDate); 234 if ($oDate !== null) 235 { 236 $oTargetFormat = $bDateOnly ? AttributeDate::GetInternalFormat() : AttributeDateTime::GetInternalFormat(); 237 $sDate = $oDate->format($oTargetFormat); 238 return $sDate; 239 } 240 else 241 { 242 return false; 243 } 244 } 245} 246 247 248class CLILikeWebPage extends WebPage 249{ 250 public function add_comment($sText) 251 { 252 $this->add('#'.$sText."<br/>\n"); 253 } 254} 255 256///////////////////////////////// 257// Main program 258 259if (utils::IsModeCLI()) 260{ 261 $oP = new CLIPage(Dict::S("TitleSynchroExecution")); 262} 263else 264{ 265 $oP = new CLILikeWebPage(Dict::S("TitleSynchroExecution")); 266} 267 268try 269{ 270 utils::UseParamFile(); 271} 272catch(Exception $e) 273{ 274 $oP->p("Error: ".$e->GetMessage()); 275 $oP->output(); 276 exit -2; 277} 278 279if (utils::IsModeCLI()) 280{ 281 // Next steps: 282 // specific arguments: 'csvfile' 283 // 284 $sAuthUser = ReadMandatoryParam($oP, 'auth_user', 'raw_data'); 285 $sAuthPwd = ReadMandatoryParam($oP, 'auth_pwd', 'raw_data'); 286 $sCsvFile = ReadMandatoryParam($oP, 'csvfile', 'raw_data'); 287 if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) 288 { 289 UserRights::Login($sAuthUser); // Login & set the user's language 290 } 291 else 292 { 293 $oP->p("Access restricted or wrong credentials ('$sAuthUser')"); 294 $oP->output(); 295 exit -1; 296 } 297 298 if (!is_readable($sCsvFile)) 299 { 300 $oP->p("Input file could not be found or could not be read: '$sCsvFile'"); 301 $oP->output(); 302 exit -1; 303 } 304 $sCSVData = file_get_contents($sCsvFile); 305 306} 307else 308{ 309 $_SESSION['login_mode'] = 'basic'; 310 require_once(APPROOT.'/application/loginwebpage.class.inc.php'); 311 LoginWebPage::DoLogin(); // Check user rights and prompt if needed 312 313 $sCSVData = utils::ReadPostedParam('csvdata', '', false, 'raw_data'); 314} 315 316 317try 318{ 319 ////////////////////////////////////////////////// 320 // 321 // Read parameters 322 // 323 $iDataSourceId = ReadMandatoryParam($oP, 'data_source_id', 'raw_data'); 324 $sSynchronize = ReadParam($oP, 'synchronize'); 325 $sSep = ReadParam($oP, 'separator', 'raw_data'); 326 $sQualifier = ReadParam($oP, 'qualifier', 'raw_data'); 327 $sCharSet = ReadParam($oP, 'charset', 'raw_data'); 328 $sDateTimeFormat = ReadParam($oP, 'date_format', 'raw_data'); 329 if ($sDateTimeFormat == '') 330 { 331 $sDateTimeFormat = 'Y-m-d H:i:s'; // By default use the SQL date & time format 332 } 333 if (strpos($sDateTimeFormat, '%') !== false) 334 { 335 $sDateTimeFormat = utils::DateTimeFormatToPHP($sDateTimeFormat); 336 } 337 $oDateTimeFormat = new DateTimeFormat($sDateTimeFormat); 338 $sDateFormat = $oDateTimeFormat->ToDateFormat(); // Keep only the date part 339 $sOutput = ReadParam($oP, 'output'); 340// $sReportLevel = ReadParam($oP, 'reportlevel'); 341 $sSimulate = ReadParam($oP, 'simulate'); 342 $sComment = ReadParam($oP, 'comment', 'raw_data'); 343 $sNoStopOnImportError = ReadParam($oP, 'no_stop_on_import_error'); 344 345 if (strtolower(trim($sSep)) == 'tab') 346 { 347 $sSep = "\t"; 348 } 349 350 // In case there is a difference between the web server time and the DB server time, 351 // use the DB server time as a reference since this date/time will be compared with the "status_last_seen" 352 // column, which is populated by MySQL triggers (and so based on the DB server time) 353 $oLoadStartDate = new DateTime(CMDBSource::QueryToScalar('SELECT NOW()')); // Now... but as read from the database 354 355 // Note about date formatting: These MySQL settings are read-only... and in fact unused :-( 356 // SET SESSION date_format = '%d/%m/%Y'; 357 // SET SESSION datetime_format = '%d/%m/%Y %H:%i:%s'; 358 // Therefore, we have to allow users to transform the format according to a given specification: date_format 359 360 361 ////////////////////////////////////////////////// 362 // 363 // Statistics 364 // 365 $iCountErrors = 0; 366 $iCountCreations = 0; 367 $iCountUpdates = 0; 368 369 ////////////////////////////////////////////////// 370 // 371 // Check parameters format/consistency 372 // 373 if (strlen($sCSVData) == 0) 374 { 375 throw new ExchangeException("Missing data - at least one line is expected"); 376 } 377 378 $oDataSource = MetaModel::GetObject('SynchroDataSource', $iDataSourceId, false); 379 if (is_null($oDataSource)) 380 { 381 throw new ExchangeException("Unknown data source id: '$iDataSourceId'"); 382 } 383 $sClass = $oDataSource->GetTargetClass(); 384 385 if (strlen($sSep) > 1) 386 { 387 throw new ExchangeException("Separator is limited to one character, found '$sSep'"); 388 } 389 390 if (strlen($sQualifier) > 1) 391 { 392 throw new ExchangeException("Text qualifier is limited to one character, found '$sQualifier'"); 393 } 394 395 if (!in_array($sOutput, array('retcode', 'summary', 'details'))) 396 { 397 throw new ExchangeException("Unknown output format: '$sOutput'"); 398 } 399 400/* 401 $aReportLevels = explode('|', $sReportLevel); 402 foreach($aReportLevels as $sLevel) 403 { 404 if (!in_array($sLevel, explode('|', 'errors|warnings|created|changed|unchanged'))) 405 { 406 throw new ExchangeException("Unknown level in reporting level: '$sLevel'"); 407 } 408 } 409*/ 410 411 if ($sSimulate == '1') 412 { 413 $bSimulate = true; 414 } 415 else 416 { 417 $bSimulate = false; 418 } 419 420 if ($sSynchronize == '1') 421 { 422 $bSynchronize = true; 423 } 424 else 425 { 426 $bSynchronize = false; 427 } 428 429 ////////////////////////////////////////////////// 430 // 431 // Parse first line, check attributes, analyse the request 432 // 433 if ($sCharSet == 'UTF-8') 434 { 435 $sUTF8Data = $sCSVData; 436 } 437 else 438 { 439 $sUTF8Data = iconv($sCharSet, 'UTF-8//IGNORE//TRANSLIT', $sCSVData); 440 } 441 $oCSVParser = new CSVParser($sUTF8Data, $sSep, $sQualifier, MetaModel::GetConfig()->Get('max_execution_time_per_loop')); 442 443 $aInputColumns = $oCSVParser->ListFields(); 444 $iColCount = count($aInputColumns); 445 446 // Check columns 447 $aColumns = $oDataSource->GetSQLColumns(); 448 449 $aDateColumns = array(); 450 foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) 451 { 452 if ($oAttDef instanceof AttributeDate) 453 { 454 $aDateColumns[$sAttCode] = 'DATE'; 455 } 456 elseif ($oAttDef instanceof AttributeDateTime) 457 { 458 $aDateColumns[$sAttCode] = 'DATETIME'; 459 } 460 } 461 462 $aIsDateToTransform = array(); 463 $aDateToTransformReport = array(); 464 foreach($aInputColumns as $iFieldId => $sInputColumn) 465 { 466 if (array_key_exists($sInputColumn, $aDateColumns)) 467 { 468 $aIsDateToTransform[$iFieldId] = $aDateColumns[$sInputColumn]; // either DATE or DATETIME 469 $aDateToTransformReport[] = $sInputColumn; 470 } 471 else 472 { 473 $aIsDateToTransform[$iFieldId] = false; 474 } 475 476 if ($sInputColumn == 'primary_key') 477 { 478 $iPrimaryKeyCol = $iFieldId; 479 continue; 480 } 481 if (!array_key_exists($sInputColumn, $aColumns)) 482 { 483 throw new ExchangeException("Unknown column '$sInputColumn' (class: '$sClass')"); 484 } 485 } 486 if (!isset($iPrimaryKeyCol)) 487 { 488 throw new ExchangeException("Missing reconciliation column 'primary_key'"); 489 } 490 491 ////////////////////////////////////////////////// 492 // 493 // Go for parsing and interpretation 494 // 495 try 496 { 497 if ($sOutput == 'details') 498 { 499 $oP->add_comment('------------------------------------------------------------'); 500 $oP->add_comment(' Import phase'); 501 $oP->add_comment('------------------------------------------------------------'); 502 } 503 504 if ($bSimulate) 505 { 506 CMDBSource::Query('START TRANSACTION'); 507 } 508 $aData = $oCSVParser->ToArray(); 509 $iLineCount = count($aData); 510 511 $sTable = $oDataSource->GetDataTable(); 512 513 // Prepare insert columns 514 $sInsertColumns = '`'.implode('`, `', $aInputColumns).'`'; 515 516 $iPreviousTimeLimit = ini_get('max_execution_time'); 517 $iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop'); 518 $oMutex = new iTopMutex('synchro_import_'.$oDataSource->GetKey()); 519 $oMutex->Lock(); 520 foreach($aData as $iRow => $aRow) 521 { 522 set_time_limit($iLoopTimeLimit); 523 $sReconciliationCondition = "`primary_key` = ".CMDBSource::Quote($aRow[$iPrimaryKeyCol]); 524 $sSelect = "SELECT COUNT(*) FROM `$sTable` WHERE $sReconciliationCondition"; 525 $aRes = CMDBSource::QueryToArray($sSelect); 526 $iCount = $aRes[0]['COUNT(*)']; 527 528 if ($iCount == 0) 529 { 530 // No record... create it 531 // 532 $iCountCreations++; 533 if ($sOutput == 'details') 534 { 535 $oP->add("$iRow: New entry, reconciliation: '$sReconciliationCondition'\n"); 536 } 537 538 $aValues = array(); // Used to build the insert query 539 foreach ($aRow as $iCol => $value) 540 { 541 if (is_null($value)) 542 { 543 $aValues[] = 'NULL'; 544 } 545 elseif ($aIsDateToTransform[$iCol] !== false) 546 { 547 $bDateOnly = false; 548 $sFormat = $sDateTimeFormat; 549 if ($aIsDateToTransform[$iCol] == 'DATE') 550 { 551 $bDateOnly = true; 552 $sFormat = $sDateFormat; 553 } 554 $sDate = ChangeDateFormat($value, $sFormat, $bDateOnly); 555 if ($sDate === false) 556 { 557 $aValues[] = CMDBSource::Quote(''); 558 if ($sOutput == 'details') 559 { 560 $oP->add("$iRow: Wrong format for {$aIsDateToTransform[$iCol]} column $iCol: '$value' does not match the expected format: '$sFormat' (column skipped)\n"); 561 } 562 } 563 else 564 { 565 $aValues[] = CMDBSource::Quote($sDate); 566 } 567 } 568 else 569 { 570 $aValues[] = CMDBSource::Quote($value); 571 } 572 } 573 $sValues = implode(', ', $aValues); 574 $sInsert = "INSERT INTO `$sTable` ($sInsertColumns) VALUES ($sValues)"; 575 try 576 { 577 CMDBSource::Query($sInsert); 578 } 579 catch(Exception $e) 580 { 581 if ($sNoStopOnImportError == '1') 582 { 583 $iCountErrors++; 584 $oP->add("$iRow: Import error '".$e->getMessage()."' (continuing)...\n"); 585 } 586 else // Fatal error 587 { 588 throw $e; 589 } 590 } 591 } 592 elseif ($iCount == 1) 593 { 594 // Found a match... update it 595 // 596 $iCountUpdates++; 597 if ($sOutput == 'details') 598 { 599 $oP->add("$iRow: Update entry, reconciliation: '$sReconciliationCondition'\n"); 600 } 601 602 $aValuePairs = array(); // Used to build the update query 603 foreach ($aRow as $iCol => $value) 604 { 605 // Skip reconciliation column 606 if ($iCol == $iPrimaryKeyCol) continue; 607 608 $sCol = $aInputColumns[$iCol]; 609 if ($aIsDateToTransform[$iCol] !== false) 610 { 611 $bDateOnly = false; 612 $sFormat = $sDateTimeFormat; 613 if ($aIsDateToTransform[$iCol] == 'DATE') 614 { 615 $bDateOnly = true; 616 $sFormat = $sDateFormat; 617 } 618 $sDate = ChangeDateFormat($value, $sFormat, $bDateOnly); 619 if ($sDate === false) 620 { 621 if ($sOutput == 'details') 622 { 623 $oP->add("$iRow: Wrong format for {$aIsDateToTransform[$iCol]} column $iCol: '$value' does not match the expected format: '$sFormat' (column skipped)\n"); 624 } 625 } 626 else 627 { 628 $aValuePairs[] = "`$sCol` = ".CMDBSource::Quote($sDate); 629 } 630 } 631 else 632 { 633 $aValuePairs[] = "`$sCol` = ".CMDBSource::Quote($aRow[$iCol]); 634 } 635 } 636 $sValuePairs = implode(', ', $aValuePairs); 637 $sUpdateQuery = "UPDATE `$sTable` SET $sValuePairs WHERE $sReconciliationCondition"; 638 try 639 { 640 CMDBSource::Query($sUpdateQuery); 641 } 642 catch(Exception $e) 643 { 644 if ($sNoStopOnImportError == '1') 645 { 646 $iCountErrors++; 647 $oP->add("$iRow: Import error '".$e->getMessage()."' (continuing)...\n"); 648 } 649 else // Fatal error 650 { 651 throw $e; 652 } 653 } 654 } 655 else 656 { 657 // Too many records... ambiguity 658 // 659 $iCountErrors++; 660 $oP->add("$iRow: Error - Failed to reconcile, found $iCount rows having '$sReconciliationCondition'\n"); 661 } 662 } 663 $oMutex->Unlock(); 664 set_time_limit($iPreviousTimeLimit); 665 666 if (($sOutput == "summary") || ($sOutput == 'details')) 667 { 668 $oP->add_comment('------------------------------------------------------------'); 669 $oP->add_comment(' Import phase summary'); 670 $oP->add_comment('------------------------------------------------------------'); 671 $oP->add_comment("Data Source: ".$iDataSourceId); 672 $oP->add_comment("Synchronize: ".($bSynchronize ? '1' : '0')); 673 $oP->add_comment("Class: ".$sClass); 674 $oP->add_comment("Separator: ".$sSep); 675 $oP->add_comment("Qualifier: ".$sQualifier); 676 $oP->add_comment("Charset Encoding:".$sCharSet); 677 678 if (strlen($sDateTimeFormat) > 0) 679 { 680 $aDateTimeColumns = array(); 681 $aDateColumns = array(); 682 foreach($aIsDateToTransform as $iCol => $sType) 683 { 684 if ($sType !== false) 685 { 686 $sCol = $aInputColumns[$iCol]; 687 if ($sType == 'DATE') 688 { 689 $aDateColumns[] = $sCol; 690 } 691 else 692 { 693 $aDateTimeColumns[] = $sCol; 694 } 695 } 696 } 697 $sFormatedDateTimeColumns = (count($aDateTimeColumns) > 0) ? ', applied to columns ['.implode(', ', $aDateTimeColumns).']' : ''; 698 $sFormatedDateColumns = (count($aDateColumns) > 0) ? ', applied to columns ['.implode(', ', $aDateColumns).']' : ''; 699 $oP->add_comment("Date and time format: '$sDateTimeFormat' $sFormatedDateTimeColumns"); 700 $oP->add_comment("Date only format: '$sDateFormat' $sFormatedDateColumns"); 701 } 702 else 703 { 704 // shall never get there 705 $oP->add_comment("Date format: <none>"); 706 } 707 $oP->add_comment("Data Size: ".strlen($sCSVData)); 708 $oP->add_comment("Data Lines: ".$iLineCount); 709 $oP->add_comment("Columns: ".implode(', ', $aInputColumns)); 710 $oP->add_comment("Output format: ".$sOutput); 711 // $oP->add_comment("Report level: ".$sReportLevel); 712 $oP->add_comment("Simulate: ".($bSimulate ? '1' : '0')); 713 $oP->add_comment("Change tracking comment: ".$sComment); 714 $oP->add_comment("Issues (before synchro): ".$iCountErrors); 715 // $oP->add_comment("Warnings: ".$iCountWarnings); 716 $oP->add_comment("Created (before synchro): ".$iCountCreations); 717 $oP->add_comment("Updated (before synchro): ".$iCountUpdates); 718 } 719 720 ////////////////////////////////////////////////// 721 // 722 // Synchronize 723 // 724 if ($bSynchronize) 725 { 726 $oSynchroExec = new SynchroExecution($oDataSource, $oLoadStartDate); 727 $oStatLog = $oSynchroExec->Process(); 728 if ($sOutput == 'details') 729 { 730 $oP->add_comment('------------------------------------------------------------'); 731 $oP->add_comment(' Synchronization phase'); 732 $oP->add_comment('------------------------------------------------------------'); 733 $iCount = 0; 734 foreach ($oStatLog->GetTraces() as $sMessage) 735 { 736 $iCount++; 737 $oP->add_comment($sMessage); 738 } 739 if ($iCount == 0) 740 { 741 $oP->add_comment(' No traces. (To activate the traces set "synchro_trace" => true in the configuration file)'); 742 } 743 } 744 if ($oStatLog->Get('status') == 'error') 745 { 746 $oP->p("ERROR: ".$oStatLog->Get('last_error')); 747 } 748 $oP->add_comment('------------------------------------------------------------'); 749 $oP->add_comment(' Synchronization phase summary'); 750 $oP->add_comment('------------------------------------------------------------'); 751 $oP->add_comment("Replicas: ".$oStatLog->Get('stats_nb_replica_total')); 752 $oP->add_comment("Replicas touched since last synchro: ".$oStatLog->Get('stats_nb_replica_seen')); 753 $oP->add_comment("Objects deleted: ".$oStatLog->Get('stats_nb_obj_deleted')); 754 $oP->add_comment("Objects deletion errors: ".$oStatLog->Get('stats_nb_obj_deleted_errors')); 755 $oP->add_comment("Objects obsoleted: ".$oStatLog->Get('stats_nb_obj_obsoleted')); 756 $oP->add_comment("Objects obsolescence errors: ".$oStatLog->Get('stats_nb_obj_obsoleted_errors')); 757 $oP->add_comment("Objects created: ".$oStatLog->Get('stats_nb_obj_created')." (".$oStatLog->Get('stats_nb_obj_created_warnings')." warnings)"); 758 $oP->add_comment("Objects creation errors: ".$oStatLog->Get('stats_nb_obj_created_errors')); 759 $oP->add_comment("Objects updated: ".$oStatLog->Get('stats_nb_obj_updated')." (".$oStatLog->Get('stats_nb_obj_updated_warnings')." warnings)"); 760 $oP->add_comment("Objects update errors: ".$oStatLog->Get('stats_nb_obj_updated_errors')); 761 $oP->add_comment("Objects reconciled (updated): ".$oStatLog->Get('stats_nb_obj_new_updated')." (".$oStatLog->Get('stats_nb_obj_new_updated_warnings')." warnings)"); 762 $oP->add_comment("Objects reconciled (unchanged): ".$oStatLog->Get('stats_nb_obj_new_unchanged')." (".$oStatLog->Get('stats_nb_obj_new_updated_warnings')." warnings)"); 763 $oP->add_comment("Objects reconciliation errors: ".$oStatLog->Get('stats_nb_replica_reconciled_errors')); 764 $oP->add_comment("Replica disappeared, no action taken: ".$oStatLog->Get('stats_nb_replica_disappeared_no_action')); 765 } 766 } 767 catch(Exception $e) 768 { 769 if ($bSimulate) 770 { 771 CMDBSource::Query('ROLLBACK'); 772 } 773 throw $e; 774 } 775 if ($bSimulate) 776 { 777 CMDBSource::Query('ROLLBACK'); 778 } 779 780 ////////////////////////////////////////////////// 781 // 782 // Summary of settings and results 783 // 784 if ($sOutput == 'retcode') 785 { 786 $oP->add($iCountErrors); 787 } 788} 789catch(ExchangeException $e) 790{ 791 $oP->add_comment($e->getMessage()); 792} 793catch(SecurityException $e) 794{ 795 $oP->add_comment($e->getMessage()); 796} 797catch(Exception $e) 798{ 799 $oP->add_comment((string)$e); 800} 801 802$oP->output(); 803?> 804