/**
     * Displays the status (SynchroLog) of the datasource in a graphical manner
     * @param $oPage WebPage
     * @return void
     */
    protected function DisplayStatusTab(WebPage $oPage)
    {
        $oPage->SetCurrentTab(Dict::S('Core:SynchroStatus'));
        $sSelectSynchroLog = 'SELECT SynchroLog WHERE sync_source_id = :source_id';
        $oSetSynchroLog = new CMDBObjectSet(DBObjectSearch::FromOQL($sSelectSynchroLog), array('start_date' => false), array('source_id' => $this->GetKey()));
        $oSetSynchroLog->SetLimit(100);
        // Display only the 100 latest runs
        if ($oSetSynchroLog->Count() > 0) {
            $oLastLog = $oSetSynchroLog->Fetch();
            $sStartDate = $oLastLog->Get('start_date');
            $oLastLog->Get('stats_nb_replica_seen');
            $iLastLog = 0;
            $iDSid = $this->GetKey();
            if ($oLastLog->Get('status') == 'running') {
                // Still running !
                $oPage->p('<h2>' . Dict::Format('Core:Synchro:SynchroRunningStartedOn_Date', $sStartDate) . '</h2>');
            } else {
                $sEndDate = $oLastLog->Get('end_date');
                $iLastLog = $oLastLog->GetKey();
                $oPage->p('<h2>' . Dict::Format('Core:Synchro:SynchroEndedOn_Date', $sEndDate) . '</h2>');
                $sOQL = "SELECT SynchroReplica WHERE sync_source_id={$iDSid}";
                $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL));
                $iCountAllReplicas = $oSet->Count();
                $sAllReplicas = "<a href=\"../synchro/replica.php?operation=oql&datasource={$iDSid}&oql={$sOQL}\">{$iCountAllReplicas}</a>";
                $sOQL = "SELECT SynchroReplica WHERE sync_source_id={$iDSid} AND status_last_error !=''";
                $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL));
                $iCountAllErrors = $oSet->Count();
                $sAllErrors = "<a href=\"../synchro/replica.php?operation=oql&datasource={$iDSid}&oql={$sOQL}\">{$iCountAllErrors}</a>";
                $sOQL = "SELECT SynchroReplica WHERE sync_source_id={$iDSid} AND status_last_warning !=''";
                $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL));
                $iCountAllWarnings = $oSet->Count();
                $sAllWarnings = "<a href=\"../synchro/replica.php?operation=oql&datasource={$iDSid}&oql={$sOQL}\">{$iCountAllWarnings}</a>";
                $oPage->p('<h2>' . Dict::Format('Core:Synchro:ListReplicas_AllReplicas_Errors_Warnings', $sAllReplicas, $sAllErrors, $sAllWarnings) . '</h2>');
            }
            $oPage->add('<table class="synoptics"><tr><td style="color:#333;vertical-align:top">');
            // List all the log entries for the user to select
            $oPage->add('<h2 style="line-height:55px;">' . Dict::S('Core:Synchro:History') . '</h2>');
            $oSetSynchroLog->Rewind();
            $oPage->add('<select size="25" onChange="UpdateSynoptics(this.value);">');
            $sSelected = ' selected';
            // First log is selected by default
            $sScript = "var aSynchroLog = {\n";
            while ($oLog = $oSetSynchroLog->Fetch()) {
                $sLogTitle = Dict::Format('Core:SynchroLogTitle', $oLog->Get('status'), $oLog->Get('start_date'));
                $oPage->add('<option value="' . $oLog->GetKey() . '"' . $sSelected . '>' . $sLogTitle . '</option>');
                $sSelected = '';
                // only the first log is selected by default
                $aData = $this->ProcessLog($oLog);
                $sScript .= '"' . $oLog->GetKey() . '": ' . json_encode($aData) . ",\n";
            }
            $sScript .= "end: 'Done'";
            $sScript .= "};\n";
            $sScript .= <<<EOF
