Skip to content

Commit aad8c71

Browse files
committed
Add operation on replica
1 parent 3818bc3 commit aad8c71

File tree

4 files changed

+266
-37
lines changed

4 files changed

+266
-37
lines changed

application/cmdbabstract.class.inc.php

+5
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,11 @@ public function DisplayBareHeader(WebPage $oPage, $bEditMode = false)
527527
$sLabel = Dict::S('Tag:Synchronized');
528528
$sSynchroTagId = 'synchro_icon-'.$this->GetKey();
529529
$aTags[$sSynchroTagId] = ['title' => $sTip, 'css_classes' => 'ibo-object-details--tag--synchronized', 'decoration_classes' => 'fas fa-lock', 'label' => $sLabel];
530+
if (UserRights::IsActionAllowed('SynchroReplica', UR_ACTION_READ)) {
531+
$sFilter = 'SELECT SynchroReplica WHERE dest_class=\''.get_class($this).'\' AND dest_id='.$this->GetKey();
532+
$sUrlSearchReplica = 'UI.php?operation=search&filter='.urlencode(json_encode([$sFilter, [], []]));
533+
$oPage->add_ready_script("$('#$sSynchroTagId').on('click',function() {window.location = '$sUrlSearchReplica' });");
534+
}
530535
}
531536
}
532537

core/attributedef.class.inc.php

+6-2
Original file line numberDiff line numberDiff line change
@@ -3047,14 +3047,18 @@ public function MakeRealValue($proposedValue, $oHostObj)
30473047
{
30483048
return 0;
30493049
}
3050-
if (MetaModel::IsValidObject($proposedValue))
3051-
{
3050+
if (MetaModel::IsValidObject($proposedValue)) {
30523051
/** @var \DBObject $proposedValue */
30533052
return $proposedValue->GetKey();
30543053
}
30553054

30563055
return (int)$proposedValue;
30573056
}
3057+
3058+
public function GetTargetClass($iType = EXTKEY_RELATIVE)
3059+
{
3060+
return '';
3061+
}
30583062
}
30593063

30603064
/**

synchro/replica.php

+153-34
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
* You should have received a copy of the GNU Affero General Public License
1818
*/
1919

20+
use Combodo\iTop\Core\CMDBChange\CMDBChangeOrigin;
21+
2022
require_once('../approot.inc.php');
2123
require_once(APPROOT.'/application/application.inc.php');
2224

2325
require_once(APPROOT.'/application/startup.inc.php');
2426
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
25-
LoginWebPage::DoLogin();
27+
LoginWebPage::DoLogin();
2628

2729
$sOperation = utils::ReadParam('operation', 'menu');
2830
$oAppContext = new ApplicationContext();
@@ -31,46 +33,163 @@
3133

