synchrodatasource.class.inc.php 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963
  1. <?php
  2. // Copyright (C) 2010 Combodo SARL
  3. //
  4. // This program is free software; you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation; version 3 of the License.
  7. //
  8. // This program is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU General Public License
  14. // along with this program; if not, write to the Free Software
  15. // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  16. /**
  17. * Data Exchange - synchronization with external applications (incoming data)
  18. *
  19. * @author Erwan Taloc <erwan.taloc@combodo.com>
  20. * @author Romain Quetiez <romain.quetiez@combodo.com>
  21. * @author Denis Flaven <denis.flaven@combodo.com>
  22. * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
  23. */
  24. class SynchroDataSource extends cmdbAbstractObject
  25. {
  26. public static function Init()
  27. {
  28. $aParams = array
  29. (
  30. "category" => "core/cmdb,view_in_gui",
  31. "key_type" => "autoincrement",
  32. "name_attcode" => array('name'),
  33. "state_attcode" => "",
  34. "reconc_keys" => array(),
  35. "db_table" => "priv_sync_datasource",
  36. "db_key_field" => "id",
  37. "db_finalclass_field" => "realclass",
  38. "display_template" => "",
  39. "icon" => "../images/synchro.png",
  40. );
  41. MetaModel::Init_Params($aParams);
  42. //MetaModel::Init_InheritAttributes();
  43. MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
  44. MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
  45. MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('implementation,production,obsolete'), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array())));
  46. MetaModel::Init_AddAttribute(new AttributeExternalKey("user_id", array("targetclass"=>"User", "jointype"=>null, "allowed_values"=>null, "sql"=>"user_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
  47. MetaModel::Init_AddAttribute(new AttributeClass("scope_class", array("class_category"=>"bizmodel", "more_values"=>"", "sql"=>"scope_class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
  48. MetaModel::Init_AddAttribute(new AttributeString("scope_restriction", array("allowed_values"=>null, "sql"=>"scope_restriction", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
  49. //MetaModel::Init_AddAttribute(new AttributeDateTime("last_synchro_date", array("allowed_values"=>null, "sql"=>"last_synchro_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
  50. // Format: '1 hour', '2 weeks', '3 hoursABCDEF'... Cf DateTime->Modify()
  51. MetaModel::Init_AddAttribute(new AttributeString("full_load_periodicity", array("allowed_values"=>null, "sql"=>"full_load_periodicity", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
  52. // MetaModel::Init_AddAttribute(new AttributeString("reconciliation_list", array("allowed_values"=>null, "sql"=>"reconciliation_list", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
  53. MetaModel::Init_AddAttribute(new AttributeEnum("reconciliation_policy", array("allowed_values"=>new ValueSetEnum('use_primary_key,use_attributes'), "sql"=>"reconciliation_policy", "default_value"=>"use_attributes", "is_null_allowed"=>false, "depends_on"=>array())));
  54. MetaModel::Init_AddAttribute(new AttributeEnum("action_on_zero", array("allowed_values"=>new ValueSetEnum('create,error'), "sql"=>"action_on_zero", "default_value"=>"create", "is_null_allowed"=>false, "depends_on"=>array())));
  55. MetaModel::Init_AddAttribute(new AttributeEnum("action_on_one", array("allowed_values"=>new ValueSetEnum('update,error,delete'), "sql"=>"action_on_one", "default_value"=>"update", "is_null_allowed"=>false, "depends_on"=>array())));
  56. MetaModel::Init_AddAttribute(new AttributeEnum("action_on_multiple", array("allowed_values"=>new ValueSetEnum('take_first,create,error'), "sql"=>"action_on_multiple", "default_value"=>"error", "is_null_allowed"=>false, "depends_on"=>array())));
  57. MetaModel::Init_AddAttribute(new AttributeEnum("delete_policy", array("allowed_values"=>new ValueSetEnum('ignore,delete,update,update_then_delete'), "sql"=>"delete_policy", "default_value"=>"ignore", "is_null_allowed"=>false, "depends_on"=>array())));
  58. MetaModel::Init_AddAttribute(new AttributeString("delete_policy_update", array("allowed_values"=>null, "sql"=>"delete_policy_update", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
  59. // Format: '1 hour', '2 weeks', '3 hoursABCDEF'... Cf DateTime->Modify()
  60. MetaModel::Init_AddAttribute(new AttributeString("delete_policy_retention", array("allowed_values"=>null, "sql"=>"delete_policy_retention", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
  61. MetaModel::Init_AddAttribute(new AttributeLinkedSet("attribute_list", array("linked_class"=>"SynchroAttribute", "ext_key_to_me"=>"sync_source_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array())));
  62. // Display lists
  63. MetaModel::Init_SetZListItems('details', array('name', 'description', 'scope_class', 'scope_restriction', 'status', 'user_id', 'full_load_periodicity', 'reconciliation_policy', 'action_on_zero', 'action_on_one', 'action_on_multiple', 'delete_policy', 'delete_policy_update', 'delete_policy_retention', /*'attribute_list'*/)); // Attributes to be displayed for the complete details
  64. MetaModel::Init_SetZListItems('list', array('scope_class', 'status', 'user_id', 'full_load_periodicity')); // Attributes to be displayed for a list
  65. // Search criteria
  66. MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'scope_class', 'user_id')); // Criteria of the std search form
  67. // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
  68. }
  69. public function DisplayBareRelations(WebPage $oPage, $bEditMode = false)
  70. {
  71. if (!$this->IsNew())
  72. {
  73. $oPage->SetCurrentTab(Dict::S('Core:SynchroAttributes'));
  74. $oAttributeSet = $this->Get('attribute_list');
  75. $aAttributes = array();
  76. while($oAttribute = $oAttributeSet->Fetch())
  77. {
  78. $aAttributes[$oAttribute->Get('attcode')] = $oAttribute;
  79. }
  80. $aAttribs = array(
  81. 'attcode' => array('label'=>'Attribute', 'description' => 'Field of the object'),
  82. 'reconciliation' => array('label'=>'Reconciliation ?', 'description' => 'Used for searching'),
  83. 'update' => array('label'=>'Update ?', 'description' => 'Used to update the object'),
  84. 'update_policy' => array('label'=>'Update Policy', 'description' => 'Behavior of the updated field'),
  85. );
  86. $aValues = array();
  87. foreach(MetaModel::ListAttributeDefs($this->GetTargetClass()) as $sAttCode=>$oAttDef)
  88. {
  89. if ($oAttDef->IsScalar() && $oAttDef->IsWritable())
  90. {
  91. if (isset($aAttributes[$sAttCode]))
  92. {
  93. $oAttribute = $aAttributes[$sAttCode];
  94. }
  95. else
  96. {
  97. $oAttribute = new SynchroAttribute();
  98. $oAttribute->Set('sync_source_id', $this->GetKey());
  99. $oAttribute->Set('attcode', $sAttCode);
  100. $oAttribute->Set('reconcile', MetaModel::IsReconcKey($this->GetTargetClass(), $sAttCode) ? 1 : 0);
  101. $oAttribute->Set('update', 1);
  102. $oAttribute->Set('update_policy', 'master_locked');
  103. }
  104. if (!$bEditMode)
  105. {
  106. // Read-only mode
  107. $aRow['reconciliation'] = $oAttribute->Get('reconcile') == 1 ? Dict::S('UI:Synchro:Yes') : Dict::S('UI:Synchro:No');
  108. $aRow['update'] = $oAttribute->Get('update') == 1 ? Dict::S('UI:Synchro:Yes') : Dict::S('UI:Synchro:No');
  109. $aRow['attcode'] = MetaModel::GetLabel($this->GetTargetClass(), $oAttribute->Get('attcode'));
  110. $aRow['update_policy'] = $oAttribute->GetAsHTML('update_policy');
  111. }
  112. else
  113. {
  114. // Read-only mode
  115. $sAttCode = $oAttribute->Get('attcode');
  116. $sChecked = $oAttribute->Get('reconcile') == 1 ? 'checked' : '';
  117. $aRow['reconciliation'] = "<input type=\"checkbox\" name=\"reconciliation[$sAttCode]\" $sChecked/>";
  118. $sChecked = $oAttribute->Get('update') == 1 ? 'checked' : '';
  119. $aRow['update'] = "<input type=\"checkbox\" name=\"update[$sAttCode]\" $sChecked/>";
  120. $aRow['attcode'] = MetaModel::GetLabel($this->GetTargetClass(), $oAttribute->Get('attcode'));
  121. $oAttDef = MetaModel::GetAttributeDef(get_class($oAttribute), 'update_policy');
  122. $aRow['update_policy'] = cmdbAbstractObject::GetFormElementForField($oPage, get_class($oAttribute), 'update_policy', $oAttDef, $oAttribute->Get('update_policy'), '', 'update_policy_'.$sAttCode, "[$sAttCode]");
  123. }
  124. $aValues[] = $aRow;
  125. }
  126. }
  127. $oPage->Table($aAttribs, $aValues);
  128. }
  129. parent::DisplayBareRelations($oPage, $bEditMode);
  130. }
  131. public function GetAttributeFlags($sAttCode)
  132. {
  133. if (($sAttCode == 'scope_class') && (!$this->IsNew()))
  134. {
  135. return OPT_ATT_READONLY;
  136. }
  137. return parent::GetAttributeFlags($sAttCode);
  138. }
  139. public function UpdateObject($sFormPrefix = '')
  140. {
  141. parent::UpdateObject($sFormPrefix);
  142. // And now read the other post parameters...
  143. $oAttributeSet = $this->Get('attribute_list');
  144. $aAttributes = array();
  145. while($oAttribute = $oAttributeSet->Fetch())
  146. {
  147. $aAttributes[$oAttribute->Get('attcode')] = $oAttribute;
  148. }
  149. $aReconcile = utils::ReadPostedParam('reconciliation', array());
  150. $aUpdate = utils::ReadPostedParam('update', array());
  151. $aUpdatePolicy = utils::ReadPostedParam('attr_update_policy', array());
  152. // update_policy cannot be empty, so there is one entry per attribute, use this to iterate
  153. // through all the writable attributes
  154. foreach($aUpdatePolicy as $sAttCode => $sValue)
  155. {
  156. if(!isset($aAttributes[$sAttCode]))
  157. {
  158. $oAttribute = new SynchroAttribute();
  159. $oAttribute->Set('sync_source_id', $this->GetKey());
  160. $oAttribute->Set('attcode', $sAttCode);
  161. }
  162. else
  163. {
  164. $oAttribute = $aAttributes[$sAttCode];
  165. }
  166. $bReconcile = 0;
  167. if (isset($aReconcile[$sAttCode]))
  168. {
  169. $bReconcile = $aReconcile[$sAttCode] == 'on' ? 1 : 0;
  170. }
  171. $bUpdate = 0 ; // Default / initial value
  172. if (isset($aUpdate[$sAttCode]))
  173. {
  174. $bUpdate = $aUpdate[$sAttCode] == 'on' ? 1 : 0;
  175. }
  176. $oAttribute->Set('reconcile', $bReconcile);
  177. $oAttribute->Set('update', $bUpdate);
  178. $oAttribute->Set('update_policy', $sValue);
  179. $oAttributeSet->AddObject($oAttribute);
  180. }
  181. $this->Set('attribute_list', $oAttributeSet);
  182. }
  183. public function GetTargetClass()
  184. {
  185. return $this->Get('scope_class');
  186. }
  187. public function GetDataTable()
  188. {
  189. $sName = strtolower($this->GetTargetClass());
  190. $sName = str_replace('\'"&@|\\/ ', '_', $sName); // Remove forbidden characters from the table name
  191. $sName .= '_'.$this->GetKey(); // Add a suffix for unicity
  192. $sTable = MetaModel::GetConfig()->GetDBSubName()."synchro_data_$sName"; // Add the prefix if any
  193. return $sTable;
  194. }
  195. /**
  196. * When inserting a new datasource object, also create the SynchroAttribute objects
  197. * for each field of the target class
  198. */
  199. protected function OnInsert()
  200. {
  201. // Create all the SynchroAttribute records
  202. $oAttributeSet = $this->Get('attribute_list');
  203. foreach(MetaModel::ListAttributeDefs($this->GetTargetClass()) as $sAttCode=>$oAttDef)
  204. {
  205. if ($oAttDef->IsScalar() && $oAttDef->IsWritable())
  206. {
  207. $oAttribute = new SynchroAttribute();
  208. $oAttribute->Set('sync_source_id', $this->GetKey());
  209. $oAttribute->Set('attcode', $sAttCode);
  210. $oAttribute->Set('reconcile', MetaModel::IsReconcKey($this->GetTargetClass(), $sAttCode) ? 1 : 0);
  211. $oAttribute->Set('update', 1);
  212. $oAttribute->Set('update_policy', 'master_locked');
  213. $oAttributeSet->AddObject($oAttribute);
  214. }
  215. }
  216. $this->Set('attribute_list', $oAttributeSet);
  217. }
  218. /**
  219. * When the new datasource has been created, let's create the synchro_data table
  220. * that will hold the data records and the correspoding triggers which will maintain
  221. * both tables in sync
  222. */
  223. protected function AfterInsert()
  224. {
  225. parent::AfterInsert();
  226. $sTable = $this->GetDataTable();
  227. $aColumns = $this->GetSQLColumns();
  228. $aFieldDefs = array();
  229. // Allow '0', otherwise mysql will render an error when the id is not given
  230. // (the trigger is expected to set the value, but it is not executed soon enough)
  231. $aFieldDefs[] = "id INTEGER(11) NOT NULL DEFAULT 0 ";
  232. $aFieldDefs[] = "`primary_key` VARCHAR(255) NULL DEFAULT NULL";
  233. foreach($aColumns as $sColumn => $ColSpec)
  234. {
  235. $aFieldDefs[] = "`$sColumn` $ColSpec NULL DEFAULT NULL";
  236. }
  237. $aFieldDefs[] = "INDEX (id)";
  238. $aFieldDefs[] = "INDEX (primary_key)";
  239. $sFieldDefs = implode(', ', $aFieldDefs);
  240. $sCreateTable = "CREATE TABLE `$sTable` ($sFieldDefs) ENGINE = innodb;";
  241. CMDBSource::Query($sCreateTable);
  242. $sTriggerInsert = "CREATE TRIGGER `{$sTable}_bi` BEFORE INSERT ON $sTable";
  243. $sTriggerInsert .= " FOR EACH ROW";
  244. $sTriggerInsert .= " BEGIN";
  245. $sTriggerInsert .= " INSERT INTO priv_sync_replica (sync_source_id, status_last_seen, `status`) VALUES ({$this->GetKey()}, NOW(), 'new');";
  246. $sTriggerInsert .= " SET NEW.id = LAST_INSERT_ID();";
  247. $sTriggerInsert .= " END;";
  248. CMDBSource::Query($sTriggerInsert);
  249. $aModified = array();
  250. foreach($aColumns as $sColumn => $ColSpec)
  251. {
  252. // <=> is a null-safe 'EQUALS' operator (there is no equivalent for "DIFFERS FROM")
  253. $aModified[] = "NOT(NEW.`$sColumn` <=> OLD.`$sColumn`)";
  254. }
  255. $sIsModified = '('.implode(') OR (', $aModified).')';
  256. // Update the replica
  257. //
  258. // status is forced to "new" if the replica was obsoleted directly from the state "new" (dest_id = null)
  259. // otherwise, if status was either 'obsolete' or 'synchronized' it is turned into 'modified' or 'synchronized' depending on the changes
  260. // otherwise, the status is left as is
  261. $sTriggerUpdate = "CREATE TRIGGER `{$sTable}_bu` BEFORE UPDATE ON $sTable";
  262. $sTriggerUpdate .= " FOR EACH ROW";
  263. $sTriggerUpdate .= " BEGIN";
  264. $sTriggerUpdate .= " IF @itopuser is null THEN";
  265. $sTriggerUpdate .= " UPDATE priv_sync_replica SET status_last_seen = NOW(), `status` = IF(`status` = 'obsolete', IF(`dest_id` IS NULL, 'new', 'modified'), IF(`status` IN ('synchronized') AND ($sIsModified), 'modified', `status`)) WHERE sync_source_id = {$this->GetKey()} AND id = OLD.id;";
  266. $sTriggerUpdate .= " SET NEW.id = OLD.id;"; // make sure this id won't change
  267. $sTriggerUpdate .= " END IF;";
  268. $sTriggerUpdate .= " END;";
  269. CMDBSource::Query($sTriggerUpdate);
  270. $sTriggerInsert = "CREATE TRIGGER `{$sTable}_ad` AFTER DELETE ON $sTable";
  271. $sTriggerInsert .= " FOR EACH ROW";
  272. $sTriggerInsert .= " BEGIN";
  273. $sTriggerInsert .= " DELETE FROM priv_sync_replica WHERE id = OLD.id;";
  274. $sTriggerInsert .= " END;";
  275. CMDBSource::Query($sTriggerInsert);
  276. }
  277. protected function AfterDelete()
  278. {
  279. parent::AfterDelete();
  280. $sTable = $this->GetDataTable();
  281. $sDropTable = "DROP TABLE `$sTable`";
  282. CMDBSource::Query($sDropTable);
  283. // TO DO - check that triggers get dropped with the table
  284. }
  285. /**
  286. * Perform a synchronization between the data stored in the replicas (&synchro_data_xxx_xx table)
  287. * and the iTop objects. If the lastFullLoadStartDate is NOT specified then the full_load_periodicity
  288. * is used to determine which records are obsolete.
  289. * @param Hash $aDataToReplica Debugs/Trace information, one entry per replica
  290. * @param DateTime $oLastFullLoadStartDate Date of the last full load (start date/time), if known
  291. * @return void
  292. */
  293. public function Synchronize(&$aDataToReplica, $oLastFullLoadStartDate = null)
  294. {
  295. // Create a change used for logging all the modifications/creations happening during the synchro
  296. $oMyChange = MetaModel::NewObject("CMDBChange");
  297. $oMyChange->Set("date", time());
  298. $sUserString = CMDBChange::GetCurrentUserName();
  299. $oMyChange->Set("userinfo", $sUserString);
  300. $iChangeId = $oMyChange->DBInsert();
  301. // Get all the replicas that were not seen in the last import and mark them as obsolete
  302. if ($oLastFullLoadStartDate == null)
  303. {
  304. // No previous import known, use the full_load_periodicity value... and the current date
  305. $oLastFullLoadStartDate = new DateTime(); // Now
  306. // TO DO: how do we support localization here ??
  307. $sLoadPeriodicity = trim($this->Get('full_load_periodicity'));
  308. if (strlen($sLoadPeriodicity) > 0)
  309. {
  310. $sInterval = '-'.$sLoadPeriodicity;
  311. // Note: the PHP doc states that Modify return FALSE in case of error
  312. // but, this is actually NOT the case
  313. // Therefore, I do compare before and after, considering that the
  314. // format is incorrect when the datetime remains unchanged
  315. $sBefore = $oLastFullLoadStartDate->Format('Y-m-d H:i:s');
  316. $oLastFullLoadStartDate->Modify($sInterval);
  317. $sAfter = $oLastFullLoadStartDate->Format('Y-m-d H:i:s');
  318. if ($sBefore == $sAfter)
  319. {
  320. throw new CoreException("Data exchange: Wrong interval specification", array('interval' => $sInterval, 'source_id' => $this->GetKey()));
  321. }
  322. }
  323. }
  324. $sLimitDate = $oLastFullLoadStartDate->Format('Y-m-d H:i:s');
  325. // TO DO: remove trace
  326. echo "<p>sLimitDate: $sLimitDate</p>\n";
  327. $sSelectToObsolete = "SELECT SynchroReplica WHERE sync_source_id = :source_id AND status IN ('new', 'synchronized', 'modified', 'orphan') AND status_last_seen < :last_import";
  328. $oSetToObsolete = new DBObjectSet(DBObjectSearch::FromOQL($sSelectToObsolete), array() /* order by*/, array('source_id' => $this->GetKey(), 'last_import' => $sLimitDate));
  329. while($oReplica = $oSetToObsolete->Fetch())
  330. {
  331. // TO DO: take the appropriate action based on the 'delete_policy' field
  332. $sUpdateOnObsolete = $this->Get('delete_policy');
  333. if ( ($sUpdateOnObsolete == 'update') || ($sUpdateOnObsolete == 'update_then_delete') )
  334. {
  335. // TO DO: remove trace
  336. echo "<p>Destination object: (dest_id:".$oReplica->Get('dest_id').") to be updated.</p>";
  337. $aToUpdate = array();
  338. $aToUpdate = explode(';', $this->Get('delete_policy_update')); //ex: 'status:obsolete;description:stopped',
  339. foreach($aToUpdate as $sUpdateSpec)
  340. {
  341. $aUpdateSpec = explode(':', $sUpdateSpec);
  342. if (count($aUpdateSpec) == 2)
  343. {
  344. $sAttCode = $aUpdateSpec[0];
  345. $sValue = $aUpdateSpec[1];
  346. $aToUpdate[$sAttCode] = $sValue;
  347. }
  348. }
  349. $oReplica->UpdateDestObject($aToUpdate, $oMyChange);
  350. }
  351. // TO DO: remove trace
  352. echo "<p>Replica id:".$oReplica->GetKey()." (dest_id:".$oReplica->Get('dest_id').") marked as obsolete</p>";
  353. $oReplica->Set('status', 'obsolete');
  354. $oReplica->DBUpdateTracked($oMyChange);
  355. }
  356. // Get all the replicas that are 'new' or modified
  357. //
  358. // Get the list of SQL columns
  359. $sClass = $this->GetTargetClass();
  360. // TO DO: remove trace
  361. echo "<p>TargetClass: $sClass</p>";
  362. $aAttCodes = array();
  363. $sSelectAtt = "SELECT SynchroAttribute WHERE sync_source_id = :source_id AND update = 1";
  364. $oSetAtt = new DBObjectSet(DBObjectSearch::FromOQL($sSelectAtt), array() /* order by*/, array('source_id' => $this->GetKey()) /* aArgs */);
  365. while ($oSyncAtt = $oSetAtt->Fetch())
  366. {
  367. $aAttCodes[] = $oSyncAtt->Get('attcode');
  368. }
  369. $aColumns = $this->GetSQLColumns($aAttCodes);
  370. $aExtDataFields = array_keys($aColumns);
  371. $aExtDataFields[] = 'primary_key';
  372. $aExtDataSpec = array(
  373. 'table' => $this->GetDataTable(),
  374. 'join_key' => 'id',
  375. 'fields' => $aExtDataFields
  376. );
  377. // Get the list of reconciliation keys
  378. $aReconciliationKeys = array();
  379. if ($this->Get('reconciliation_policy') == 'use_attributes')
  380. {
  381. $sSelectAtt = "SELECT SynchroAttribute WHERE sync_source_id = :source_id AND reconcile = 1";
  382. $oAttSet = new DBObjectSet(DBObjectSearch::FromOQL($sSelectAtt), array() /* order by*/, array('source_id' => $this->GetKey()) /* aArgs */);
  383. while ($oSyncAtt = $oAttSet->Fetch())
  384. {
  385. $aReconciliationKeys[] = $oSyncAtt->Get('attcode');
  386. }
  387. }
  388. elseif ($this->Get('reconciliation_policy') == 'use_primary_key')
  389. {
  390. $aReconciliationKeys[] = "primary_key";
  391. }
  392. // TO DO: remove trace
  393. echo "Reconciliation on: {".implode(', ', $aReconciliationKeys)."}<br/>\n";
  394. $aAttributes = array();
  395. foreach($aAttCodes as $sAttCode)
  396. {
  397. $oAttDef = MetaModel::GetAttributeDef($this->GetTargetClass(), $sAttCode);
  398. if ($oAttDef->IsWritable() && $oAttDef->IsScalar())
  399. {
  400. $aAttributes[] = $sAttCode;
  401. }
  402. }
  403. $sSelectToSync = "SELECT SynchroReplica WHERE (status = 'new' OR status = 'modified') AND sync_source_id = :source_id";
  404. $oSetToSync = new DBObjectSet(DBObjectSearch::FromOQL($sSelectToSync), array() /* order by*/, array('source_id' => $this->GetKey()) /* aArgs */, $aExtDataSpec, 0 /* limitCount */, 0 /* limitStart */);
  405. while($oReplica = $oSetToSync->Fetch())
  406. {
  407. $oReplica->Synchro($this, $aReconciliationKeys, $aAttributes, $oMyChange);
  408. }
  409. // Get all the replicas that are to be deleted
  410. //
  411. $oDeletionDate = $oLastFullLoadStartDate;
  412. $sDeleteRetention = trim($this->Get('delete_policy_retention'));
  413. if (strlen($sDeleteRetention) > 0)
  414. {
  415. $sInterval = '-'.$sDeleteRetention;
  416. // Note: the PHP doc states that Modify return FALSE in case of error
  417. // but, this is actually NOT the case
  418. // Therefore, I do compare before and after, considering that the
  419. // format is incorrect when the datetime remains unchanged
  420. $sBefore = $oDeletionDate->Format('Y-m-d H:i:s');
  421. $oDeletionDate->Modify($sInterval);
  422. $sAfter = $oDeletionDate->Format('Y-m-d H:i:s');
  423. if ($sBefore == $sAfter)
  424. {
  425. throw new CoreException("Data exchange: Wrong interval specification", array('interval' => $sInterval, 'source_id' => $this->GetKey()));
  426. }
  427. }
  428. $sDeletionDate = $oDeletionDate->Format('Y-m-d H:i:s');
  429. // TO DO: remove trace
  430. echo "<p>sDeletionDate: $sDeletionDate</p>\n";
  431. $sSelectToDelete = "SELECT SynchroReplica WHERE sync_source_id = :source_id AND status IN ('obsolete') AND status_last_seen < :last_import";
  432. $oSetToDelete = new DBObjectSet(DBObjectSearch::FromOQL($sSelectToDelete), array() /* order by*/, array('source_id' => $this->GetKey(), 'last_import' => $sDeletionDate));
  433. while($oReplica = $oSetToDelete->Fetch())
  434. {
  435. $sUpdateOnObsolete = $this->Get('delete_policy');
  436. if ( ($sUpdateOnObsolete == 'delete') || ($sUpdateOnObsolete == 'update_then_delete') )
  437. {
  438. // TO DO: remove trace
  439. echo "<p>Destination object: (dest_id:".$oReplica->Get('dest_id').") to be DELETED.</p>";
  440. // TO DO: delete the dest object for real...
  441. $oReplica->DeleteDestObject($oMyChange);
  442. }
  443. // TO DO: remove trace
  444. echo "<p>Replica id:".$oReplica->GetKey()." (dest_id:".$oReplica->Get('dest_id').") to be deleted</p>";
  445. $oReplica->DBDeleteTracked($oMyChange);
  446. }
  447. return;
  448. }
  449. /**
  450. * Get the list of SQL columns corresponding to a particular list of attribute codes
  451. * Defaults to the whole list of columns for the current class
  452. */
  453. public function GetSQLColumns($aAttributeCodes = null)
  454. {
  455. $aColumns = array();
  456. $sClass = $this->GetTargetClass();
  457. if (is_null($aAttributeCodes))
  458. {
  459. $aAttributeCodes = array();
  460. foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
  461. {
  462. if ($sAttCode == 'finalclass') continue;
  463. $aAttributeCodes[] = $sAttCode;
  464. }
  465. }
  466. foreach($aAttributeCodes as $sAttCode)
  467. {
  468. $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
  469. foreach($oAttDef->GetSQLColumns() as $sField => $sDBFieldType)
  470. {
  471. $aColumns[$sField] = $sDBFieldType;
  472. }
  473. }
  474. return $aColumns;
  475. }
  476. public function IsRunning()
  477. {
  478. $sOQL = "SELECT SynchroLog WHERE sync_source_id = :source_id AND status='running'";
  479. $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array('start_date' => false) /* order by*/, array('source_id' => $this->GetKey()) /* aArgs */, array(), 1 /* limitCount */, 0 /* limitStart */);
  480. if ($oSet->Count() < 1)
  481. {
  482. $bRet = false;
  483. }
  484. else
  485. {
  486. $bRet = true;
  487. }
  488. return $bRet;
  489. }
  490. public function GetLatestLog()
  491. {
  492. $oLog = null;
  493. $sOQL = "SELECT SynchroLog WHERE sync_source_id = :source_id";
  494. $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array('start_date' => false) /* order by*/, array('source_id' => $this->GetKey()) /* aArgs */, array(), 1 /* limitCount */, 0 /* limitStart */);
  495. if ($oSet->Count() >= 1)
  496. {
  497. $oLog = $oSet->Fetch();
  498. }
  499. return $oLog;
  500. }
  501. /**
  502. * Retrieve from the log, the date of the last completed import
  503. * @return DateTime
  504. */
  505. public function GetLastCompletedImportDate()
  506. {
  507. $date = null;
  508. $sOQL = "SELECT SynchroLog WHERE sync_source_id = :source_id AND status='completed'";
  509. $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array('end_date' => false) /* order by*/, array('source_id' => $this->GetKey()) /* aArgs */, array(), 0 /* limitCount */, 0 /* limitStart */);
  510. if ($oSet->Count() >= 1)
  511. {
  512. $oLog = $oSet->Fetch();
  513. $date = $oLog->Get('end_date');
  514. }
  515. else
  516. {
  517. // TO DO: remove trace
  518. echo "<p>No completed log found</p>\n";
  519. }
  520. return $date;
  521. }
  522. }
  523. class SynchroAttribute extends cmdbAbstractObject
  524. {
  525. public static function Init()
  526. {
  527. $aParams = array
  528. (
  529. "category" => "core/cmdb,view_in_gui",
  530. "key_type" => "autoincrement",
  531. "name_attcode" => "",
  532. "state_attcode" => "",
  533. "reconc_keys" => array(),
  534. "db_table" => "priv_sync_att",
  535. "db_key_field" => "id",
  536. "db_finalclass_field" => "",
  537. "display_template" => "",
  538. );
  539. MetaModel::Init_Params($aParams);
  540. MetaModel::Init_InheritAttributes();
  541. MetaModel::Init_AddAttribute(new AttributeExternalKey("sync_source_id", array("targetclass"=>"SynchroDataSource", "jointype"=> "", "allowed_values"=>null, "sql"=>"sync_source_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
  542. MetaModel::Init_AddAttribute(new AttributeString("attcode", array("allowed_values"=>null, "sql"=>"attcode", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
  543. MetaModel::Init_AddAttribute(new AttributeBoolean("update", array("allowed_values"=>null, "sql"=>"update", "default_value"=>true, "is_null_allowed"=>false, "depends_on"=>array())));
  544. MetaModel::Init_AddAttribute(new AttributeBoolean("reconcile", array("allowed_values"=>null, "sql"=>"reconcile", "default_value"=>false, "is_null_allowed"=>false, "depends_on"=>array())));
  545. MetaModel::Init_AddAttribute(new AttributeEnum("update_policy", array("allowed_values"=>new ValueSetEnum('master_locked,master_unlocked,write_once'), "sql"=>"update_policy", "default_value"=>"master_locked", "is_null_allowed"=>false, "depends_on"=>array())));
  546. // Display lists
  547. MetaModel::Init_SetZListItems('details', array('sync_source_id', 'attcode', 'update', 'reconcile', 'update_policy')); // Attributes to be displayed for the complete details
  548. MetaModel::Init_SetZListItems('list', array('sync_source_id', 'attcode', 'update', 'reconcile', 'update_policy')); // Attributes to be displayed for a list
  549. // Search criteria
  550. // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
  551. // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
  552. }
  553. }
  554. class SynchroAttExtKey extends SynchroAttribute
  555. {
  556. public static function Init()
  557. {
  558. $aParams = array
  559. (
  560. "category" => "core/cmdb,view_in_gui",
  561. "key_type" => "autoincrement",
  562. "name_attcode" => "",
  563. "state_attcode" => "",
  564. "reconc_keys" => array(),
  565. "db_table" => "priv_sync_att_extkey",
  566. "db_key_field" => "id",
  567. "db_finalclass_field" => "",
  568. "display_template" => "",
  569. );
  570. MetaModel::Init_Params($aParams);
  571. MetaModel::Init_InheritAttributes();
  572. MetaModel::Init_AddAttribute(new AttributeString("reconciliation_attcode", array("allowed_values"=>null, "sql"=>"reconciliation_attcode", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
  573. // Display lists
  574. MetaModel::Init_SetZListItems('details', array('sync_source_id', 'attcode', 'update', 'reconcile', 'update_policy', 'reconciliation_attcode')); // Attributes to be displayed for the complete details
  575. MetaModel::Init_SetZListItems('list', array('sync_source_id', 'attcode', 'update', 'reconcile', 'update_policy')); // Attributes to be displayed for a list
  576. // Search criteria
  577. // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
  578. // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
  579. }
  580. }
  581. class SynchroAttLinkSet extends SynchroAttribute
  582. {
  583. public static function Init()
  584. {
  585. $aParams = array
  586. (
  587. "category" => "core/cmdb,view_in_gui",
  588. "key_type" => "autoincrement",
  589. "name_attcode" => "",
  590. "state_attcode" => "",
  591. "reconc_keys" => array(),
  592. "db_table" => "priv_sync_att_linkset",
  593. "db_key_field" => "id",
  594. "db_finalclass_field" => "",
  595. "display_template" => "",
  596. );
  597. MetaModel::Init_Params($aParams);
  598. MetaModel::Init_InheritAttributes();
  599. MetaModel::Init_AddAttribute(new AttributeString("row_separator", array("allowed_values"=>null, "sql"=>"row_separator", "default_value"=>'|', "is_null_allowed"=>true, "depends_on"=>array())));
  600. MetaModel::Init_AddAttribute(new AttributeString("attribute_separator", array("allowed_values"=>null, "sql"=>"attribute_separator", "default_value"=>';', "is_null_allowed"=>true, "depends_on"=>array())));
  601. // Display lists
  602. MetaModel::Init_SetZListItems('details', array('sync_source_id', 'attcode', 'update', 'reconcile', 'update_policy', 'row_separator', 'attribute_separator')); // Attributes to be displayed for the complete details
  603. MetaModel::Init_SetZListItems('list', array('sync_source_id', 'attcode', 'update', 'reconcile', 'update_policy')); // Attributes to be displayed for a list
  604. // Search criteria
  605. // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
  606. // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
  607. }
  608. }
  609. class SynchroLog extends CmdbAbstractObject
  610. {
  611. public static function Init()
  612. {
  613. $aParams = array
  614. (
  615. "category" => "core/cmdb,view_in_gui",
  616. "key_type" => "autoincrement",
  617. "name_attcode" => "",
  618. "state_attcode" => "",
  619. "reconc_keys" => array(),
  620. "db_table" => "priv_sync_log",
  621. "db_key_field" => "id",
  622. "db_finalclass_field" => "",
  623. "display_template" => "",
  624. );
  625. MetaModel::Init_Params($aParams);
  626. MetaModel::Init_InheritAttributes();
  627. MetaModel::Init_AddAttribute(new AttributeExternalKey("sync_source_id", array("targetclass"=>"SynchroDataSource", "jointype"=> "", "allowed_values"=>null, "sql"=>"sync_source_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
  628. MetaModel::Init_AddAttribute(new AttributeDateTime("start_date", array("allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
  629. MetaModel::Init_AddAttribute(new AttributeDateTime("end_date", array("allowed_values"=>null, "sql"=>"end_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
  630. MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('running,completed'), "sql"=>"status", "default_value"=>"running", "is_null_allowed"=>false, "depends_on"=>array())));
  631. MetaModel::Init_AddAttribute(new AttributeInteger("stats_nb_seen", array("allowed_values"=>null, "sql"=>"stats_nb_seen", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
  632. MetaModel::Init_AddAttribute(new AttributeInteger("stats_nb_modified", array("allowed_values"=>null, "sql"=>"stats_nb_modified", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
  633. MetaModel::Init_AddAttribute(new AttributeInteger("stats_nb_errors", array("allowed_values"=>null, "sql"=>"stats_nb_errors", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
  634. MetaModel::Init_AddAttribute(new AttributeInteger("stats_nb_created", array("allowed_values"=>null, "sql"=>"stats_nb_created", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
  635. MetaModel::Init_AddAttribute(new AttributeInteger("stats_nb_deleted", array("allowed_values"=>null, "sql"=>"stats_nb_deleted", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
  636. MetaModel::Init_AddAttribute(new AttributeInteger("stats_nb_reconciled", array("allowed_values"=>null, "sql"=>"stats_nb_reconciled", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
  637. // Display lists
  638. MetaModel::Init_SetZListItems('details', array('sync_source_id', 'start_date', 'end_date', 'status', 'stats_nb_seen', 'stats_nb_modified', 'stats_nb_errors', 'stats_nb_created', 'stats_nb_deleted', 'stats_nb_reconciled')); // Attributes to be displayed for the complete details
  639. MetaModel::Init_SetZListItems('list', array('sync_source_id', 'start_date', 'end_date', 'status', 'stats_nb_seen', 'stats_nb_modified', 'stats_nb_errors')); // Attributes to be displayed for a list
  640. // Search criteria
  641. // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
  642. // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
  643. }
  644. }
  645. class SynchroReplica extends DBObject
  646. {
  647. static $aSearches = array(); // Cache of OQL queries used for reconciliation (per data source)
  648. public static function Init()
  649. {
  650. $aParams = array
  651. (
  652. "category" => "core/cmdb,view_in_gui",
  653. "key_type" => "autoincrement",
  654. "name_attcode" => "",
  655. "state_attcode" => "",
  656. "reconc_keys" => array(),
  657. "db_table" => "priv_sync_replica",
  658. "db_key_field" => "id",
  659. "db_finalclass_field" => "",
  660. "display_template" => "",
  661. );
  662. MetaModel::Init_Params($aParams);
  663. MetaModel::Init_InheritAttributes();
  664. MetaModel::Init_AddAttribute(new AttributeExternalKey("sync_source_id", array("targetclass"=>"SynchroDataSource", "jointype"=> "", "allowed_values"=>null, "sql"=>"sync_source_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
  665. MetaModel::Init_AddAttribute(new AttributeInteger("dest_id", array("allowed_values"=>null, "sql"=>"dest_id", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array())));
  666. MetaModel::Init_AddAttribute(new AttributeClass("dest_class", array("class_category"=>"bizmodel", "more_values"=>"", "sql"=>"dest_class", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
  667. MetaModel::Init_AddAttribute(new AttributeDateTime("status_last_seen", array("allowed_values"=>null, "sql"=>"status_last_seen", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
  668. MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('new,synchronized,modified,orphan,obsolete'), "sql"=>"status", "default_value"=>"new", "is_null_allowed"=>false, "depends_on"=>array())));
  669. MetaModel::Init_AddAttribute(new AttributeBoolean("status_dest_creator", array("allowed_values"=>null, "sql"=>"status_dest_creator", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array())));
  670. MetaModel::Init_AddAttribute(new AttributeString("status_last_error", array("allowed_values"=>null, "sql"=>"status_last_error", "default_value"=>'', "is_null_allowed"=>true, "depends_on"=>array())));
  671. MetaModel::Init_AddAttribute(new AttributeDateTime("info_creation_date", array("allowed_values"=>null, "sql"=>"info_creation_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
  672. MetaModel::Init_AddAttribute(new AttributeDateTime("info_last_modified", array("allowed_values"=>null, "sql"=>"info_last_modified", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
  673. MetaModel::Init_AddAttribute(new AttributeDateTime("info_last_synchro", array("allowed_values"=>null, "sql"=>"info_last_synchro", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
  674. // Display lists
  675. MetaModel::Init_SetZListItems('details', array('sync_source_id', 'dest_id', 'dest_class', 'status_last_seen', 'status', 'status_dest_creator', 'status_last_error', 'info_creation_date', 'info_last_modified', 'info_last_synchro')); // Attributes to be displayed for the complete details
  676. MetaModel::Init_SetZListItems('list', array('sync_source_id', 'dest_id', 'dest_class', 'status_last_seen', 'status', 'status_dest_creator', 'status_last_error')); // Attributes to be displayed for a list
  677. // Search criteria
  678. MetaModel::Init_SetZListItems('standard_search', array('sync_source_id', 'status_last_seen', 'status', 'status_dest_creator', 'dest_class', 'dest_id', 'status_last_error')); // Criteria of the std search form
  679. // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
  680. }
  681. public function DBInsert()
  682. {
  683. throw new CoreException('A synchronization replica must be created only by the mean of triggers');
  684. }
  685. // Overload the deletion -> the replica has been created by the mean of a trigger,
  686. // it will be deleted by the mean of a trigger too
  687. public function DBDelete()
  688. {
  689. $oDataSource = MetaModel::GetObject('SynchroDataSource', $this->Get('sync_source_id'));
  690. $sTable = $oDataSource->GetDataTable();
  691. $sSQL = "DELETE FROM `$sTable` WHERE id = '{$this->GetKey()}'";
  692. CMDBSource::Query($sSQL);
  693. $this->m_bIsInDB = false;
  694. $this->m_iKey = null;
  695. }
  696. public function SetLastError($sMessage, $oException = null)
  697. {
  698. if ($oException)
  699. {
  700. $sText = $sMessage.$oException->getMessage();
  701. }
  702. else
  703. {
  704. $sText = $sMessage;
  705. }
  706. if (strlen($sText) > 255)
  707. {
  708. $sText = substr($sText, 0, 200).'...('.strlen($sText).' chars)...';
  709. }
  710. $this->Set('status_last_error', $sText);
  711. }
  712. public function Synchro($oDataSource, $aReconciliationKeys, $aAttributes, $oChange)
  713. {
  714. switch($this->Get('status'))
  715. {
  716. case 'new':
  717. // If needed, construct the query used for the reconciliation
  718. if (!isset(self::$aSearches[$oDataSource->GetKey()]))
  719. {
  720. foreach($aReconciliationKeys as $sFilterCode)
  721. {
  722. $aCriterias[] = ($sFilterCode == 'primary_key' ? 'id' : $sFilterCode).' = :'.$sFilterCode;
  723. }
  724. $sOQL = "SELECT ".$oDataSource->GetTargetClass()." WHERE ".implode(' AND ', $aCriterias);
  725. self::$aSearches[$oDataSource->GetKey()] = DBObjectSearch::FromOQL($sOQL);
  726. }
  727. // Get the criterias for the search
  728. $aFilterValues = array();
  729. foreach($aReconciliationKeys as $sFilterCode)
  730. {
  731. $aFilterValues[$sFilterCode] = $this->GetValueFromExtData($sFilterCode);
  732. }
  733. $oDestSet = new DBObjectSet(self::$aSearches[$oDataSource->GetKey()], array(), $aFilterValues);
  734. $iCount = $oDestSet->Count();
  735. // How many objects match the reconciliation criterias
  736. switch($iCount)
  737. {
  738. case 0:
  739. //echo "<p>Nothing found for: ".self::$aSearches[$oDataSource->GetKey()]->ToOQL(true, $aFilterValues)."</p>";
  740. $this->CreateObjectFromReplica($oDataSource->GetTargetClass(), $aAttributes, $oChange);
  741. break;
  742. case 1:
  743. //echo "<p>Found 1 for: ".self::$aSearches[$oDataSource->GetKey()]->ToOQL(true, $aFilterValues)."</p>";
  744. $oDestObj = $oDestSet->Fetch();
  745. $this->UpdateObjectFromReplica($oDestObj, $aAttributes, $oChange);
  746. $this->Set('dest_id', $oDestObj->GetKey());
  747. $this->Set('status_dest_creator', false);
  748. $this->Set('dest_class', get_class($oDestObj));
  749. break;
  750. default:
  751. $aConditions = array();
  752. foreach($aFilterValues as $sCode => $sValue)
  753. {
  754. $aConditions[] = $sCode.'='.$sValue;
  755. }
  756. $sCondition = implode(' AND ', $aConditions);
  757. //echo "<p>Found N for: ".self::$aSearches[$oDataSource->GetKey()]->ToOQL(true, $aFilterValues)."</p>";
  758. $this->SetLastError($iCount.' destination objects match the reconciliation criterias: '.$sCondition);
  759. }
  760. break;
  761. case 'modified':
  762. $oDestObj = MetaModel::GetObject($oDataSource->GetTargetClass(), $this->Get('dest_id'));
  763. if ($oDestObj == null)
  764. {
  765. $this->Set('status', 'orphan'); // The destination object has been deleted !
  766. $this->SetLastError('Destination object deleted unexpectedly');
  767. }
  768. else
  769. {
  770. $this->UpdateObjectFromReplica($oDestObj, $aAttributes, $oChange);
  771. }
  772. break;
  773. default: // Do nothing in all other cases
  774. }
  775. $this->DBUpdateTracked($oChange);
  776. }
  777. /**
  778. * Updates the destination object with the Extended data found in the synchro_data_XXXX table
  779. */
  780. protected function UpdateObjectFromReplica($oDestObj, $aAttributes, $oChange)
  781. {
  782. // TO DO: remove trace
  783. echo "<p>Update object ".$oDestObj->GetHyperLink()."</p>";
  784. foreach($aAttributes as $sAttCode)
  785. {
  786. $value = $this->GetValueFromExtData($sAttCode);
  787. $oDestObj->Set($sAttCode, $value);
  788. // TO DO: remove trace
  789. echo "<p>&nbsp;&nbsp;&nbsp;Setting $sAttCode to $value</p>";
  790. }
  791. try
  792. {
  793. $oDestObj->DBUpdateTracked($oChange);
  794. $this->Set('status_last_error', '');
  795. $this->Set('status', 'synchronized');
  796. }
  797. catch(Exception $e)
  798. {
  799. $this->SetLastError('Unable to update destination object: ', $e);
  800. }
  801. }
  802. /**
  803. * Creates the destination object populating it with the Extended data found in the synchro_data_XXXX table
  804. */
  805. protected function CreateObjectFromReplica($sClass, $aAttributes, $oChange)
  806. {
  807. // TO DO: remove trace
  808. echo "<p>Creating new $sClass</p>";
  809. $oDestObj = MetaModel::NewObject($sClass);
  810. foreach($aAttributes as $sAttCode)
  811. {
  812. $value = $this->GetValueFromExtData($sAttCode);
  813. $oDestObj->Set($sAttCode, $value);
  814. // TO DO: remove trace
  815. echo "<p>&nbsp;&nbsp;&nbsp;Setting $sAttCode to $value</p>";
  816. }
  817. try
  818. {
  819. $iNew = $oDestObj->DBInsertTracked($oChange);
  820. // TO DO: remove trace
  821. echo "<p>Created: $iNew</p>";
  822. $this->Set('dest_id', $oDestObj->GetKey());
  823. $this->Set('dest_class', get_class($oDestObj));
  824. $this->Set('status_dest_creator', true);
  825. $this->Set('status_last_error', '');
  826. $this->Set('status', 'synchronized');
  827. }
  828. catch(Exception $e)
  829. {
  830. $this->SetLastError('Unable to create destination object: ', $e);
  831. }
  832. }
  833. /**
  834. * Update the destination object with given values
  835. */
  836. public function UpdateDestObject($aValues, $oChange)
  837. {
  838. try
  839. {
  840. $oDestObj = MetaModel::GetObject($this->Get('dest_class'), $this->Get('dest_id'));
  841. foreach($aValues as $sAttCode => $value)
  842. {
  843. $oDestObj->Set($sAttCode, $value);
  844. }
  845. $oDestObj->DBUpdateTracked($oChange);
  846. }
  847. catch(Exception $e)
  848. {
  849. $this->SetLastError('Unable to update the destination object: ', $e);
  850. }
  851. }
  852. /**
  853. * Delete the destination object
  854. */
  855. public function DeleteDestObject($oChange)
  856. {
  857. if($this->Get('status_dest_creator'))
  858. {
  859. $oDestObj = MetaModel::GetObject($this->Get('dest_class'), $this->Get('dest_id'));
  860. try
  861. {
  862. $oDestObj->DBDeleteTracked($oChange);
  863. }
  864. catch(Exception $e)
  865. {
  866. $this->SetLastError('Unable to delete the destination object: ', $e);
  867. }
  868. }
  869. }
  870. /**
  871. * Get the value from the 'Extended Data' located in the synchro_data_xxx table for this replica
  872. */
  873. protected function GetValueFromExtData($sColumnName)
  874. {
  875. $aData = $this->GetExtendedData();
  876. return $aData[$sColumnName];
  877. }
  878. }
  879. //if (UserRights::IsAdministrator())
  880. {
  881. $oAdminMenu = new MenuGroup('AdminTools', 80 /* fRank */);
  882. new OQLMenuNode('DataSources', 'SELECT SynchroDataSource', $oAdminMenu->GetIndex(), 12 /* fRank */, true, 'SynchroDataSource', UR_ACTION_MODIFY, UR_ALLOWED_YES);
  883. new WebPageMenuNode('Test:RunSynchro', '../synchro/synchro_exec.php', $oAdminMenu->GetIndex(), 13 /* fRank */);
  884. }
  885. ?>