public function DisplayAttachments($oObject, WebPage $oPage, $bEditMode = false)
    {
        // Exit here if the class is not allowed
        if (!$this->IsTargetObject($oObject)) {
            return;
        }
        $oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE item_class = :class AND item_id = :item_id");
        $oSet = new DBObjectSet($oSearch, array(), array('class' => get_class($oObject), 'item_id' => $oObject->GetKey()));
        if ($this->GetAttachmentsPosition() == 'relations') {
            $sTitle = $oSet->Count() > 0 ? Dict::Format('Attachments:TabTitle_Count', $oSet->Count()) : Dict::S('Attachments:EmptyTabTitle');
            $oPage->SetCurrentTab($sTitle);
        }
        $oPage->add_style(<<<EOF
.attachment {
\tdisplay: inline-block;
\ttext-align:center;
\tfloat:left;
\tpadding:5px;\t
}
.attachment:hover {
\tbackground-color: #e0e0e0;
}
.attachment img {
\tborder: 0;
}
.attachment a {
\ttext-decoration: none;
\tcolor: #1C94C4;
}
.btn_hidden {
\tdisplay: none;
}
EOF
);
        $oPage->add('<fieldset>');
        $oPage->add('<legend>' . Dict::S('Attachments:FieldsetTitle') . '</legend>');
        if ($bEditMode) {
            $sIsDeleteEnabled = $this->m_bDeleteEnabled ? 'true' : 'false';
            $iTransactionId = $oPage->GetTransactionId();
            $sClass = get_class($oObject);
            $sTempId = session_id() . '_' . $iTransactionId;
            $sDeleteBtn = Dict::S('Attachments:DeleteBtn');
            $oPage->add_script(<<<EOF
\tfunction RemoveNewAttachment(att_id)
\t{
\t\t\$('#attachment_'+att_id).attr('name', 'removed_attachments[]');
\t\t\$('#display_attachment_'+att_id).hide();
\t\t\$('#attachment_plugin').trigger('remove_attachment', [att_id]);
\t\treturn false; // Do not submit the form !
\t}
\tfunction ajaxFileUpload()
\t{
\t\t//starting setting some animation when the ajax starts and completes
\t\t\$("#attachment_loading").ajaxStart(function(){
\t\t\t\$(this).show();
\t\t}).ajaxComplete(function(){
\t\t\t\$(this).hide();
\t\t});
\t\t
\t\t/*
\t\t\tprepareing ajax file upload
\t\t\turl: the url of script file handling the uploaded files
                        fileElementId: the file type of input element id and it will be the index of  \$_FILES Array()
\t\t\tdataType: it support json, xml
\t\t\tsecureuri:use secure protocol
\t\t\tsuccess: call back function when the ajax complete
\t\t\terror: callback function when the ajax failed
\t\t\t
                */
\t\t\$.ajaxFileUpload
\t\t(
\t\t\t{
\t\t\t\turl: GetAbsoluteUrlModulesRoot()+'itop-attachments/ajax.attachment.php?obj_class={$sClass}&temp_id={$sTempId}&operation=add', 
\t\t\t\tsecureuri:false,
\t\t\t\tfileElementId:'file',
\t\t\t\tdataType: 'json',
\t\t\t\tsuccess: function (data, status)
\t\t\t\t{
\t\t\t\t\tif(typeof(data.error) != 'undefined')
\t\t\t\t\t{
\t\t\t\t\t\tif(data.error != '')
\t\t\t\t\t\t{
\t\t\t\t\t\t\talert(data.error);
\t\t\t\t\t\t}
\t\t\t\t\t\telse
\t\t\t\t\t\t{
\t\t\t\t\t\t\tvar sDownloadLink = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?operation=download_document&class=Attachment&id='+data.att_id+'&field=contents';
\t\t\t\t\t\t\t\$('#attachments').append('<div class="attachment" id="display_attachment_'+data.att_id+'"><a href="'+sDownloadLink+'"><img src="'+data.icon+'"><br/>'+data.msg+'<input id="attachment_'+data.att_id+'" type="hidden" name="attachments[]" value="'+data.att_id+'"/></a><br/><input type="button" class="btn_hidden" value="{$sDeleteBtn}" onClick="RemoveNewAttachment('+data.att_id+');"/></div>');
\t\t\t\t\t\t\tif({$sIsDeleteEnabled})
\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\t\$('#display_attachment_'+data.att_id).hover( function() { \$(this).children(':button').toggleClass('btn_hidden'); } );
\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t\$('#attachment_plugin').trigger('add_attachment', [data.att_id, data.msg]);
\t\t\t\t\t\t\t
\t\t\t\t\t\t\t//alert(data.msg);
\t\t\t\t\t\t}
\t\t\t\t\t}
\t\t\t\t},
\t\t\t\terror: function (data, status, e)
\t\t\t\t{
\t\t\t\t\talert(e);
\t\t\t\t}
\t\t\t}
\t\t)
\t\t
\t\treturn false;

\t}
EOF
);
            $oPage->add('<span id="attachments">');
            while ($oAttachment = $oSet->Fetch()) {
                $iAttId = $oAttachment->GetKey();
                $oDoc = $oAttachment->Get('contents');
                $sFileName = $oDoc->GetFileName();
                $sIcon = utils::GetAbsoluteUrlAppRoot() . AttachmentPlugIn::GetFileIcon($sFileName);
                $sDownloadLink = utils::GetAbsoluteUrlAppRoot() . 'pages/ajax.render.php?operation=download_document&class=Attachment&id=' . $iAttId . '&field=contents';
                $oPage->add('<div class="attachment" id="attachment_' . $iAttId . '"><a href="' . $sDownloadLink . '"><img src="' . $sIcon . '"><br/>' . $sFileName . '<input type="hidden" name="attachments[]" value="' . $iAttId . '"/></a><br/>&nbsp;<input id="btn_remove_' . $iAttId . '" type="button" class="btn_hidden" value="Delete" onClick="$(\'#attachment_' . $iAttId . '\').remove();"/>&nbsp;</div>');
            }
            // Suggested attachments are listed here but treated as temporary
            $aDefault = utils::ReadParam('default', array(), false, 'raw_data');
            if (array_key_exists('suggested_attachments', $aDefault)) {
                $sSuggestedAttachements = $aDefault['suggested_attachments'];
                if (is_array($sSuggestedAttachements)) {
                    $sSuggestedAttachements = implode(',', $sSuggestedAttachements);
                }
                $oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE id IN({$sSuggestedAttachements})");
                $oSet = new DBObjectSet($oSearch, array());
                if ($oSet->Count() > 0) {
                    while ($oAttachment = $oSet->Fetch()) {
                        // Mark the attachments as temporary attachments for the current object/form
                        $oAttachment->Set('temp_id', $sTempId);
                        $oAttachment->DBUpdate();
                        // Display them
                        $iAttId = $oAttachment->GetKey();
                        $oDoc = $oAttachment->Get('contents');
                        $sFileName = $oDoc->GetFileName();
                        $sIcon = utils::GetAbsoluteUrlAppRoot() . AttachmentPlugIn::GetFileIcon($sFileName);
                        $sDownloadLink = utils::GetAbsoluteUrlAppRoot() . 'pages/ajax.render.php?operation=download_document&class=Attachment&id=' . $iAttId . '&field=contents';
                        $oPage->add('<div class="attachment" id="display_attachment_' . $iAttId . '"><a href="' . $sDownloadLink . '"><img src="' . $sIcon . '"><br/>' . $sFileName . '<input type="hidden" name="attachments[]" value="' . $iAttId . '"/></a><br/>&nbsp;<input id="btn_remove_' . $iAttId . '" type="button" class="btn_hidden" value="Delete" onClick="RemoveNewAttachment(' . $iAttId . ');"/>&nbsp;</div>');
                        $oPage->add_ready_script("\$('#attachment_plugin').trigger('add_attachment', [{$iAttId}, '" . addslashes($sFileName) . "']);");
                    }
                }
            }
            $oPage->add('</span>');
            $oPage->add('<div style="clear:both"></div>');
            $sMaxUpload = $this->GetMaxUpload();
            $oPage->p(Dict::S('Attachments:AddAttachment') . '<input type="file" name="file" id="file" onChange="ajaxFileUpload();"><span style="display:none;" id="attachment_loading">&nbsp;<img src="../images/indicator.gif"></span> ' . $sMaxUpload);
            $oPage->p('<span style="display:none;" id="attachment_loading">Loading, please wait...</span>');
            $oPage->p('<input type="hidden" id="attachment_plugin" name="attachment_plugin"/>');
            $oPage->add('</fieldset>');
            if ($this->m_bDeleteEnabled) {
                $oPage->add_ready_script('$(".attachment").hover( function() {$(this).children(":button").toggleClass("btn_hidden"); } );');
            }
        } else {
            $oPage->add('<span id="attachments">');
            if ($oSet->Count() == 0) {
                $oPage->add(Dict::S('Attachments:NoAttachment'));
            } else {
                while ($oAttachment = $oSet->Fetch()) {
                    $iAttId = $oAttachment->GetKey();
                    $oDoc = $oAttachment->Get('contents');
                    $sFileName = $oDoc->GetFileName();
                    $sIcon = utils::GetAbsoluteUrlAppRoot() . AttachmentPlugIn::GetFileIcon($sFileName);
                    $sDownloadLink = utils::GetAbsoluteUrlAppRoot() . 'pages/ajax.render.php?operation=download_document&class=Attachment&id=' . $iAttId . '&field=contents';
                    $oPage->add('<div class="attachment" id="attachment_' . $iAttId . '"><a href="' . $sDownloadLink . '"><img src="' . $sIcon . '"><br/>' . $sFileName . '</a><input type="hidden" name="attachments[]" value="' . $iAttId . '"/><br/>&nbsp;&nbsp;</div>');
                }
            }
        }
    }
    public function DisplayAttachments($oObject, WebPage $oPage, $bEditMode = false)
    {
        // Exit here if the class is not allowed
        if (!$this->IsTargetObject($oObject)) {
            return;
        }
        $oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE item_class = :class AND item_id = :item_id");
        $oSet = new DBObjectSet($oSearch, array(), array('class' => get_class($oObject), 'item_id' => $oObject->GetKey()));
        if ($this->GetAttachmentsPosition() == 'relations') {
            $sTitle = $oSet->Count() > 0 ? Dict::Format('Attachments:TabTitle_Count', $oSet->Count()) : Dict::S('Attachments:EmptyTabTitle');
            $oPage->SetCurrentTab($sTitle);
        }
        $oPage->add_style(<<<EOF
.attachment {
\tdisplay: inline-block;
\ttext-align:center;
\tfloat:left;
\tpadding:5px;\t
}
.attachment:hover {
\tbackground-color: #e0e0e0;
}
.attachment img {
\tborder: 0;
}
.attachment a {
\ttext-decoration: none;
\tcolor: #1C94C4;
}
.btn_hidden {
\tdisplay: none;
}
.drag_in {
\t-webkit-box-shadow:inset 0 0 10px 2px #1C94C4;
\tbox-shadow:inset 0 0 10px 2px #1C94C4;
}
#history .attachment-history-added {
\tpadding: 0;
\tfloat: none;
}
EOF
);
        $oPage->add('<fieldset>');
        $oPage->add('<legend>' . Dict::S('Attachments:FieldsetTitle') . '</legend>');
        if ($bEditMode) {
            $sIsDeleteEnabled = $this->m_bDeleteEnabled ? 'true' : 'false';
            $iTransactionId = $oPage->GetTransactionId();
            $sClass = get_class($oObject);
            $sTempId = session_id() . '_' . $iTransactionId;
            $sDeleteBtn = Dict::S('Attachments:DeleteBtn');
            $oPage->add_script(<<<EOF
\tfunction RemoveAttachment(att_id)
\t{
\t\t\$('#attachment_'+att_id).attr('name', 'removed_attachments[]');
\t\t\$('#display_attachment_'+att_id).hide();
\t\t\$('#attachment_plugin').trigger('remove_attachment', [att_id]);
\t\treturn false; // Do not submit the form !
\t}
EOF
);
            $oPage->add('<span id="attachments">');
            while ($oAttachment = $oSet->Fetch()) {
                $iAttId = $oAttachment->GetKey();
                $oDoc = $oAttachment->Get('contents');
                $sFileName = $oDoc->GetFileName();
                $sIcon = utils::GetAbsoluteUrlAppRoot() . AttachmentPlugIn::GetFileIcon($sFileName);
                $sPreview = $oDoc->IsPreviewAvailable() ? 'true' : 'false';
                $sDownloadLink = utils::GetAbsoluteUrlAppRoot() . 'pages/ajax.render.php?operation=download_document&class=Attachment&id=' . $iAttId . '&field=contents';
                $oPage->add('<div class="attachment" id="display_attachment_' . $iAttId . '"><a data-preview="' . $sPreview . '" href="' . $sDownloadLink . '"><img src="' . $sIcon . '"><br/>' . $sFileName . '<input id="attachment_' . $iAttId . '" type="hidden" name="attachments[]" value="' . $iAttId . '"/></a><br/>&nbsp;<input id="btn_remove_' . $iAttId . '" type="button" class="btn_hidden" value="Delete" onClick="RemoveAttachment(' . $iAttId . ');"/>&nbsp;</div>');
            }
            // Suggested attachments are listed here but treated as temporary
            $aDefault = utils::ReadParam('default', array(), false, 'raw_data');
            if (array_key_exists('suggested_attachments', $aDefault)) {
                $sSuggestedAttachements = $aDefault['suggested_attachments'];
                if (is_array($sSuggestedAttachements)) {
                    $sSuggestedAttachements = implode(',', $sSuggestedAttachements);
                }
                $oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE id IN({$sSuggestedAttachements})");
                $oSet = new DBObjectSet($oSearch, array());
                if ($oSet->Count() > 0) {
                    while ($oAttachment = $oSet->Fetch()) {
                        // Mark the attachments as temporary attachments for the current object/form
                        $oAttachment->Set('temp_id', $sTempId);
                        $oAttachment->DBUpdate();
                        // Display them
                        $iAttId = $oAttachment->GetKey();
                        $oDoc = $oAttachment->Get('contents');
                        $sFileName = $oDoc->GetFileName();
                        $sIcon = utils::GetAbsoluteUrlAppRoot() . AttachmentPlugIn::GetFileIcon($sFileName);
                        $sDownloadLink = utils::GetAbsoluteUrlAppRoot() . 'pages/ajax.render.php?operation=download_document&class=Attachment&id=' . $iAttId . '&field=contents';
                        $sPreview = $oDoc->IsPreviewAvailable() ? 'true' : 'false';
                        $oPage->add('<div class="attachment" id="display_attachment_' . $iAttId . '"><a data-preview="' . $sPreview . '" href="' . $sDownloadLink . '"><img src="' . $sIcon . '"><br/>' . $sFileName . '<input id="attachment_' . $iAttId . '" type="hidden" name="attachments[]" value="' . $iAttId . '"/></a><br/>&nbsp;<input id="btn_remove_' . $iAttId . '" type="button" class="btn_hidden" value="Delete" onClick="RemoveAttachment(' . $iAttId . ');"/>&nbsp;</div>');
                        $oPage->add_ready_script("\$('#attachment_plugin').trigger('add_attachment', [{$iAttId}, '" . addslashes($sFileName) . "']);");
                    }
                }
            }
            $oPage->add('</span>');
            $oPage->add('<div style="clear:both"></div>');
            $sMaxUpload = $this->GetMaxUpload();
            $oPage->p(Dict::S('Attachments:AddAttachment') . '<input type="file" name="file" id="file"><span style="display:none;" id="attachment_loading">&nbsp;<img src="../images/indicator.gif"></span> ' . $sMaxUpload);
            $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot() . 'js/jquery.iframe-transport.js');
            $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot() . 'js/jquery.fileupload.js');
            $oPage->add_ready_script(<<<EOF
    \$('#file').fileupload({
\t\turl: GetAbsoluteUrlModulesRoot()+'itop-attachments/ajax.attachment.php',
\t\tformData: { operation: 'add', temp_id: '{$sTempId}', obj_class: '{$sClass}' },
        dataType: 'json',
\t\tpasteZone: null, // Don't accept files via Chrome's copy/paste
        done: function (e, data) {
\t\t\tif(typeof(data.result.error) != 'undefined')
\t\t\t{
\t\t\t\tif(data.result.error != '')
\t\t\t\t{
\t\t\t\t\talert(data.result.error);
\t\t\t\t}
\t\t\t\telse
\t\t\t\t{
\t\t\t\t\tvar sDownloadLink = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?operation=download_document&class=Attachment&id='+data.result.att_id+'&field=contents';
\t\t\t\t\t\$('#attachments').append('<div class="attachment" id="display_attachment_'+data.result.att_id+'"><a data-preview="'+data.result.preview+'" href="'+sDownloadLink+'"><img src="'+data.result.icon+'"><br/>'+data.result.msg+'<input id="attachment_'+data.result.att_id+'" type="hidden" name="attachments[]" value="'+data.result.att_id+'"/></a><br/><input type="button" class="btn_hidden" value="{$sDeleteBtn}" onClick="RemoveAttachment('+data.result.att_id+');"/></div>');
\t\t\t\t\tif({$sIsDeleteEnabled})
\t\t\t\t\t{
\t\t\t\t\t\t\$('#display_attachment_'+data.result.att_id).hover( function() { \$(this).children(':button').toggleClass('btn_hidden'); } );
\t\t\t\t\t}
\t\t\t\t\t\$('#attachment_plugin').trigger('add_attachment', [data.result.att_id, data.result.msg]);
\t\t\t\t}
\t\t\t}
        },
        start: function() {
        \t\$('#attachment_loading').show();
\t\t},
        stop: function() {
        \t\$('#attachment_loading').hide();
\t\t}
    });

\t\$(document).bind('dragover', function (e) {
\t\tvar bFiles = false;
\t\tif (e.dataTransfer && e.dataTransfer.types)
\t\t{
\t\t\tfor (var i = 0; i < e.dataTransfer.types.length; i++)
\t\t\t{
\t\t\t\tif (e.dataTransfer.types[i] == "application/x-moz-nativeimage")
\t\t\t\t{
\t\t\t\t\tbFiles = false; // mozilla contains "Files" in the types list when dragging images inside the page, but it also contains "application/x-moz-nativeimage" before
\t\t\t\t\tbreak;
\t\t\t\t}
\t\t\t\t
\t\t\t\tif (e.dataTransfer.types[i] == "Files")
\t\t\t\t{
\t\t\t\t\tbFiles = true;
\t\t\t\t\tbreak;
\t\t\t\t}
\t\t\t}
\t\t}
\t
\t\tif (!bFiles) return; // Not dragging files
\t\t
\t\tvar dropZone = \$('#file').closest('fieldset');
\t\tif (!dropZone.is(':visible'))
\t\t{
\t\t\t// Hidden, but inside an inactive tab? Higlight the tab
\t\t\tvar sTabId = dropZone.closest('.ui-tabs-panel').attr('aria-labelledby');
\t\t\tdropZone = \$('#'+sTabId).closest('li');
\t\t}
\t    timeout = window.dropZoneTimeout;
\t    if (!timeout) {
\t        dropZone.addClass('drag_in');
\t    } else {
\t        clearTimeout(timeout);
\t    }
\t    window.dropZoneTimeout = setTimeout(function () {
\t        window.dropZoneTimeout = null;
\t        dropZone.removeClass('drag_in');
\t    }, 300);
\t});   
EOF
);
            $oPage->p('<span style="display:none;" id="attachment_loading">Loading, please wait...</span>');
            $oPage->p('<input type="hidden" id="attachment_plugin" name="attachment_plugin"/>');
            if ($this->m_bDeleteEnabled) {
                $oPage->add_ready_script('$(".attachment").hover( function() {$(this).children(":button").toggleClass("btn_hidden"); } );');
            }
        } else {
            $oPage->add('<span id="attachments">');
            if ($oSet->Count() == 0) {
                $oPage->add(Dict::S('Attachments:NoAttachment'));
            } else {
                while ($oAttachment = $oSet->Fetch()) {
                    $iAttId = $oAttachment->GetKey();
                    $oDoc = $oAttachment->Get('contents');
                    $sFileName = $oDoc->GetFileName();
                    $sIcon = utils::GetAbsoluteUrlAppRoot() . AttachmentPlugIn::GetFileIcon($sFileName);
                    $sPreview = $oDoc->IsPreviewAvailable() ? 'true' : 'false';
                    $sDownloadLink = utils::GetAbsoluteUrlAppRoot() . 'pages/ajax.render.php?operation=download_document&class=Attachment&id=' . $iAttId . '&field=contents';
                    $oPage->add('<div class="attachment" id="attachment_' . $iAttId . '"><a data-preview="' . $sPreview . '" href="' . $sDownloadLink . '"><img src="' . $sIcon . '"><br/>' . $sFileName . '</a><input type="hidden" name="attachments[]" value="' . $iAttId . '"/><br/>&nbsp;&nbsp;</div>');
                }
            }
            $oPage->add('</span>');
        }
        $oPage->add('</fieldset>');
        $sPreviewNotAvailable = addslashes(Dict::S('Attachments:PreviewNotAvailable'));
        $iMaxWidth = MetaModel::GetModuleSetting('itop-attachments', 'preview_max_width', 290);
        $oPage->add_ready_script("\$(document).tooltip({ items: '.attachment a',  position: { my: 'left top', at: 'right top', using: function( position, feedback ) { \$( this ).css( position ); }}, content: function() { if (\$(this).attr('data-preview') == 'true') { return('<img style=\"max-width:{$iMaxWidth}px\" src=\"'+\$(this).attr('href')+'\"></img>');} else { return '{$sPreviewNotAvailable}'; }}});");
    }
    /**
     * 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>');
        }
    }
    /**
     * Display the graph inside the given page, with the "filter" drawer above it
     * @param WebPage $oP
     * @param hash $aResults
     * @param string $sRelation
     * @param ApplicationContext $oAppContext
     * @param array $aExcludedObjects
     */
    function Display(WebPage $oP, $aResults, $sRelation, ApplicationContext $oAppContext, $aExcludedObjects = array(), $sObjClass = null, $iObjKey = null, $sContextKey, $aContextParams = array())
    {
        $aContextDefs = static::GetContextDefinitions($sContextKey, true, $aContextParams);
        $aExcludedByClass = array();
        foreach ($aExcludedObjects as $oObj) {
            if (!array_key_exists(get_class($oObj), $aExcludedByClass)) {
                $aExcludedByClass[get_class($oObj)] = array();
            }
            $aExcludedByClass[get_class($oObj)][] = $oObj->GetKey();
        }
        $oP->add("<div class=\"not-printable\">\n");
        $oP->add("<div id=\"ds_flash\" class=\"SearchDrawer\" style=\"display:none;\">\n");
        if (!$oP->IsPrintableVersion()) {
            $oP->add_ready_script(<<<EOF
\t\$( "#tabbedContent_0" ).tabs({ heightStyle: "fill" });
EOF
);
        }
        $oP->add_ready_script(<<<EOF
\t\$("#dh_flash").click( function() {
\t\t\$("#ds_flash").slideToggle('normal', function() { \$("#ds_flash").parent().resize(); \$("#dh_flash").trigger('toggle_complete'); } );
\t\t\$("#dh_flash").toggleClass('open');
\t});
    \$('#ReloadMovieBtn').button().button('disable');
EOF
);
        $aSortedElements = array();
        foreach ($aResults as $sClassIdx => $aObjects) {
            foreach ($aObjects as $oCurrObj) {
                $sSubClass = get_class($oCurrObj);
                $aSortedElements[$sSubClass] = MetaModel::GetName($sSubClass);
            }
        }
        asort($aSortedElements);
        $idx = 0;
        foreach ($aSortedElements as $sSubClass => $sClassName) {
            $oP->add("<span style=\"padding-right:2em; white-space:nowrap;\"><input type=\"checkbox\" id=\"exclude_{$idx}\" name=\"excluded[]\" value=\"{$sSubClass}\" checked onChange=\"\$('#ReloadMovieBtn').button('enable')\"><label for=\"exclude_{$idx}\">&nbsp;" . MetaModel::GetClassIcon($sSubClass) . "&nbsp;{$sClassName}</label></span> ");
            $idx++;
        }
        $oP->add("<p style=\"text-align:right\"><button type=\"button\" id=\"ReloadMovieBtn\" onClick=\"DoReload()\">" . Dict::S('UI:Button:Refresh') . "</button></p>");
        $oP->add("</div>\n");
        $oP->add("<div class=\"HRDrawer\"></div>\n");
        $oP->add("<div id=\"dh_flash\" class=\"DrawerHandle\">" . Dict::S('UI:ElementsDisplayed') . "</div>\n");
        $oP->add("</div>\n");
        // class="not-printable"
        $aAdditionalContexts = array();
        foreach ($aContextDefs as $sKey => $aDefinition) {
            $aAdditionalContexts[] = array('key' => $sKey, 'label' => Dict::S($aDefinition['dict']), 'oql' => $aDefinition['oql'], 'default' => array_key_exists('default', $aDefinition) && $aDefinition['default'] == 'yes');
        }
        $sDirection = utils::ReadParam('d', 'horizontal');
        $iGroupingThreshold = utils::ReadParam('g', 5);
        $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot() . 'js/fraphael.js');
        $oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot() . 'css/jquery.contextMenu.css');
        $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot() . 'js/jquery.contextMenu.js');
        $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot() . 'js/simple_graph.js');
        try {
            $this->InitFromGraphviz();
            $sExportAsPdfURL = '';
            $sExportAsPdfURL = utils::GetAbsoluteUrlAppRoot() . 'pages/ajax.render.php?operation=relation_pdf&relation=' . $sRelation . '&direction=' . ($this->bDirectionDown ? 'down' : 'up');
            $oAppcontext = new ApplicationContext();
            $sContext = $oAppContext->GetForLink();
            $sDrillDownURL = utils::GetAbsoluteUrlAppRoot() . 'pages/UI.php?operation=details&class=%1$s&id=%2$s&' . $sContext;
            $sExportAsDocumentURL = utils::GetAbsoluteUrlAppRoot() . 'pages/ajax.render.php?operation=relation_attachment&relation=' . $sRelation . '&direction=' . ($this->bDirectionDown ? 'down' : 'up');
            $sLoadFromURL = utils::GetAbsoluteUrlAppRoot() . 'pages/ajax.render.php?operation=relation_json&relation=' . $sRelation . '&direction=' . ($this->bDirectionDown ? 'down' : 'up');
            $sAttachmentExportTitle = '';
            if ($sObjClass != null && $iObjKey != null) {
                $oTargetObj = MetaModel::GetObject($sObjClass, $iObjKey, false);
                if ($oTargetObj) {
                    $sAttachmentExportTitle = Dict::Format('UI:Relation:AttachmentExportOptions_Name', $oTargetObj->GetName());
                }
            }
            $sId = 'graph';
            $sStyle = '';
            if ($oP->IsPrintableVersion()) {
                // Optimize for printing on A4/Letter vertically
                $sStyle = 'margin-left:auto; margin-right:auto;';
                $oP->add_ready_script("\$('.simple-graph').width(18/2.54*96).resizable({ stop: function() { \$(window).trigger('resized'); }});");
                // Default width about 18 cm, since most browsers assume 96 dpi
            }
            $oP->add('<div id="' . $sId . '" class="simple-graph" style="' . $sStyle . '"></div>');
            $aParams = array('source_url' => $sLoadFromURL, 'sources' => $this->bDirectionDown ? $this->aSourceObjects : $this->aSinkObjects, 'excluded' => $aExcludedByClass, 'grouping_threshold' => $iGroupingThreshold, 'export_as_pdf' => array('url' => $sExportAsPdfURL, 'label' => Dict::S('UI:Relation:ExportAsPDF')), 'export_as_attachment' => array('url' => $sExportAsDocumentURL, 'label' => Dict::S('UI:Relation:ExportAsAttachment'), 'obj_class' => $sObjClass, 'obj_key' => $iObjKey), 'drill_down' => array('url' => $sDrillDownURL, 'label' => Dict::S('UI:Relation:DrillDown')), 'labels' => array('export_pdf_title' => Dict::S('UI:Relation:PDFExportOptions'), 'export_as_attachment_title' => $sAttachmentExportTitle, 'export' => Dict::S('UI:Button:Export'), 'cancel' => Dict::S('UI:Button:Cancel'), 'title' => Dict::S('UI:RelationOption:Title'), 'untitled' => Dict::S('UI:RelationOption:Untitled'), 'include_list' => Dict::S('UI:RelationOption:IncludeList'), 'comments' => Dict::S('UI:RelationOption:Comments'), 'grouping_threshold' => Dict::S('UI:RelationOption:GroupingThreshold'), 'refresh' => Dict::S('UI:Button:Refresh'), 'check_all' => Dict::S('UI:SearchValue:CheckAll'), 'uncheck_all' => Dict::S('UI:SearchValue:UncheckAll'), 'none_selected' => Dict::S('UI:Relation:NoneSelected'), 'nb_selected' => Dict::S('UI:SearchValue:NbSelected'), 'additional_context_info' => Dict::S('UI:Relation:AdditionalContextInfo'), 'zoom' => Dict::S('UI:Relation:Zoom'), 'loading' => Dict::S('UI:Loading')), 'page_format' => array('label' => Dict::S('UI:Relation:PDFExportPageFormat'), 'values' => array('A3' => Dict::S('UI:PageFormat_A3'), 'A4' => Dict::S('UI:PageFormat_A4'), 'Letter' => Dict::S('UI:PageFormat_Letter'))), 'page_orientation' => array('label' => Dict::S('UI:Relation:PDFExportPageOrientation'), 'values' => array('P' => Dict::S('UI:PageOrientation_Portrait'), 'L' => Dict::S('UI:PageOrientation_Landscape'))), 'additional_contexts' => $aAdditionalContexts, 'context_key' => $sContextKey);
            if (!extension_loaded('gd')) {
                // PDF export requires GD
                unset($aParams['export_as_pdf']);
            }
            if (!extension_loaded('gd') || is_null($sObjClass) || is_null($iObjKey)) {
                // Export as Attachment requires GD (for building the PDF) AND a valid objclass/objkey couple
                unset($aParams['export_as_attachment']);
            }
            $oP->add_ready_script("\$('#{$sId}').simple_graph(" . json_encode($aParams) . ");");
        } catch (Exception $e) {
            $oP->add('<div>' . $e->getMessage() . '</div>');
        }
        $oP->add_script(<<<EOF
\t\t
\tfunction DoReload()
\t{
\t\t\$('#ReloadMovieBtn').button('disable');
\t\ttry
\t\t{
\t\t\tvar aExcluded = [];
\t\t\t\$('input[name^=excluded]').each( function() {
\t\t\t\tif (!\$(this).prop('checked'))
\t\t\t\t{
\t\t\t\t\taExcluded.push(\$(this).val());
\t\t\t\t}
\t\t\t} );
\t\t\t\$('#graph').simple_graph('option', {excluded_classes: aExcluded});
\t\t\t\$('#graph').simple_graph('reload');
\t\t}
\t\tcatch(err)
\t\t{
\t\t\talert(err);
\t\t}
\t}
EOF
);
    }
    public function Display(WebPage $oP, $aExtraParams = array())
    {
        $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr);
        $sTargetClass = $oAttDef->GetTargetClass();
        $oTargetObj = MetaModel::GetObject($sTargetClass, $this->m_iObjectId);
        $oP->set_title("iTop - " . MetaModel::GetName($this->m_sLinkedClass) . " objects linked with " . MetaModel::GetName(get_class($oTargetObj)) . ": " . $oTargetObj->GetRawName());
        $oP->add("<div class=\"wizContainer\">\n");
        $oP->add("<form method=\"post\">\n");
        $oP->add("<div class=\"page_header\">\n");
        $oP->add("<input type=\"hidden\" id=\"linksToRemove\" name=\"linksToRemove\" value=\"\">\n");
        $oP->add("<input type=\"hidden\" name=\"operation\" value=\"do_modify_links\">\n");
        $oP->add("<input type=\"hidden\" name=\"class\" value=\"{$this->m_sClass}\">\n");
        $oP->add("<input type=\"hidden\" name=\"linkage\" value=\"{$this->m_sLinkageAttr}\">\n");
        $oP->add("<input type=\"hidden\" name=\"object_id\" value=\"{$this->m_iObjectId}\">\n");
        $oP->add("<input type=\"hidden\" name=\"linking_attcode\" value=\"{$this->m_sLinkingAttCode}\">\n");
        $oP->add("<h1>" . Dict::Format('UI:ManageObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName(get_class($oTargetObj)), "<span class=\"hilite\">" . $oTargetObj->GetHyperlink() . "</span>") . "</h1>\n");
        $oP->add("</div>\n");
        $oP->add_script(<<<EOF
\t\tfunction OnSelectChange()
\t\t{
\t\t\tvar nbChecked = \$('.selection:checked').length;
\t\t\tif (nbChecked > 0)
\t\t\t{
\t\t\t\t\$('#btnRemove').removeAttr('disabled');
\t\t\t}
\t\t\telse
\t\t\t{
\t\t\t\t\$('#btnRemove').attr('disabled','disabled');
\t\t\t}
\t\t}
\t\t
\t\tfunction RemoveSelected()
\t\t{
\t\t\t\$('.selection:checked').each(
\t\t\t\tfunction()
\t\t\t\t{
\t\t\t\t\t\$('#linksToRemove').val(\$('#linksToRemove').val() + ' ' + this.value);
\t\t\t\t\t\$('#row_'+this.value).remove();
\t\t\t\t}
\t\t\t);
\t\t\t// Disable the button since all the selected items have been removed
\t\t\t\$('#btnRemove').attr('disabled','disabled');
\t\t\t// Re-run the zebra plugin to properly highlight the remaining lines
\t\t\t\$('.listResults').trigger('update');
\t\t\t
\t\t}
\t\t
\t\tfunction AddObjects()
\t\t{
\t\t\t// TO DO: compute the list of objects already linked with the current Object
\t\t\t\$.post( GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', { 'operation': 'addObjects',
\t\t\t\t\t\t\t\t\t\t'class': '{$this->m_sClass}',
\t\t\t\t\t\t\t\t\t\t'linkageAttr': '{$this->m_sLinkageAttr}',
\t\t\t\t\t\t\t\t\t\t'linkedClass': '{$this->m_sLinkedClass}',
\t\t\t\t\t\t\t\t\t\t'objectId': '{$this->m_iObjectId}'
\t\t\t\t\t\t\t\t\t\t}, 
\t\t\t\tfunction(data)
\t\t\t\t{
\t\t\t\t\t\$('#ModalDlg').html(data);
\t\t\t\t\tdlgWidth = \$(document).width() - 100;
\t\t\t\t\t\$('#ModalDlg').css('width', dlgWidth);
\t\t\t\t\t\$('#ModalDlg').css('left', 50);
\t\t\t\t\t\$('#ModalDlg').css('top', 50);
\t\t\t\t\t\$('#ModalDlg').dialog( 'open' );
\t\t\t\t},
\t\t\t\t'html'
\t\t\t);
\t\t}
\t\t
\t\tfunction SearchObjectsToAdd(currentFormId)
\t\t{
\t\t\tvar theMap = { 'class': '{$this->m_sClass}',
\t\t\t\t\t\t   'linkageAttr': '{$this->m_sLinkageAttr}',
\t\t\t\t\t\t   'linkedClass': '{$this->m_sLinkedClass}',
\t\t\t\t\t\t   'objectId': '{$this->m_iObjectId}'
\t\t\t\t\t\t }
\t\t\tif (\$('#'+currentFormId+' :input[name=class]').val() != undefined)
\t\t\t{
\t\t\t\ttheMap.linkedClass = \$('#'+currentFormId+' :input[name=class]').val();
\t\t\t}
\t\t\t// Gather the parameters from the search form
\t\t\t\$('#'+currentFormId+' :input').each(
\t\t\t\tfunction(i)
\t\t\t\t{
\t\t\t\t\tif (this.name != '')
\t\t\t\t\t{
\t\t\t\t\t\ttheMap[this.name] = this.value;
\t\t\t\t\t}
\t\t\t\t}
\t\t\t);
\t\t\ttheMap['operation'] = 'searchObjectsToAdd';
\t\t\t
\t\t\t// Run the query and display the results
\t\t\t\$.post( GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', theMap, 
\t\t\t\tfunction(data)
\t\t\t\t{
\t\t\t\t\t\$('#SearchResultsToAdd').html(data);
\t\t\t\t\t\$('#SearchResultsToAdd .listResults').tablesorter( { headers: {0: false}}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
\t\t\t\t\t
\t\t\t\t},
\t\t\t\t'html'
\t\t\t);

\t\t\treturn false;
\t\t}
\t\t
\t\tfunction DoAddObjects(currentFormId)
\t\t{
\t\t\tvar theMap = { 'class': '{$this->m_sClass}',
\t\t\t\t\t\t   'linkageAttr': '{$this->m_sLinkageAttr}',
\t\t\t\t\t\t   'linkedClass': '{$this->m_sLinkedClass}',
\t\t\t\t\t\t   'objectId': '{$this->m_iObjectId}'
\t\t\t\t\t\t }
\t\t\t
\t\t\t// Gather the parameters from the search form
\t\t\t\$('#'+currentFormId+' :input').each(
\t\t\t\tfunction(i)
\t\t\t\t{
\t\t\t\t\tif ( (this.name != '') && ((this.type != 'checkbox') || (this.checked)) ) 
\t\t\t\t\t{
\t\t\t\t\t\t//console.log(this.type);
\t\t\t\t\t\tarrayExpr = /\\[\\]\$/;
\t\t\t\t\t\tif (arrayExpr.test(this.name))
\t\t\t\t\t\t{
\t\t\t\t\t\t\t// Array
\t\t\t\t\t\t\tif (theMap[this.name] == undefined)
\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\ttheMap[this.name] = new Array();
\t\t\t\t\t\t\t}
\t\t\t\t\t\t\ttheMap[this.name].push(this.value);
\t\t\t\t\t\t}
\t\t\t\t\t\telse
\t\t\t\t\t\t{
\t\t\t\t\t\t\ttheMap[this.name] = this.value;
\t\t\t\t\t\t}
\t\t\t\t\t}
\t\t\t\t}
\t\t\t);
\t\t\ttheMap['operation'] = 'doAddObjects';
\t\t\t
\t\t\t// Run the query and display the results
\t\t\t\$.post( GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', theMap, 
\t\t\t\tfunction(data)
\t\t\t\t{
\t\t\t\t\t//console.log('Data: ' + data);
\t\t\t\t\tif (data != '')
\t\t\t\t\t{
\t\t\t\t\t\t\$('#empty_row').remove();
\t\t\t\t\t}
\t\t\t\t\t\$('.listResults tbody').append(data);
\t\t\t\t\t\$('.listResults').trigger('update');
\t\t\t\t\t\$('.listResults').tablesorter( { headers: {0: false}}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
\t\t\t\t},
\t\t\t\t'html'
\t\t\t);
\t\t\t\$('#ModalDlg').dialog('close');
\t\t\treturn false;
\t\t}
\t\t
\t\tfunction InitForm()
\t\t{
\t\t\t// make sure that the form is clean
\t\t\t\$('.selection').each( function() { this.checked = false; });
\t\t\t\$('#btnRemove').attr('disabled','disabled');
\t\t\t\$('#linksToRemove').val('');
\t\t}
\t\t
\t\tfunction SubmitHook() 
\t\t{
\t\t\tvar the_form = this;
\t\t\tSearchObjectsToAdd(the_form.id);
\t\t\treturn false;
\t\t}
EOF
);
        $oP->add_ready_script("InitForm();");
        $oFilter = new DBObjectSearch($this->m_sClass);
        $oFilter->AddCondition($this->m_sLinkageAttr, $this->m_iObjectId, '=');
        $oSet = new DBObjectSet($oFilter);
        $aForm = array();
        while ($oCurrentLink = $oSet->Fetch()) {
            $aRow = array();
            $key = $oCurrentLink->GetKey();
            $oLinkedObj = MetaModel::GetObject($this->m_sLinkedClass, $oCurrentLink->Get($this->m_sLinkingAttCode));
            $aForm[$key] = $this->GetFormRow($oP, $oLinkedObj, $oCurrentLink);
        }
        //var_dump($aTableLabels);
        //var_dump($aForm);
        $this->DisplayFormTable($oP, $this->m_aTableConfig, $aForm);
        $oP->add("<span style=\"float:left;\">&nbsp;&nbsp;&nbsp;<img src=\"../images/tv-item-last.gif\">&nbsp;&nbsp;<input id=\"btnRemove\" type=\"button\" value=\"" . Dict::S('UI:RemoveLinkedObjectsOf_Class') . "\" onClick=\"RemoveSelected();\" >");
        $oP->add("&nbsp;&nbsp;&nbsp;<input id=\"btnAdd\" type=\"button\" value=\"" . Dict::Format('UI:AddLinkedObjectsOf_Class', MetaModel::GetName($this->m_sLinkedClass)) . "\" onClick=\"AddObjects();\"></span>\n");
        $oP->add("<span style=\"float:right;\"><input id=\"btnCancel\" type=\"button\" value=\"" . Dict::S('UI:Button:Cancel') . "\" onClick=\"BackToDetails('" . $sTargetClass . "', " . $this->m_iObjectId . ");\">");
        $oP->add("&nbsp;&nbsp;&nbsp;<input id=\"btnOk\" type=\"submit\" value=\"" . Dict::S('UI:Button:Ok') . "\"></span>\n");
        $oP->add("<span style=\"clear:both;\"><p>&nbsp;</p></span>\n");
        $oP->add("</div>\n");
        $oP->add("</form>\n");
        if (isset($aExtraParams['StartWithAdd']) && $aExtraParams['StartWithAdd']) {
            $oP->add_ready_script("AddObjects();");
        }
    }
    /**
     * Display the history of bulk imports
     */
    static function DisplayImportHistory(WebPage $oPage, $bFromAjax = false, $bShowAll = false)
    {
        $sAjaxDivId = "CSVImportHistory";
        if (!$bFromAjax) {
            $oPage->add('<div id="' . $sAjaxDivId . '">');
        }
        $oPage->p(Dict::S('UI:History:BulkImports+') . ' <span id="csv_history_reload"></span>');
        $oBulkChangeSearch = DBObjectSearch::FromOQL("SELECT CMDBChange WHERE origin IN ('csv-interactive', 'csv-import.php')");
        $iQueryLimit = $bShowAll ? 0 : appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit());
        $oBulkChanges = new DBObjectSet($oBulkChangeSearch, array('date' => false), array(), null, $iQueryLimit);
        $oAppContext = new ApplicationContext();
        $bLimitExceeded = false;
        if ($oBulkChanges->Count() > appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit())) {
            $bLimitExceeded = true;
            if (!$bShowAll) {
                $iMaxObjects = appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit());
                $oBulkChanges->SetLimit($iMaxObjects);
            }
        }
        $oBulkChanges->Seek(0);
        $aDetails = array();
        while ($oChange = $oBulkChanges->Fetch()) {
            $sDate = '<a href="csvimport.php?step=10&changeid=' . $oChange->GetKey() . '&' . $oAppContext->GetForLink() . '">' . $oChange->Get('date') . '</a>';
            $sUser = $oChange->GetUserName();
            if (preg_match('/^(.*)\\(CSV\\)$/i', $oChange->Get('userinfo'), $aMatches)) {
                $sUser = $aMatches[1];
            } else {
                $sUser = $oChange->Get('userinfo');
            }
            $oOpSearch = DBObjectSearch::FromOQL("SELECT CMDBChangeOpCreate WHERE change = :change_id");
            $oOpSet = new DBObjectSet($oOpSearch, array(), array('change_id' => $oChange->GetKey()));
            $iCreated = $oOpSet->Count();
            // Get the class from the first item found (assumption: a CSV load is done for a single class)
            if ($oCreateOp = $oOpSet->Fetch()) {
                $sClass = $oCreateOp->Get('objclass');
            }
            $oOpSearch = DBObjectSearch::FromOQL("SELECT CMDBChangeOpSetAttribute WHERE change = :change_id");
            $oOpSet = new DBObjectSet($oOpSearch, array(), array('change_id' => $oChange->GetKey()));
            $aModified = array();
            $aAttList = array();
            while ($oModified = $oOpSet->Fetch()) {
                // Get the class (if not done earlier on object creation)
                $sClass = $oModified->Get('objclass');
                $iKey = $oModified->Get('objkey');
                $sAttCode = $oModified->Get('attcode');
                $aAttList[$sClass][$sAttCode] = true;
                $aModified["{$sClass}::{$iKey}"] = true;
            }
            $iModified = count($aModified);
            // Assumption: there is only one class of objects being loaded
            // Then the last class found gives us the class for every object
            if ($iModified > 0 || $iCreated > 0) {
                $aDetails[] = array('date' => $sDate, 'user' => $sUser, 'class' => $sClass, 'created' => $iCreated, 'modified' => $iModified);
            }
        }
        $aConfig = array('date' => array('label' => Dict::S('UI:History:Date'), 'description' => Dict::S('UI:History:Date+')), 'user' => array('label' => Dict::S('UI:History:User'), 'description' => Dict::S('UI:History:User+')), 'class' => array('label' => Dict::S('Core:AttributeClass'), 'description' => Dict::S('Core:AttributeClass+')), 'created' => array('label' => Dict::S('UI:History:StatsCreations'), 'description' => Dict::S('UI:History:StatsCreations+')), 'modified' => array('label' => Dict::S('UI:History:StatsModifs'), 'description' => Dict::S('UI:History:StatsModifs+')));
        if ($bLimitExceeded) {
            if ($bShowAll) {
                // Collapsible list
                $oPage->add('<p>' . Dict::Format('UI:CountOfResults', $oBulkChanges->Count()) . '&nbsp;&nbsp;<a class="truncated" onclick="OnTruncatedHistoryToggle(false);">' . Dict::S('UI:CollapseList') . '</a></p>');
            } else {
                // Truncated list
                $iMinDisplayLimit = appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit());
                $sCollapsedLabel = Dict::Format('UI:TruncatedResults', $iMinDisplayLimit, $oBulkChanges->Count());
                $sLinkLabel = Dict::S('UI:DisplayAll');
                $oPage->add('<p>' . $sCollapsedLabel . '&nbsp;&nbsp;<a class="truncated" onclick="OnTruncatedHistoryToggle(true);">' . $sLinkLabel . '</p>');
                $oPage->add_ready_script(<<<EOF
\t\$('#{$sAjaxDivId} table.listResults').addClass('truncated');
\t\$('#{$sAjaxDivId} table.listResults tr:last td').addClass('truncated');
EOF
);
                $sAppContext = $oAppContext->GetForLink();
                $oPage->add_script(<<<EOF
\tfunction OnTruncatedHistoryToggle(bShowAll)
\t{
\t\t\$('#csv_history_reload').html('<img src="../images/indicator.gif"/>');
\t\t\$.get(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?{$sAppContext}', {operation: 'displayCSVHistory', showall: bShowAll}, function(data)
\t\t\t{
\t\t\t\t\$('#{$sAjaxDivId}').html(data);
\t\t\t\tvar table = \$('#{$sAjaxDivId} .listResults');
\t\t\t\ttable.tableHover(); // hover tables
\t\t\t\ttable.tablesorter( { widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
\t\t\t}
\t\t);
\t}
EOF
);
            }
        } else {
            // Normal display - full list without any decoration
        }
        $oPage->table($aConfig, $aDetails);
        if (!$bFromAjax) {
            $oPage->add('</div>');
        }
    }