3234
// Main program
3335
$sOperation = utils::ReadParam('operation', 'details');
34-
try
36+
37+
/**
38+
* @param \DBObject|null $oReplica
39+
* @param $this
40+
*
41+
* @return \SynchroLog
42+
* @throws \ArchivedObjectException
43+
* @throws \CoreCannotSaveObjectException
44+
* @throws \CoreException
45+
* @throws \CoreUnexpectedValue
46+
* @throws \CoreWarning
47+
* @throws \MySQLException
48+
* @throws \OQLException
49+
* @throws \SynchroExceptionNotStarted
50+
*/
51+
function Synchro($oReplica): SynchroLog
3552
{
36-
switch($sOperation)
37-
{
38-
case 'details':
39-
$iId = utils::ReadParam('id', null);
40-
if ($iId == null)
41-
{
42-
throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'id'));
53+
$oDataSource = MetaModel::GetObject('SynchroDataSource', $oReplica->Get('sync_source_id'));
54+
55+
$oStatLog = new SynchroLog();
56+
$oStatLog->Set('sync_source_id', $oDataSource->GetKey());
57+
$oStatLog->Set('start_date', time());
58+
$oStatLog->Set('status', 'running');
59+
$oStatLog->AddTrace('Manual synchro');
60+
61+
// Get the list of SQL columns
62+
$aAttCodesExpected = array();
63+
$aAttCodesToReconcile = array();
64+
$aAttCodesToUpdate = array();
65+
$sSelectAtt = 'SELECT SynchroAttribute WHERE sync_source_id = :source_id AND (update = 1 OR reconcile = 1)';
66+
$oSetAtt = new DBObjectSet(DBObjectSearch::FromOQL($sSelectAtt), array() /* order by*/, array('source_id' => $oDataSource->GetKey()) /* aArgs */);
67+
while ($oSyncAtt = $oSetAtt->Fetch()) {
68+
if ($oSyncAtt->Get('update')) {
69+
$aAttCodesToUpdate[$oSyncAtt->Get('attcode')] = $oSyncAtt;
4370
}
44-
$oReplica = MetaModel::GetObject('SynchroReplica', $iId);
45-
$oReplica->DisplayDetails($oP);
46-
break;
47-
48-
case 'oql':
49-
$sOQL = utils::ReadParam('oql', null, false, 'raw_data');
50-
if ($sOQL == null)
51-
{
52-
throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'oql'));
71+
if ($oSyncAtt->Get('reconcile')) {
72+
$aAttCodesToReconcile[$oSyncAtt->Get('attcode')] = $oSyncAtt;
5373
}
54-
$oFilter = DBObjectSearch::FromOQL($sOQL);
55-
$oBlock1 = new DisplayBlock($oFilter, 'search', false, array('menu' => false, 'table_id' => '1'));
56-
$oBlock1->Display($oP, 0);
57-
$oP->add('<p class="page-header">'.MetaModel::GetClassIcon('SynchroReplica').Dict::S('Core:SynchroReplica:ListOfReplicas').'</p>');
58-
$iSourceId = utils::ReadParam('datasource', null);
59-
if ($iSourceId != null)
60-
{
61-
$oSource = MetaModel::GetObject('SynchroDataSource', $iSourceId);
62-
$oP->p(Dict::Format('Core:SynchroReplica:BackToDataSource', $oSource->GetHyperlink()).'</a>');
74+
$aAttCodesExpected[$oSyncAtt->Get('attcode')] = $oSyncAtt;
75+
}
76+
77+
// Get the list of attributes, determine reconciliation keys and update targets
78+
//
79+
if ($oDataSource->Get('reconciliation_policy') == 'use_attributes') {
80+
$aReconciliationKeys = $aAttCodesToReconcile;
81+
} elseif ($oDataSource->Get('reconciliation_policy') == 'use_primary_key') {
82+
// Override the settings made at the attribute level !
83+
$aReconciliationKeys = array('primary_key' => null);
84+
}
85+
86+
if (count($aAttCodesToUpdate) == 0) {
87+
$oStatLog->AddTrace('No attribute to update');
88+
throw new SynchroExceptionNotStarted('There is no attribute to update');
89+
}
90+
if (count($aReconciliationKeys) == 0) {
91+
$oStatLog->AddTrace('No attribute for reconciliation');
92+
throw new SynchroExceptionNotStarted('No attribute for reconciliation');
93+
}
94+
95+
96+
$aAttributesToUpdate = array();
97+
foreach ($aAttCodesToUpdate as $sAttCode => $oSyncAtt) {
98+
$oAttDef = MetaModel::GetAttributeDef($oDataSource->GetTargetClass(), $sAttCode);
99+
if ($oAttDef->IsWritable()) {
100+
$aAttributesToUpdate[$sAttCode] = $oSyncAtt;
63101
}
64-
$oBlock = new DisplayBlock($oFilter, 'list', false, array('menu'=>false));
65-
$oBlock->Display($oP, 1);
66-
break;
102+
}
103+
// Create a change used for logging all the modifications/creations happening during the synchro
104+
$oChange = MetaModel::NewObject('CMDBChange');
105+
$oChange->Set('date', time());
106+
$sUserString = CMDBChange::GetCurrentUserName();
107+
$oChange->Set('userinfo', $sUserString.' '.Dict::S('Core:SyncDataExchangeComment'));
108+
$oChange->Set('origin', CMDBChangeOrigin::SYNCHRO_DATA_SOURCE);
109+
$oChange->DBInsert();
110+
CMDBObject::SetCurrentChange($oChange);
111+
112+
$oReplica->InitExtendedData($oDataSource);
113+
114+
$oReplica->Synchro($oDataSource, $aReconciliationKeys, $aAttributesToUpdate, $oChange, $oStatLog);
115+
$oReplica->DBUpdate();
116+
117+
return $oStatLog;
118+
}
119+
120+
try {
121+
switch ($sOperation) {
122+
case 'details':
123+
$iId = utils::ReadParam('id', null);
124+
if ($iId == null) {
125+
throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'id'));
126+
}
127+
$oReplica = MetaModel::GetObject('SynchroReplica', $iId);
128+
$oReplica->DisplayDetails($oP);
129+
break;
130+
131+
case 'oql':
132+
$sOQL = utils::ReadParam('oql', null, false, 'raw_data');
133+
if ($sOQL == null) {
134+
throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'oql'));
135+
}
136+
$oFilter = DBObjectSearch::FromOQL($sOQL);
137+
$oBlock1 = new DisplayBlock($oFilter, 'search', false, array('menu' => false, 'table_id' => '1'));
138+
$oBlock1->Display($oP, 0);
139+
$oP->add('<p class="page-header">'.MetaModel::GetClassIcon('SynchroReplica').Dict::S('Core:SynchroReplica:ListOfReplicas').'</p>');
140+
$iSourceId = utils::ReadParam('datasource', null);
141+
if ($iSourceId != null) {
142+
$oSource = MetaModel::GetObject('SynchroDataSource', $iSourceId);
143+
$oP->p(Dict::Format('Core:SynchroReplica:BackToDataSource', $oSource->GetHyperlink()).'</a>');
144+
}
145+
$oBlock = new DisplayBlock($oFilter, 'list', false, array('menu' => false));
146+
$oBlock->Display($oP, 1);
147+
break;
67148