\t\t\tvar sLastLog = '{$iLastLog}';
\tfunction ToggleSynoptics(sId, bShow)
\t{
\t\tif (bShow)
\t\t{
\t\t\t\$(sId).show();
\t\t}
\t\telse
\t\t{
\t\t\t\$(sId).hide();
\t\t}
\t}
\t
\tfunction UpdateSynoptics(id)
\t{
\t\tvar aValues = aSynchroLog[id];
\t\tif (aValues == undefined) return;
\t\t
\t\tfor (var sKey in aValues)
\t\t{
\t\t\t\$('#c_'+sKey).html(aValues[sKey]);
\t\t\tvar fOpacity = (aValues[sKey] == 0) ? 0.3 : 1;
\t\t\t\$('#'+sKey).fadeTo("slow", fOpacity);
\t\t}
\t\t//alert('id = '+id+', lastLog='+sLastLog+', id==sLastLog: '+(id==sLastLog)+' obj_updated_errors:  '+aValues['obj_updated_errors']);
\t\tif ( (id == sLastLog) && (aValues['obj_new_errors'] > 0) )
\t\t{
\t\t\t\$('#new_errors_link').show();
\t\t}
\t\telse
\t\t{
\t\t\t\$('#new_errors_link').hide();
\t\t}
\t\t
\t\tif ( (id == sLastLog) && (aValues['obj_updated_errors'] > 0) )
\t\t{
\t\t\t\$('#updated_errors_link').show();
\t\t}
\t\telse
\t\t{
\t\t\t\$('#updated_errors_link').hide();
\t\t}
\t\t
\t\tif ( (id == sLastLog) && (aValues['obj_disappeared_errors'] > 0) )
\t\t{
\t\t\t\$('#disappeared_errors_link').show();
\t\t}
\t\telse
\t\t{
\t\t\t\$('#disappeared_errors_link').hide();
\t\t}
\t\t
\t\tToggleSynoptics('#cw_obj_created_warnings', aValues['obj_created_warnings'] > 0);
\t\tToggleSynoptics('#cw_obj_new_updated_warnings', aValues['obj_new_updated_warnings'] > 0);
\t\tToggleSynoptics('#cw_obj_new_unchanged_warnings', aValues['obj_new_unchanged_warnings'] > 0);
\t\tToggleSynoptics('#cw_obj_updated_warnings', aValues['obj_updated_warnings'] > 0);
\t\tToggleSynoptics('#cw_obj_unchanged_warnings', aValues['obj_unchanged_warnings'] > 0);
\t}
EOF;
            $oPage->add_script($sScript);
            $oPage->add('</select>');
            $oPage->add('</td><td style="vertical-align:top;">');
            // Now build the big "synoptics" view
            $aData = $this->ProcessLog($oLastLog);
            $sNbReplica = $this->GetIcon() . "&nbsp;" . Dict::Format('Core:Synchro:Nb_Replica', "<span id=\"c_nb_replica_total\">{$aData['nb_replica_total']}</span>");
            $sNbObjects = MetaModel::GetClassIcon($this->GetTargetClass()) . "&nbsp;" . Dict::Format('Core:Synchro:Nb_Class:Objects', $this->GetTargetClass(), "<span id=\"c_nb_obj_total\">{$aData['nb_obj_total']}</span>");
            $oPage->add(<<<EOF
\t<table class="synoptics">
\t<tr class="synoptics_header">
\t<td>{$sNbReplica}</td><td>&nbsp;</td><td>{$sNbObjects}</td>
\t</tr>
\t<tr>
EOF
);
            $sBaseOQL = "SELECT SynchroReplica WHERE sync_source_id=" . $this->GetKey() . " AND status_last_error!=''";
            $oPage->add($this->HtmlBox('repl_ignored', $aData, '#999') . '<td colspan="2">&nbsp;</td>');
            $oPage->add("</tr>\n<tr>");
            $oPage->add($this->HtmlBox('repl_disappeared', $aData, '#630', 'rowspan="4"') . '<td rowspan="4" class="arrow">=&gt;</td>' . $this->HtmlBox('obj_disappeared_no_action', $aData, '#333'));
            $oPage->add("</tr>\n<tr>");
            $oPage->add($this->HtmlBox('obj_deleted', $aData, '#000'));
            $oPage->add("</tr>\n<tr>");
            $oPage->add($this->HtmlBox('obj_obsoleted', $aData, '#630'));
            $oPage->add("</tr>\n<tr>");
            $sOQL = urlencode($sBaseOQL . " AND status='obsolete'");
            $oPage->add($this->HtmlBox('obj_disappeared_errors', $aData, '#C00', '', " <a style=\"color:#fff\" href=\"../synchro/replica.php?operation=oql&datasource={$iDSid}&oql={$sOQL}\" id=\"disappeared_errors_link\">Show</a>"));
            $oPage->add("</tr>\n<tr>");
            $oPage->add($this->HtmlBox('repl_existing', $aData, '#093', 'rowspan="3"') . '<td rowspan="3" class="arrow">=&gt;</td>' . $this->HtmlBox('obj_unchanged', $aData, '#393'));
            $oPage->add("</tr>\n<tr>");
            $oPage->add($this->HtmlBox('obj_updated', $aData, '#3C3'));
            $oPage->add("</tr>\n<tr>");
            $sOQL = urlencode($sBaseOQL . " AND status='modified'");
            $oPage->add($this->HtmlBox('obj_updated_errors', $aData, '#C00', '', " <a style=\"color:#fff\" href=\"../synchro/replica.php?operation=oql&datasource={$iDSid}&oql={$sOQL}\" id=\"updated_errors_link\">Show</a>"));
            $oPage->add("</tr>\n<tr>");
            $oPage->add($this->HtmlBox('repl_new', $aData, '#339', 'rowspan="4"') . '<td rowspan="4" class="arrow">=&gt;</td>' . $this->HtmlBox('obj_new_unchanged', $aData, '#393'));
            $oPage->add("</tr>\n<tr>");
            $oPage->add($this->HtmlBox('obj_new_updated', $aData, '#3C3'));
            $oPage->add("</tr>\n<tr>");
            $oPage->add($this->HtmlBox('obj_created', $aData, '#339'));
            $oPage->add("</tr>\n<tr>");
            $sOQL = urlencode($sBaseOQL . " AND status='new'");
            $oPage->add($this->HtmlBox('obj_new_errors', $aData, '#C00', '', " <a style=\"color:#fff\" href=\"../synchro/replica.php?operation=oql&datasource={$iDSid}&oql={$sOQL}\" id=\"new_errors_link\">Show</a>"));
            $oPage->add("</tr>\n</table>\n");
            $oPage->add('</td></tr></table>');
            $oPage->add_ready_script("UpdateSynoptics('{$iLastLog}')");
        } else {
            $oPage->p('<h2>' . Dict::S('Core:Synchro:NeverRun') . '</h2>');
        }
    }
 protected static function BulkUpdateTracked_Internal(DBSearch $oFilter, array $aValues)
 {
     // $aValues is an array of $sAttCode => $value
     // Get the list of objects to update (and load it before doing the change)
     $oObjSet = new CMDBObjectSet($oFilter);
     $oObjSet->Load();
     // Keep track of the previous values (will be overwritten when the objects are synchronized with the DB)
     $aOriginalValues = array();
     $oObjSet->Rewind();
     while ($oItem = $oObjSet->Fetch()) {
         $aOriginalValues[$oItem->GetKey()] = $oItem->m_aOrigValues;
     }
     // Update in one single efficient query
     $ret = parent::BulkUpdate($oFilter, $aValues);
     // Record... in many queries !!!
     $oObjSet->Rewind();
     while ($oItem = $oObjSet->Fetch()) {
         $aChangedValues = $oItem->ListChangedValues($aValues);
         $oItem->RecordAttChanges($aChangedValues, $aOriginalValues[$oItem->GetKey()]);
     }
     return $ret;
 }