68149
case 'delete':
69150
case 'select_for_deletion':
70-
// Redirect to the page that implements bulk delete
71-
$sDelete = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?'.$_SERVER['QUERY_STRING'];
72-
header("Location: $sDelete");
73-
break;
151+
// Redirect to the page that implements bulk delete
152+
$sDelete = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?'.$_SERVER['QUERY_STRING'];
153+
header("Location: $sDelete");
154+
break;
155+
156+
case 'unlinksynchro':
157+
$iId = utils::ReadParam('id', null);
158+
if ($iId == null) {
159+
throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'id'));
160+
}
161+
$oReplica = MetaModel::GetObject('SynchroReplica', $iId);
162+
$oReplica->Set('dest_id', '');
163+
$oReplica->Set('status', 'new');
164+
$oReplica->DBWrite();
165+
166+
$oStatLog = Synchro($oReplica);
167+
$oP->add(implode('<br>', $oStatLog->GetTraces()));
168+
169+
$oReplica->DisplayDetails($oP);
170+
break;
171+
172+
case 'unlink':
173+
$iId = utils::ReadParam('id', null);
174+
if ($iId == null) {
175+
throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'id'));
176+
}
177+
$oReplica = MetaModel::GetObject('SynchroReplica', $iId);
178+
$oReplica->Set('dest_id', '');
179+
$oReplica->Set('status', 'new');
180+
$oReplica->DBWrite();
181+
182+
$oReplica->DisplayDetails($oP);
183+
break;
184+
185+
case 'synchro':
186+
$iId = utils::ReadParam('id', null);
187+
if ($iId == null) {
188+
throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'id'));
189+
}
190+
$oReplica = MetaModel::GetObject('SynchroReplica', $iId);
191+
$oStatLog = Synchro($oReplica);
192+
break;
74193
}
75194
}
76195
catch(CoreException $e)

synchro/synchrodatasource.class.inc.php

+102-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
* @license http://opensource.org/licenses/AGPL-3.0
55
*/
66

7+
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
8+
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenu;
9+
use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
10+
use Combodo\iTop\Application\UI\Base\Component\Toolbar\ToolbarUIBlockFactory;
11+
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
12+
713
class SynchroDataSource extends cmdbAbstractObject
814
{
915
public static function Init()
@@ -2106,6 +2112,12 @@ public static function Init()
21062112
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
21072113
}
21082114

2115+
public function InitExtendedData($oSource)
2116+
{
2117+
$sSQLTable = $oSource->GetDataTable();
2118+
$this->m_aExtendedData = $this->LoadExtendedDataFromTable($sSQLTable);
2119+
}
2120+
21092121
public function __construct($aRow = null, $sClassAlias = '', $aAttToLoad = null, $aExtendedDataSpec = null)
21102122
{
21112123
parent::__construct($aRow, $sClassAlias, $aAttToLoad, $aExtendedDataSpec);
@@ -2760,13 +2772,102 @@ public static function GetUIPage()
27602772
function DisplayDetails(WebPage $oPage, $bEditMode = false)
27612773
{
27622774
// Object's details
2763-
//$this->DisplayBareHeader($oPage, $bEditMode);
2775+
$this->DisplayBareHeader($oPage, $bEditMode);
2776+
27642777
$oPage->AddTabContainer(OBJECT_PROPERTIES_TAB);
27652778
$oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB);
27662779
$oPage->SetCurrentTab('UI:PropertiesTab');
27672780
$this->DisplayBareProperties($oPage, $bEditMode);
27682781
}
27692782

2783+
public function DisplayBareHeader(WebPage $oPage, $bEditMode = false)
2784+
{
2785+
$oBlock = UIContentBlockUIBlockFactory::MakeStandard('title-for-replica', ['ibo-page-header--replica-title']);
2786+
$oPage->AddSubBlock($oBlock);
2787+
$oPage->add_style('.ibo-page-header--replica-title{ display: table; width: 100%;}');
2788+
$oPage->add_style('.ibo-page-header--replica-title>.ibo-toolbar--button{ display: table-cell; vertical-align:middle;}');
2789+
2790+
$sId = $this->GetKey();
2791+
$oTitle = TitleUIBlockFactory::MakeNeutral(Dict::S('Class:SynchroReplica'));
2792+
$oBlock->AddSubBlock($oTitle);
2793+
$oActionsToolbar = ToolbarUIBlockFactory::MakeForButton(MenuBlock::ACTIONS_TOOLBAR_ID_PREFIX.$sId);
2794+
$oActionsToolbar->AddCSSClass('ibo-panel--toolbar');
2795+
$oBlock->AddSubBlock($oActionsToolbar);
2796+
2797+
$sClass = get_class($this);
2798+
$sRootUrl = utils::GetAbsoluteUrlAppRoot();
2799+
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($sClass);
2800+
$oAppContext = new ApplicationContext();
2801+
$sContext = $oAppContext->GetForLink();
2802+
if (utils::IsNotNullOrEmptyString($sContext)) {
2803+
$sContext = '&'.$sContext;
2804+
}
2805+
2806+
$aActions = [];
2807+
//Delete
2808+
if (UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE)) {
2809+
$aActions['UI:Menu:Delete'] = array(
2810+
'label' => Dict::S('UI:Menu:Delete'),
2811+
'url' => "{$sRootUrl}pages/$sUIPage?operation=delete&class=$sClass&id=$sId{$sContext}",
2812+
);
2813+
}
2814+
2815+
if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY)) {
2816+
if (count($aActions) > 0) {
2817+
$sSeparator = '<hr class="menu-separator"/>';
2818+
$aActions['sep_0'] = array('label' => $sSeparator, 'url' => '');
2819+
}
2820+
$sUrl = "{$sRootUrl}synchro/replica.php?operation=unlink&class=$sClass&id=$sId{$sContext}";
2821+
$aActions['Class:SynchroReplica/Action:unlink'] = [
2822+
'label' => Dict::S('Class:SynchroReplica/Action:unlink'),
2823+
'url' => $sUrl,
2824+
'tooltip' => Dict::S('Class:SynchroReplica/Action:unlink+'),
2825+
];
2826+
2827+
$sUrl = "{$sRootUrl}synchro/replica.php?operation=unlinksynchro&class=$sClass&id=$sId{$sContext}";
2828+
$aActions['Class:SynchroReplica/Action:unlinksynchro'] = [
2829+
'label' => Dict::S('Class:SynchroReplica/Action:unlinksynchro'),
2830+
'url' => $sUrl,
2831+
'tooltip' => Dict::S('Class:SynchroReplica/Action:unlinksynchro+'),
2832+
];
2833+
2834+
$sUrl = "{$sRootUrl}synchro/replica.php?operation=synchro&class=$sClass&id=$sId{$sContext}";
2835+
$aActions['Class:SynchroReplica/Action:synchro'] = [
2836+
'label' => Dict::S('Class:SynchroReplica/Action:synchro'),
2837+
'url' => $sUrl,
2838+
'tooltip' => Dict::S('Class:SynchroReplica/Action:synchro+'),
2839+
];
2840+
}
2841+
if (count($aActions) > 0) {
2842+
$sRegularActionsMenuTogglerId = "ibo-regular-actions-menu-toggler-{$sId}";
2843+
$sRegularActionsPopoverMenuId = "ibo-regular-actions-popover-{$sId}";
2844+
2845+
$oActionButton = ButtonUIBlockFactory::MakeIconAction('fas fa-ellipsis-v', Dict::S('UI:Menu:Actions'), 'UI:Menu:Actions', '', false, $sRegularActionsMenuTogglerId)
2846+
->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button']);
2847+
2848+
$oRegularActionsMenu = $oPage->GetPopoverMenu($sRegularActionsPopoverMenuId, $aActions)
2849+
->SetTogglerJSSelector("#$sRegularActionsMenuTogglerId")
2850+
->SetContainer(PopoverMenu::ENUM_CONTAINER_BODY);
2851+
2852+
$oActionsToolbar->AddSubBlock($oActionButton)
2853+
->AddSubBlock($oRegularActionsMenu);
2854+
2855+
$oActionButton = ButtonUIBlockFactory::MakeIconLink('fas fa-search', Dict::Format('UI:SearchFor_Class', MetaModel::GetName($sClass)), "{$sRootUrl}pages/UI.php?operation=search_form&do_search=0&class=$sClass{$sContext}", '', 'UI:SearchFor_Class');
2856+
$oActionButton->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button']);
2857+
$oActionsToolbar->AddSubBlock($oActionButton);
2858+
}
2859+
2860+
$sUrl = "{$sRootUrl}pages/$sUIPage?operation=display&class=$sClass&id=$sId{$sContext}";
2861+
$oActionButton = ButtonUIBlockFactory::MakeAlternativeNeutral('', 'UI:Button:Refresh');
2862+
$oActionButton->SetIconClass('fas fa-sync-alt')
2863+
->SetOnClickJsCode('window.location.href=\''.$sUrl.'\'')
2864+
->SetTooltip(Dict::S('UI:Button:Refresh'))
2865+
->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button']);
2866+
$oActionsToolbar->AddSubBlock($oActionButton);
2867+
2868+
return $oBlock;
2869+
}
2870+
27702871
function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array())
27712872
{
27722873
if ($bEditMode) {

0 commit comments

Comments
 (0)