Integration of the new impact analysis into the tickets.

SVN:trunk[3578]
This commit is contained in:
Denis Flaven
2015-05-15 13:49:25 +00:00
parent d1a74589b1
commit 7ca7cb39ae
21 changed files with 700 additions and 505 deletions

View File

@@ -218,7 +218,7 @@ EOF;
},
beforeLoad: function( event, ui ) {
if ( ui.tab.data('loaded') && (ui.tab.attr('data-cache') == 'true')) {
event.defaultPrevented = true;
event.preventDefault();
return;
}
ui.panel.html('<div><img src="../images/indicator.gif"></div>');
@@ -867,26 +867,29 @@ EOF
}
else if ($this->GetOutputFormat() == 'pdf' && $this->IsOutputFormatAvailable('pdf') )
{
require_once(APPROOT.'lib/MPDF/mpdf.php');
$oMPDF = new mPDF('c');
$oMPDF->mirroMargins = false;
if ($this->a_base['href'] != '')
if (@is_readable(APPROOT.'lib/MPDF/mpdf.php'))
{
$oMPDF->setBasePath($this->a_base['href']); // Seems that the <BASE> tag is not recognized by mPDF...
require_once(APPROOT.'lib/MPDF/mpdf.php');
$oMPDF = new mPDF('c');
$oMPDF->mirroMargins = false;
if ($this->a_base['href'] != '')
{
$oMPDF->setBasePath($this->a_base['href']); // Seems that the <BASE> tag is not recognized by mPDF...
}
$oMPDF->showWatermarkText = true;
if ($this->GetOutputOption('pdf', 'template_path'))
{
$oMPDF->setImportUse(); // Allow templates
$oMPDF->SetDocTemplate ($this->GetOutputOption('pdf', 'template_path'), 1);
}
$oMPDF->WriteHTML($sHtml);
$sOutputName = $this->s_title.'.pdf';
if ($this->GetOutputOption('pdf', 'output_name'))
{
$sOutputName = $this->GetOutputOption('pdf', 'output_name');
}
$oMPDF->Output($sOutputName, 'I');
}
$oMPDF->showWatermarkText = true;
if ($this->GetOutputOption('pdf', 'template_path'))
{
$oMPDF->setImportUse(); // Allow templates
$oMPDF->SetDocTemplate ($this->GetOutputOption('pdf', 'template_path'), 1);
}
$oMPDF->WriteHTML($sHtml);
$sOutputName = $this->s_title.'.pdf';
if ($this->GetOutputOption('pdf', 'output_name'))
{
$sOutputName = $this->GetOutputOption('pdf', 'output_name');
}
$oMPDF->Output($sOutputName, 'I');
}
MetaModel::RecordQueryTrace();
ExecutionKPI::ReportStats();

View File

@@ -29,8 +29,8 @@ class iTopPDF extends TCPDF
$aMargins = $this->getMargins();
// Display the title (centered)
$this->SetY(0);
$this->MultiCell($this->getPageWidth() - $aMargins['left'] - $aMargins['right'] - $iPageNumberWidth, 15, $this->sDocumentTitle, 0, 'C', false, 0 /* $ln */, '', '', true, 0, false, true, 15, 'M' /* $valign */);
$this->SetXY($aMargins['left'] + $iPageNumberWidth, 0);
$this->MultiCell($this->getPageWidth() - $aMargins['left'] - $aMargins['right'] - 2*$iPageNumberWidth, 15, $this->sDocumentTitle, 0, 'C', false, 0 /* $ln */, '', '', true, 0, false, true, 15, 'M' /* $valign */);
$this->SetFont('dejavusans', '', 10);
// Display the page number (right aligned)

View File

@@ -560,13 +560,17 @@ class DisplayableGroupNode extends DisplayableNode
*/
class DisplayableGraph extends SimpleGraph
{
protected $sDirection;
protected $bDirectionDown;
protected $aTempImages;
protected $aSourceObjects;
protected $aSinkObjects;
public function __construct()
{
parent::__construct();
$this->aTempImages = array();
$this->aSourceObjects = array();
$this->aSinkObjects = array();
}
public function GetTempImageName()
@@ -594,6 +598,7 @@ class DisplayableGraph extends SimpleGraph
public static function FromRelationGraph(RelationGraph $oGraph, $iGroupingThreshold = 20, $bDirectionDown = true)
{
$oNewGraph = new DisplayableGraph();
$oNewGraph->bDirectionDown = $bDirectionDown;
$oNodesIter = new RelationTypeIterator($oGraph, 'Node');
foreach($oNodesIter as $oNode)
@@ -603,16 +608,27 @@ class DisplayableGraph extends SimpleGraph
case 'RelationObjectNode':
$oNewNode = new DisplayableNode($oNewGraph, $oNode->GetId(), 0, 0);
$oObj = $oNode->GetProperty('object');
$sClass = get_class($oObj);
if ($oNode->GetProperty('source'))
{
if (!array_key_exists($sClass, $oNewGraph->aSourceObjects))
{
$oNewGraph->aSourceObjects[$sClass] = array();
}
$oNewGraph->aSourceObjects[$sClass][] = $oObj->GetKey();
$oNewNode->SetProperty('source', true);
}
if ($oNode->GetProperty('sink'))
{
if (!array_key_exists($sClass, $oNewGraph->aSinkObjects))
{
$oNewGraph->aSinkObjects[$sClass] = array();
}
$oNewGraph->aSinkObjects[$sClass][] = $oObj->GetKey();
$oNewNode->SetProperty('sink', true);
}
$oObj = $oNode->GetProperty('object');
$oNewNode->SetProperty('class', get_class($oObj));
$oNewNode->SetProperty('class', $sClass);
$oNewNode->SetProperty('object', $oObj);
$oNewNode->SetProperty('icon_url', $oObj->GetIcon(false));
$oNewNode->SetProperty('label', $oObj->GetRawName());
@@ -791,71 +807,6 @@ class DisplayableGraph extends SimpleGraph
}
}
}
/**
* Renders as a suite of Javascript instructions to display the graph using the simple_graph widget
* @param WebPage $oP
* @param string $sId
* @param string $sExportAsPdfURL
* @param string $sExportAsDocumentURL
* @param string $sDrillDownURL
*/
function RenderAsRaphael(WebPage $oP, $sId = null, $sExportAsPdfURL, $sExportAsDocumentURL, $sDrillDownURL)
{
if ($sId == null)
{
$sId = 'graph';
}
$oP->add('<div id="'.$sId.'" class="simple-graph"></div>');
$aParams = array(
'export_as_pdf' => array('url' => $sExportAsPdfURL, 'label' => Dict::S('UI:Relation:ExportAsPDF')),
'export_as_document' => array('url' => $sExportAsDocumentURL, 'label' => Dict::S('UI:Relation:ExportAsDocument')),
'drill_down' => array('url' => $sDrillDownURL, 'label' => Dict::S('UI:Relation:DrillDown')),
'labels' => array(
'export_pdf_title' => Dict::S('UI:Relation:PDFExportOptions'),
'export' => Dict::S('UI:Relation:PDFExportOptions'),
'cancel' => Dict::S('UI:Button:Cancel'),
),
'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'),
),
),
);
$oP->add_ready_script("var oGraph = $('#$sId').simple_graph(".json_encode($aParams).");");
$oIterator = new RelationTypeIterator($this, 'Node');
foreach($oIterator as $sId => $oNode)
{
$aNode = $oNode->GetForRaphael();
$sJSNode = json_encode($aNode);
$oP->add_ready_script("oGraph.simple_graph('add_node', $sJSNode);");
}
$oIterator = new RelationTypeIterator($this, 'Edge');
foreach($oIterator as $sId => $oEdge)
{
$aEdge = array();
$aEdge['id'] = $oEdge->GetId();
$aEdge['source_node_id'] = $oEdge->GetSourceNode()->GetId();
$aEdge['sink_node_id'] = $oEdge->GetSinkNode()->GetId();
$fOpacity = ($oEdge->GetSinkNode()->GetProperty('is_reached') && $oEdge->GetSourceNode()->GetProperty('is_reached') ? 1 : 0.2);
$aEdge['attr'] = array('opacity' => $fOpacity, 'stroke' => '#000');
$sJSEdge = json_encode($aEdge);
$oP->add_ready_script("oGraph.simple_graph('add_edge', $sJSEdge);");
}
$oP->add_ready_script("oGraph.simple_graph('draw');");
}
/**
* Renders as JSON string suitable for loading into the simple_graph widget
@@ -964,13 +915,26 @@ class DisplayableGraph extends SimpleGraph
$oPdf->SetAlpha(1);
}
/**
* Renders (in PDF) the key (legend) of the graphics vertically to the left of the specified zone (xmin,ymin, xmax,ymax). Returns the width used by the legend.
* @param TCPDF $oPdf
* @param string $sComments
* @param float $xMin
* @param float $yMin
* @param float $xMax
* @param float $yMax
* @return number The width used by the legend
*/
protected function RenderKey(TCPDF $oPdf, $sComments, $xMin, $yMin, $xMax, $yMax)
{
$fFontSize = 7; // in mm
$fIconSize = 6; // in mm
$fPadding = 1; // in mm
$oIterator = new RelationTypeIterator($this, 'Node');
$fMaxWidth = max($oPdf->GetStringWidth(Dict::S('UI:Relation:Key')) - 6, $oPdf->GetStringWidth(Dict::S('UI:Relation:Comments')) - 6);
$fMaxWidth = max($oPdf->GetStringWidth(Dict::S('UI:Relation:Key')) - $fIconSize, $oPdf->GetStringWidth(Dict::S('UI:Relation:Comments')) - $fIconSize);
$aClasses = array();
$aIcons = array();
$oPdf->SetFont('dejavusans', '', 8, '', true);
$oPdf->SetFont('dejavusans', '', $fFontSize, '', true);
foreach($oIterator as $sId => $oNode)
{
if ($sClass = $oNode->GetProperty('class'))
@@ -987,26 +951,169 @@ class DisplayableGraph extends SimpleGraph
}
}
}
$oPdf->SetXY($xMin + 1, $yMin +1);
$yPos = $yMin + 1;
$oPdf->SetXY($xMin + $fPadding, $yMin + $fPadding);
$yPos = $yMin + $fPadding;
$oPdf->SetFillColor(225, 225, 225);
$oPdf->Cell(7 + $fMaxWidth, 7, Dict::S('UI:Relation:Key'), 0 /* border */, 1 /* ln */, 'C', true /* fill */);
$yPos += 8;
$oPdf->Cell($fIconSize + $fPadding + $fMaxWidth, $fIconSize + $fPadding, Dict::S('UI:Relation:Key'), 0 /* border */, 1 /* ln */, 'C', true /* fill */);
$yPos += $fIconSize + 2*$fPadding;
foreach($aClasses as $sClass => $sLabel)
{
$oPdf->SetX($xMin + 7);
$oPdf->Cell(0, 8, $sLabel, 0 /* border */, 1 /* ln */);
$oPdf->Image($aIcons[$sClass], $xMin+1, $yPos, 6, 6);
$yPos += 8;
$oPdf->SetX($xMin + $fIconSize + $fPadding);
$oPdf->Cell(0, $fIconSize + 2*$fPadding, $sLabel, 0 /* border */, 1 /* ln */);
$oPdf->Image($aIcons[$sClass], $xMin+1, $yPos, $fIconSize, $fIconSize);
$yPos += $fIconSize + 2*$fPadding;
}
$oPdf->Rect($xMin, $yMin, $fMaxWidth+9, $yPos - $yMin, 'D');
$oPdf->Rect($xMin, $yPos, $fMaxWidth+9, $yMax - $yPos, 'D');
$oPdf->Rect($xMin, $yMin, $fMaxWidth + $fIconSize + 3*$fPadding, $yPos - $yMin, 'D');
$oPdf->Rect($xMin, $yPos, $fMaxWidth + $fIconSize + 3*$fPadding, $yMax - $yPos, 'D');
$yPos +=1;
$oPdf->SetXY($xMin + 1, $yPos);
$oPdf->Cell(7 + $fMaxWidth, 7, Dict::S('UI:Relation:Comments'), 0 /* border */, 1 /* ln */, 'C', true /* fill */);
$yPos += 8;
$oPdf->SetXY($xMin + $fPadding, $yPos);
$oPdf->Cell($fIconSize + $fPadding + $fMaxWidth, $fIconSize + $fPadding, Dict::S('UI:Relation:Comments'), 0 /* border */, 1 /* ln */, 'C', true /* fill */);
$yPos += $fIconSize + 2*$fPadding;
$oPdf->SetX($xMin);
$oPdf->writeHTMLCell(8 + $fMaxWidth, $yMax - $yPos, $xMin, $yPos, '<p>'.str_replace("\n", '<br/>', htmlentities($sComments, ENT_QUOTES, 'UTF-8')).'</p>', 0 /* border */, 1 /* ln */);
return $fMaxWidth + 10;
$oPdf->writeHTMLCell($fIconSize + 2*$fPadding + $fMaxWidth, $yMax - $yPos, $xMin, $yPos, '<p>'.str_replace("\n", '<br/>', htmlentities($sComments, ENT_QUOTES, 'UTF-8')).'</p>', 0 /* border */, 1 /* ln */);
return $fMaxWidth + $fIconSize + 4*$fPadding;
}
/**
* 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)
{
$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 id=\"ds_flash\" class=\"SearchDrawer\" style=\"display:none;\">\n");
$oP->add_ready_script(
<<<EOF
$( "#tabbedContent_0" ).tabs({ heightStyle: "fill" });
$("#dh_flash").click( function() {
$("#ds_flash").slideToggle('normal', function() { $("#ds_flash").parent().resize(); } );
$("#dh_flash").toggleClass('open');
});
$('#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");
$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 = '';
if (extension_loaded('gd'))
{
$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 = '';
$sLoadFromURL = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=relation_json&relation='.$sRelation.'&direction='.($this->bDirectionDown ? 'down' : 'up');
$sId = 'graph';
$oP->add('<div id="'.$sId.'" class="simple-graph"></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_document' => array('url' => $sExportAsDocumentURL, 'label' => Dict::S('UI:Relation:ExportAsDocument')),
'drill_down' => array('url' => $sDrillDownURL, 'label' => Dict::S('UI:Relation:DrillDown')),
'labels' => array(
'export_pdf_title' => Dict::S('UI:Relation:PDFExportOptions'),
'export' => Dict::S('UI:Button:Export'),
'cancel' => Dict::S('UI:Button:Cancel'),
'title' => Dict::S('UI:RelationOption:Title'),
'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'),
),
'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'),
),
),
);
$oP->add_ready_script("$('#$sId').simple_graph(".json_encode($aParams).");");
}
catch(Exception $e)
{
$oP->add('<div>'.$e->getMessage().'</div>');
}
$oP->add_script(
<<<EOF
function DoReload()
{
$('#ReloadMovieBtn').button('disable');
try
{
var aExcluded = [];
$('input[name^=excluded]').each( function() {
if (!$(this).prop('checked'))
{
aExcluded.push($(this).val());
}
} );
$('#graph').simple_graph('option', {excluded_classes: aExcluded});
$('#graph').simple_graph('reload');
}
catch(err)
{
alert(err);
}
}
EOF
);
}
}

View File

@@ -1454,17 +1454,18 @@ abstract class MetaModel
* @param array $asourceObjects The objects to start with
* @param int $iMaxDepth
* @param boolean $bEnableReduncancy
* @param array $aUnreachable Array of objects to be considered as 'unreachable'
*
* @return RelationGraph The graph of all the related objects
*/
static public function GetRelatedObjectsDown($sRelCode, $aSourceObjects, $iMaxDepth = 99, $bEnableRedundancy = true)
static public function GetRelatedObjectsDown($sRelCode, $aSourceObjects, $iMaxDepth = 99, $bEnableRedundancy = true, $aUnreachable = array())
{
$oGraph = new RelationGraph();
foreach ($aSourceObjects as $oObject)
{
$oGraph->AddSourceObject($oObject);
}
$oGraph->ComputeRelatedObjectsDown($sRelCode, $iMaxDepth, $bEnableRedundancy);
$oGraph->ComputeRelatedObjectsDown($sRelCode, $iMaxDepth, $bEnableRedundancy, $aUnreachable);
return $oGraph;
}

View File

@@ -75,11 +75,11 @@ class RelationObjectNode extends GraphNode
}
/**
* Recursively mark the objects nodes as reached, unless we get stopped by a redundancy node
* Recursively mark the objects nodes as reached, unless we get stopped by a redundancy node or a 'not allowed' node
*/
public function ReachDown($sProperty, $value)
{
if (is_null($this->GetProperty($sProperty)))
if (is_null($this->GetProperty($sProperty)) && ($this->GetProperty($sProperty.'_allowed') !== false))
{
$this->SetProperty($sProperty, $value);
foreach ($this->GetOutgoingEdges() as $oOutgoingEdge)
@@ -201,7 +201,7 @@ class RelationGraph extends SimpleGraph
/**
* Build the graph downstream, and mark the nodes that can be reached from the source node
*/
public function ComputeRelatedObjectsDown($sRelCode, $iMaxDepth, $bEnableRedundancy)
public function ComputeRelatedObjectsDown($sRelCode, $iMaxDepth, $bEnableRedundancy, $aUnreachableObjects = array())
{
//echo "<h5>Sources only...</h5>\n".$this->DumpAsHtmlImage()."<br/>\n";
// Build the graph out of the sources
@@ -210,6 +210,21 @@ class RelationGraph extends SimpleGraph
$this->AddRelatedObjects($sRelCode, true, $oSourceNode, $iMaxDepth, $bEnableRedundancy);
//echo "<h5>After processing of {$oSourceNode->GetId()}</h5>\n".$this->DumpAsHtmlImage()."<br/>\n";
}
// Mark the unreachable nodes
foreach ($aUnreachableObjects as $oObj)
{
$sNodeId = RelationObjectNode::MakeId($oObj);
$oNode = $this->GetNode($sNodeId);
if($oNode)
{
$oNode->SetProperty('is_reached_allowed', false);
}
else
{
}
}
// Determine the reached nodes
foreach ($this->aSourceNodes as $oSourceNode)
{
@@ -437,4 +452,28 @@ class RelationGraph extends SimpleGraph
}
return $oRet;
}
/**
* Get the objects referenced by the graph as a hash array: 'class' => array of objects
* @return Ambigous <multitype:multitype: , unknown>
*/
public function GetObjectsByClass()
{
$aResults = array();
$oIterator = new RelationTypeIterator($this, 'Node');
foreach($oIterator as $oNode)
{
$oObj = $oNode->GetProperty('object'); // Some nodes (Redundancy Nodes and Group) do not contain an object
if ($oObj)
{
$sObjClass = get_class($oObj);
if (!array_key_exists($sObjClass, $aResults))
{
$aResults[$sObjClass] = array();
}
$aResults[$sObjClass][] = $oObj;
}
}
return $aResults;
}
}

View File

@@ -932,47 +932,32 @@
<static>false</static>
<access>protected</access>
<type>Overload-DBObject</type>
<code><![CDATA[ protected function OnInsert()
<code><![CDATA[
protected function OnInsert()
{
$oToNotify = $this->Get('contacts_list');
$oToImpact = $this->Get('functionalcis_list');
$oImpactedInfras = DBObjectSet::FromLinkSet($this, 'functionalcis_list', 'functionalci_id');
$aComputed = $oImpactedInfras->GetRelatedObjects('impacts', 10);
if (isset($aComputed['FunctionalCI']) && is_array($aComputed['FunctionalCI']))
{
foreach($aComputed['FunctionalCI'] as $iKey => $oObject)
{
$oNewLink = new lnkFunctionalCIToTicket();
$oNewLink->Set('functionalci_id', $iKey);
$oToImpact->AddObject($oNewLink);
}
}
if (isset($aComputed['Contact']) && is_array($aComputed['Contact']))
{
foreach($aComputed['Contact'] as $iKey => $oObject)
{
$oNewLink = new lnkContactToTicket();
$oNewLink->Set('contact_id', $iKey);
$oNewLink->Set('role', 'contact automatically computed');
$oToNotify->AddObject($oNewLink);
}
}
parent::OnInsert();
$this->UpdateImpactedItems();
$this->Set('creation_date', time());
$this->Set('last_update', time());
}]]></code>
}]]>
</code>
</method>
<method id="OnUpdate">
<static>false</static>
<access>protected</access>
<type>Overload-DBObject</type>
<code><![CDATA[ protected function OnUpdate()
{
$this->Set('last_update', time());
}]]></code>
<code><![CDATA[
protected function OnUpdate()
{
parent::OnUpdate();
$aChanges = $this->ListChanges();
if (array_key_exists('functionalcis_list', $aChanges))
{
$this->UpdateImpactedItems();
}
$this->Set('last_update', time());
}]]>
</code>
</method>
</methods>
<presentation>

View File

@@ -498,35 +498,11 @@
<static>false</static>
<access>protected</access>
<type>Overload-DBObject</type>
<code><![CDATA[ protected function OnInsert()
<code><![CDATA[
protected function OnInsert()
{
$oToNotify = $this->Get('contacts_list');
$oToImpact = $this->Get('functionalcis_list');
$oImpactedInfras = DBObjectSet::FromLinkSet($this, 'functionalcis_list', 'functionalci_id');
$aComputed = $oImpactedInfras->GetRelatedObjects('impacts', 10);
if (isset($aComputed['FunctionalCI']) && is_array($aComputed['FunctionalCI']))
{
foreach($aComputed['FunctionalCI'] as $iKey => $oObject)
{
$oNewLink = new lnkFunctionalCIToTicket();
$oNewLink->Set('functionalci_id', $iKey);
$oToImpact->AddObject($oNewLink);
}
}
if (isset($aComputed['Contact']) && is_array($aComputed['Contact']))
{
foreach($aComputed['Contact'] as $iKey => $oObject)
{
$oNewLink = new lnkContactToTicket();
$oNewLink->Set('contact_id', $iKey);
$oNewLink->Set('role', 'contact automatically computed');
$oToNotify->AddObject($oNewLink);
}
}
parent::OnInsert();
$this->UpdateImpactedItems();
$this->Set('creation_date', time());
$this->Set('last_update', time());
}]]></code>
@@ -535,8 +511,15 @@
<static>false</static>
<access>protected</access>
<type>Overload-DBObject</type>
<code><![CDATA[ protected function OnUpdate()
<code><![CDATA[
protected function OnUpdate()
{
parent::OnUpdate();
$aChanges = $this->ListChanges();
if (array_key_exists('functionalcis_list', $aChanges))
{
$this->UpdateImpactedItems();
}
$this->Set('last_update', time());
}]]></code>
</method>

View File

@@ -1361,61 +1361,21 @@
<static>false</static>
<access>public</access>
<type>LifecycleAction</type>
<code><![CDATA[ public function ComputeImpactedItems()
<code><![CDATA[
public function ComputeImpactedItems()
{
$oToNotify = $this->Get('contacts_list');
$oToImpact = $this->Get('functionalcis_list');
$oImpactedInfras = DBObjectSet::FromLinkSet($this, 'functionalcis_list', 'functionalci_id');
$oGraph = $oImpactedInfras->GetRelatedObjectsDown('impacts',10, true /* bEnableRedundancy */);
$oIterator = new RelationTypeIterator($oGraph, 'Node');
foreach($oIterator as $oNode)
{
if($oNode instanceof RelationObjectNode)
{
if ($oNode->GetProperty('is_reached') && (!$oNode->GetProperty('source')))
{
$oObj = $oNode->GetProperty('object');
$sRootClass = MetaModel::GetRootClass(get_class($oObj));
if (!array_key_exists($sRootClass, $aComputed))
{
$aComputed[$sRootClass] = array();
}
$aComputed[$sRootClass][$oObj->GetKey()] = $oObj;
}
}
}
if (isset($aComputed['FunctionalCI']) && is_array($aComputed['FunctionalCI']))
{
foreach($aComputed['FunctionalCI'] as $iKey => $oObject)
{
$oNewLink = new lnkFunctionalCIToTicket();
$oNewLink->Set('functionalci_id', $iKey);
$oNewLink->Set('impact', 'potentially impacted (automatically computed)');
$oToImpact->AddObject($oNewLink);
}
}
if (isset($aComputed['Contact']) && is_array($aComputed['Contact']))
{
foreach($aComputed['Contact'] as $iKey => $oObject)
{
$oNewLink = new lnkContactToTicket();
$oNewLink->Set('contact_id', $iKey);
$oNewLink->Set('role', 'contact automatically computed');
$oToNotify->AddObject($oNewLink);
}
}
parent::OnInsert();
// This method is kept for backward compatibility
// in case a delta redefines it, but you may call
// UpdateImpactedItems directly
$this->UpdateImpactedItems();
}]]></code>
</method>
<method id="OnInsert">
<static>false</static>
<access>protected</access>
<type>Overload-DBObject</type>
<code><![CDATA[ protected function OnInsert()
<code><![CDATA[
protected function OnInsert()
{
$this->ComputeImpactedItems();
$this->Set('last_update', time());
@@ -1426,8 +1386,15 @@
<static>false</static>
<access>protected</access>
<type>Overload-DBObject</type>
<code><![CDATA[ protected function OnUpdate()
<code><![CDATA[
protected function OnUpdate()
{
parent::OnUpdate();
$aChanges = $this->ListChanges();
if (array_key_exists('functionalcis_list', $aChanges))
{
$this->UpdateImpactedItems();
}
$this->Set('last_update', time());
$this->UpdateChildRequestLog();
}]]></code>

View File

@@ -1363,62 +1363,23 @@
<static>false</static>
<access>public</access>
<type>LifecycleAction</type>
<code><![CDATA[ public function ComputeImpactedItems()
<code><![CDATA[
public function ComputeImpactedItems()
{
$oToNotify = $this->Get('contacts_list');
$oToImpact = $this->Get('functionalcis_list');
$oImpactedInfras = DBObjectSet::FromLinkSet($this, 'functionalcis_list', 'functionalci_id');
$oGraph = $oImpactedInfras->GetRelatedObjectsDown('impacts',10, true /* bEnableRedundancy */);
$oIterator = new RelationTypeIterator($oGraph, 'Node');
foreach($oIterator as $oNode)
{
if($oNode instanceof RelationObjectNode)
{
if ($oNode->GetProperty('is_reached') && (!$oNode->GetProperty('source')))
{
$oObj = $oNode->GetProperty('object');
$sRootClass = MetaModel::GetRootClass(get_class($oObj));
if (!array_key_exists($sRootClass, $aComputed))
{
$aComputed[$sRootClass] = array();
}
$aComputed[$sRootClass][$oObj->GetKey()] = $oObj;
}
}
}
if (isset($aComputed['FunctionalCI']) && is_array($aComputed['FunctionalCI']))
{
foreach($aComputed['FunctionalCI'] as $iKey => $oObject)
{
$oNewLink = new lnkFunctionalCIToTicket();
$oNewLink->Set('functionalci_id', $iKey);
$oNewLink->Set('impact', 'potentially impacted (automatically computed)');
$oToImpact->AddObject($oNewLink);
}
}
if (isset($aComputed['Contact']) && is_array($aComputed['Contact']))
{
foreach($aComputed['Contact'] as $iKey => $oObject)
{
$oNewLink = new lnkContactToTicket();
$oNewLink->Set('contact_id', $iKey);
$oNewLink->Set('role', 'contact automatically computed');
$oToNotify->AddObject($oNewLink);
}
}
parent::OnInsert();
// This method is kept for backward compatibility
// in case a delta redefines it, but you may call
// UpdateImpactedItems directly
$this->UpdateImpactedItems();
}]]></code>
</method>
<method id="OnInsert">
<static>false</static>
<access>protected</access>
<type>Overload-DBObject</type>
<code><![CDATA[ protected function OnInsert()
<code><![CDATA[
protected function OnInsert()
{
parent::OnInsert();
$this->ComputeImpactedItems();
$this->Set('last_update', time());
$this->Set('start_date', time());
@@ -1428,8 +1389,15 @@
<static>false</static>
<access>protected</access>
<type>Overload-DBObject</type>
<code><![CDATA[ protected function OnUpdate()
<code><![CDATA[
protected function OnUpdate()
{
parent::OnUpdate();
$aChanges = $this->ListChanges();
if (array_key_exists('functionalcis_list', $aChanges))
{
$this->UpdateImpactedItems();
}
$this->Set('last_update', time());
$this->UpdateChildRequestLog();
}]]></code>

View File

@@ -17,6 +17,7 @@
<classes>
<class id="Ticket" _delta="define">
<parent>cmdbAbstractObject</parent>
<php_parent><name>_Ticket</name></php_parent>
<properties>
<comment><![CDATA[/**
* Persistent classes for a CMDB
@@ -366,6 +367,17 @@
<default_value/>
<is_null_allowed>true</is_null_allowed>
</field>
<field id="role_code" xsi:type="AttributeEnum">
<values>
<value id="manual">manual</value>
<value id="computed">computed</value>
<value id="do_not_notify">do_not_notify</value>
</values>
<sql>impact_code</sql>
<default_value>manual</default_value>
<is_null_allowed>false</is_null_allowed>
<display_style>list</display_style>
</field>
</fields>
<methods/>
<presentation>
@@ -377,7 +389,7 @@
<item id="contact_id">
<rank>20</rank>
</item>
<item id="role">
<item id="role_code">
<rank>30</rank>
</item>
</items>
@@ -390,7 +402,7 @@
<item id="contact_id">
<rank>20</rank>
</item>
<item id="role">
<item id="role_code">
<rank>30</rank>
</item>
</items>
@@ -403,7 +415,7 @@
<item id="contact_id">
<rank>20</rank>
</item>
<item id="role">
<item id="role_code">
<rank>30</rank>
</item>
</items>
@@ -465,6 +477,17 @@
<default_value/>
<is_null_allowed>true</is_null_allowed>
</field>
<field id="impact_code" xsi:type="AttributeEnum">
<values>
<value id="manual">manual</value>
<value id="computed">computed</value>
<value id="not_impacted">not_impacted</value>
</values>
<sql>impact_code</sql>
<default_value>manual</default_value>
<is_null_allowed>false</is_null_allowed>
<display_style>list</display_style>
</field>
</fields>
<methods/>
<presentation>
@@ -476,7 +499,7 @@
<item id="functionalci_id">
<rank>20</rank>
</item>
<item id="impact">
<item id="impact_code">
<rank>30</rank>
</item>
</items>
@@ -489,7 +512,7 @@
<item id="functionalci_id">
<rank>20</rank>
</item>
<item id="impact">
<item id="impact_code">
<rank>30</rank>
</item>
</items>
@@ -502,7 +525,7 @@
<item id="functionalci_id">
<rank>20</rank>
</item>
<item id="impact">
<item id="impact_code">
<rank>30</rank>
</item>
</items>

View File

@@ -58,22 +58,31 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Class:Ticket/Attribute:workorders_list+' => '',
'Class:Ticket/Attribute:finalclass' => 'Typ',
'Class:Ticket/Attribute:finalclass+' => '',
'Ticket:ImpactAnalysis' => 'Impact Analysis~~',
'Class:lnkContactToTicket' => 'Verknüpfung Kontakt/Ticket',
'Class:lnkContactToTicket+' => '',
'Class:lnkContactToTicket/Attribute:ticket_id' => 'Ticket',
'Class:lnkContactToTicket/Attribute:ticket_id+' => '',
'Class:lnkContactToTicket/Attribute:contact_id' => 'Kontakt',
'Class:lnkContactToTicket/Attribute:contact_id+' => '',
'Class:lnkContactToTicket/Attribute:role' => 'Rolle',
'Class:lnkContactToTicket/Attribute:role' => 'Rolle (Text)',
'Class:lnkContactToTicket/Attribute:role+' => '',
'Class:lnkContactToTicket/Attribute:role_code' => 'Rolle',
'Class:lnkContactToTicket/Attribute:role_code/Value:manual' => 'Added manually~~',
'Class:lnkContactToTicket/Attribute:role_code/Value:computed' => 'Computed~~',
'Class:lnkContactToTicket/Attribute:role_code/Value:do_not_notify' => 'Do not notify~~',
'Class:lnkFunctionalCIToTicket' => 'Verknüpfung FunctionalCI/Ticket',
'Class:lnkFunctionalCIToTicket+' => '',
'Class:lnkFunctionalCIToTicket/Attribute:ticket_id' => 'Ticket',
'Class:lnkFunctionalCIToTicket/Attribute:ticket_id+' => '',
'Class:lnkFunctionalCIToTicket/Attribute:functionalci_id' => 'CI',
'Class:lnkFunctionalCIToTicket/Attribute:functionalci_id+' => '',
'Class:lnkFunctionalCIToTicket/Attribute:impact' => 'Auswirkung',
'Class:lnkFunctionalCIToTicket/Attribute:impact' => 'Auswirkung (Text)',
'Class:lnkFunctionalCIToTicket/Attribute:impact+' => '',
'Class:lnkFunctionalCIToTicket/Attribute:impact_code' => 'Auswirkung',
'Class:lnkFunctionalCIToTicket/Attribute:impact_code/Value:manual' => 'Added manually~~',
'Class:lnkFunctionalCIToTicket/Attribute:impact_code/Value:computed' => 'Computed~~',
'Class:lnkFunctionalCIToTicket/Attribute:impact_code/Value:not_impacted' => 'Not impacted~~',
'Class:WorkOrder' => 'Arbeitsauftrag',
'Class:WorkOrder+' => '',
'Class:WorkOrder/Attribute:name' => 'Name',

View File

@@ -82,6 +82,7 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:Ticket/Attribute:workorders_list+' => 'All the work orders for this ticket',
'Class:Ticket/Attribute:finalclass' => 'Type',
'Class:Ticket/Attribute:finalclass+' => '',
'Ticket:ImpactAnalysis' => 'Impact Analysis',
));
@@ -100,8 +101,12 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:lnkContactToTicket/Attribute:contact_id+' => '',
'Class:lnkContactToTicket/Attribute:contact_email' => 'Contact Email',
'Class:lnkContactToTicket/Attribute:contact_email+' => '',
'Class:lnkContactToTicket/Attribute:role' => 'Role',
'Class:lnkContactToTicket/Attribute:role' => 'Role (text)',
'Class:lnkContactToTicket/Attribute:role+' => '',
'Class:lnkContactToTicket/Attribute:role_code' => 'Role',
'Class:lnkContactToTicket/Attribute:role_code/Value:manual' => 'Added manually',
'Class:lnkContactToTicket/Attribute:role_code/Value:computed' => 'Computed',
'Class:lnkContactToTicket/Attribute:role_code/Value:do_not_notify' => 'Do not notify',
));
//
@@ -119,8 +124,12 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:lnkFunctionalCIToTicket/Attribute:functionalci_id+' => '',
'Class:lnkFunctionalCIToTicket/Attribute:functionalci_name' => 'CI Name',
'Class:lnkFunctionalCIToTicket/Attribute:functionalci_name+' => '',
'Class:lnkFunctionalCIToTicket/Attribute:impact' => 'Impact',
'Class:lnkFunctionalCIToTicket/Attribute:impact' => 'Impact (text)',
'Class:lnkFunctionalCIToTicket/Attribute:impact+' => '',
'Class:lnkFunctionalCIToTicket/Attribute:impact_code' => 'Impact',
'Class:lnkFunctionalCIToTicket/Attribute:impact_code/Value:manual' => 'Added manually',
'Class:lnkFunctionalCIToTicket/Attribute:impact_code/Value:computed' => 'Computed',
'Class:lnkFunctionalCIToTicket/Attribute:impact_code/Value:not_impacted' => 'Not impacted',
));

View File

@@ -69,6 +69,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:Ticket/Attribute:workorders_list+' => '',
'Class:Ticket/Attribute:finalclass' => 'Type',
'Class:Ticket/Attribute:finalclass+' => '',
'Ticket:ImpactAnalysis' => 'Analyse d\'Impact',
));
@@ -87,8 +88,12 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:lnkContactToTicket/Attribute:contact_id+' => '',
'Class:lnkContactToTicket/Attribute:contact_email' => 'Email Contact',
'Class:lnkContactToTicket/Attribute:contact_email+' => '',
'Class:lnkContactToTicket/Attribute:role' => 'Rôle',
'Class:lnkContactToTicket/Attribute:role' => 'Rôle (texte)',
'Class:lnkContactToTicket/Attribute:role+' => '',
'Class:lnkContactToTicket/Attribute:role_code' => 'Rôle',
'Class:lnkContactToTicket/Attribute:role_code/Value:manual' => 'Ajouté manuellement',
'Class:lnkContactToTicket/Attribute:role_code/Value:computed' => 'Calculé',
'Class:lnkContactToTicket/Attribute:role_code/Value:do_not_notify' => 'Ne pas notifier',
));
//
@@ -106,8 +111,12 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:lnkFunctionalCIToTicket/Attribute:functionalci_id+' => '',
'Class:lnkFunctionalCIToTicket/Attribute:functionalci_name' => 'Nom CI',
'Class:lnkFunctionalCIToTicket/Attribute:functionalci_name+' => '',
'Class:lnkFunctionalCIToTicket/Attribute:impact' => 'Impact',
'Class:lnkFunctionalCIToTicket/Attribute:impact' => 'Impact (texte)',
'Class:lnkFunctionalCIToTicket/Attribute:impact+' => '',
'Class:lnkFunctionalCIToTicket/Attribute:impact_code' => 'Impact',
'Class:lnkFunctionalCIToTicket/Attribute:impact_code/Value:manual' => 'Ajouté manuellement',
'Class:lnkFunctionalCIToTicket/Attribute:impact_code/Value:computed' => 'Calculé',
'Class:lnkFunctionalCIToTicket/Attribute:impact_code/Value:not_impacted' => 'Non impacté',
));

View File

@@ -127,4 +127,124 @@ class ResponseTicketTTR extends ResponseTicketSLT implements iMetricComputer
}
}
class _Ticket extends cmdbAbstractObject
{
public function UpdateImpactedItems()
{
$oContactsSet = $this->Get('contacts_list');
$oCIsSet = $this->Get('functionalcis_list');
$aCIsToImpactCode = array();
$aSources = array();
$aExcluded = array();
$oCIsSet->Rewind();
while ($oLink = $oCIsSet->Fetch())
{
$iKey = $oLink->Get('functionalci_id');
$aCIsToImpactCode[$iKey] = $oLink->Get('impact_code');
if ($oLink->Get('impact_code') == 'manual')
{
$oObj = MetaModel::GetObject('FunctionalCI', $iKey);
$aSources[$iKey] = $oObj;
}
else if ($oLink->Get('impact_code') == 'not_impacted')
{
$oObj = MetaModel::GetObject('FunctionalCI', $iKey);
$aExcluded[$iKey] = $oObj;
}
}
$aContactsToRoleCode = array();
$oContactsSet->Rewind();
while ($oLink = $oContactsSet->Fetch())
{
$iKey = $oLink->Get('contact_id');
$aContactsToRoleCode[$iKey] = $oLink->Get('role_code');
if ($oLink->Get('role_code') == 'do_not_notify')
{
$oObj = MetaModel::GetObject('Contact', $iKey);
$aExcluded[$iKey] = $oObj;
}
}
$oNewCIsSet = DBObjectSet::FromScratch('lnkFunctionalCIToTicket');
foreach($aCIsToImpactCode as $iKey => $sImpactCode)
{
if ($sImpactCode != 'computed')
{
$oNewLink = new lnkFunctionalCIToTicket();
$oNewLink->Set('functionalci_id', $iKey);
$oNewLink->Set('impact_code', $sImpactCode);
$oNewCIsSet->AddObject($oNewLink);
}
}
$oNewContactsSet = DBObjectSet::FromScratch('lnkContactToTicket');
foreach($aContactsToRoleCode as $iKey => $sImpactCode)
{
if ($sImpactCode != 'computed')
{
$oNewLink = new lnkContactToTicket();
$oNewLink->Set('contact_id', $iKey);
$oNewLink->Set('role_code', $sImpactCode);
$oNewContactsSet->AddObject($oNewLink);
}
}
$oContactsSet = DBObjectSet::FromScratch('lnkContactToTicket');
$oGraph = MetaModel::GetRelatedObjectsDown('impacts', $aSources, 10, true /* bEnableRedundancy */, $aExcluded);
$oIterator = new RelationTypeIterator($oGraph, 'Node');
foreach ($oIterator as $oNode)
{
if ( ($oNode instanceof RelationObjectNode) && ($oNode->GetProperty('is_reached')) && (!$oNode->GetProperty('source')))
{
$oObj = $oNode->GetProperty('object');
$iKey = $oObj->GetKey();
$sRootClass = MetaModel::GetRootClass(get_class($oObj));
switch ($sRootClass)
{
case 'FunctionalCI':
// Only link FunctionalCIs which are not already linked to the ticket
if (!array_key_exists($iKey, $aCIsToImpactCode) || ($aCIsToImpactCode[$iKey] != 'not_impacted'))
{
$oNewLink = new lnkFunctionalCIToTicket();
$oNewLink->Set('functionalci_id', $iKey);
$oNewLink->Set('impact_code', 'computed');
$oNewCIsSet->AddObject($oNewLink);
}
break;
case 'Contact':
// Only link Contacts which are not already linked to the ticket
if (!array_key_exists($iKey, $aContactsToRoleCode) || ($aCIsToImpactCode[$iKey] != 'do_not_notify'))
{
$oNewLink = new lnkContactToTicket();
$oNewLink->Set('contact_id', $iKey);
$oNewLink->Set('role_code', 'computed');
$oNewContactsSet->AddObject($oNewLink);
}
break;
}
}
}
$this->Set('functionalcis_list', $oNewCIsSet);
$this->Set('contacts_list', $oNewContactsSet);
}
public function DisplayBareRelations(WebPage $oPage, $bEditMode = false)
{
parent::DisplayBareRelations($oPage, $bEditMode);
if (!$bEditMode)
{
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/fraphael.js');
$oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/jquery.contextMenu.css');
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.contextMenu.js');
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/simple_graph.js');
$oPage->AddAjaxTab(Dict::S('Ticket:ImpactAnalysis'), utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=ticket_impact&class='.get_class($this).'&id='.$this->GetKey(), true);
}
}
}
?>

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__,
'itop-tickets/2.1.0',
'itop-tickets/2.2.0',
array(
// Identification
//
@@ -22,8 +22,8 @@ SetupWebPage::AddModule(
// Components
//
'datamodel' => array(
'model.itop-tickets.php',
'main.itop-tickets.php',
'model.itop-tickets.php',
),
'data.struct' => array(
// 'data.struct.ta-actions.xml',

View File

@@ -777,6 +777,7 @@ Wenn Aktionen mit Trigger verknüpft sind, bekommt jede Aktion eine Auftragsnumm
'UI:RelationGroups' => 'Gruppen',
'UI:RelationGroupNumber_N' => 'Gruppe #%1$d~~',
'UI:Relation:ExportAsPDF' => 'Export as PDF...~~',
'UI:RelationOption:GroupingThreshold' => 'Grouping threshold~~',
'UI:Relation:ExportAsDocument' => 'Export as Document...~~',
'UI:Relation:DrillDown' => 'Details...~~',
'UI:Relation:PDFExportOptions' => 'PDF Export Options~~',

View File

@@ -970,6 +970,7 @@ When associated with a trigger, each action is given an "order" number, specifyi
'UI:ElementsDisplayed' => 'Filtering',
'UI:RelationGroupNumber_N' => 'Group #%1$d',
'UI:Relation:ExportAsPDF' => 'Export as PDF...',
'UI:RelationOption:GroupingThreshold' => 'Grouping threshold',
'UI:Relation:ExportAsDocument' => 'Export as Document...',
'UI:Relation:DrillDown' => 'Details...',
'UI:Relation:PDFExportOptions' => 'PDF Export Options',

View File

@@ -812,10 +812,11 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
'UI:RelationGroups' => 'Groupes',
'UI:ElementsDisplayed' => 'Filtrage',
'UI:RelationGroupNumber_N' => 'Groupe n°%1$d',
'UI:Relation:ExportAsPDF' => 'Exportation en PDF...',
'UI:Relation:ExportAsDocument' => 'Exportation comme Document...',
'UI:Relation:ExportAsPDF' => 'Exporter en PDF...',
'UI:RelationOption:GroupingThreshold' => 'Seuil de groupage',
'UI:Relation:ExportAsDocument' => 'Exporter comme Document...',
'UI:Relation:DrillDown' => 'Détails...',
'UI:Relation:PDFExportOptions' => 'Options de l\'Exportation PDF',
'UI:Relation:PDFExportOptions' => 'Options de l\'export en PDF',
'UI:Relation:Key' => 'Légende',
'UI:Relation:Comments' => 'Commentaires',
'UI:RelationOption:Title' => 'Titre',

View File

@@ -16,13 +16,24 @@ $(function()
align: 'center',
'vertical-align': 'middle',
source_url: null,
sources: {},
excluded: {},
export_as_pdf: null,
page_format: { label: 'Page Format:', values: { A3: 'A3', A4: 'A4', Letter: 'Letter' }, 'default': 'A4'},
page_orientation: { label: 'Page Orientation:', values: { P: 'Portait', L: 'Landscape' }, 'default': 'L' },
labels: { export_pdf_title: 'PDF Export Options', cancel: 'Cancel', 'export': 'Export', title: 'Document Title', include_list: 'Include the list of objects', comments: 'Comments' },
labels: {
export_pdf_title: 'PDF Export Options',
cancel: 'Cancel', 'export': 'Export',
title: 'Document Title',
include_list: 'Include the list of objects',
comments: 'Comments',
grouping_threshold: 'Grouping Threshold',
refresh: 'Refresh'
},
export_as_document: null,
drill_down: null,
excluded: []
grouping_threshold: 10,
excluded_classes: []
},
// the constructor
@@ -352,7 +363,10 @@ $(function()
_create_toolkit_menu: function()
{
var sPopupMenuId = 'tk_graph'+this.element.attr('id');
var sHtml = '<div class="itop_popup toolkit_menu graph" style="font-size: 12px;" id="'+sPopupMenuId+'"><ul><li><img src="../images/toolkit_menu.png"><ul>';
var sHtml = '<div class="graph_config">';
var sId = this.element.attr('id');
sHtml += this.options.labels.grouping_threshold+'&nbsp;<input type="text" name="g" value="'+this.options.grouping_threshold+'" id="'+sId+'_grouping_threshold" size="2">&nbsp;<button type="button" id="'+sId+'_refresh_btn">'+this.options.labels.refresh+'</button>';
sHtml += '<div class="itop_popup toolkit_menu graph" style="font-size: 12px;" id="'+sPopupMenuId+'"><ul><li><img src="../images/toolkit_menu.png"><ul>';
if (this.options.export_as_pdf != null)
{
sHtml += '<li><a href="#" id="'+sPopupMenuId+'_pdf">'+this.options.export_as_pdf.label+'</a></li>';
@@ -363,16 +377,17 @@ $(function()
}
//sHtml += '<li><a href="#" id="'+sPopupMenuId+'_reload">Refresh</a></li>';
sHtml += '</ul></li></ul></div>';
sHtml += '</div>';
this.element.before(sHtml);
$('#'+sPopupMenuId).popupmenu();
$('#'+sPopupMenuId+'>ul').popupmenu();
var me = this;
$('#'+sPopupMenuId+'_pdf').click(function() { me.export_as_pdf(); });
$('#'+sPopupMenuId+'_document').click(function() { me.export_as_document(); });
$('#'+sPopupMenuId+'_reload').click(function() { me.reload(); });
$('#'+sId+'_grouping_threshold').spinner({ min: 2});
$('#'+sId+'_refresh_btn').button().click(function() { me.reload(); });
},
_build_context_menus: function()
{
@@ -400,13 +415,16 @@ $(function()
{
case 'group':
var sGroupIndex = oNode.group_index;
oResult = {
callback: function(key, options) {
var me = $('.itop-simple-graph').data('itopSimple_graph'); // need a live value
me.show_group('relation_group_'+sGroupIndex);
},
items: { 'show': {name: me.options.drill_down.label } }
};
if( $('#relation_group_'+sGroupIndex).length > 0)
{
oResult = {
callback: function(key, options) {
var me = $('.itop-simple-graph').data('itopSimple_graph'); // need a live value
me.show_group('relation_group_'+sGroupIndex);
},
items: { 'show': {name: me.options.drill_down.label } }
};
}
break;
case 'icon':
@@ -438,10 +456,25 @@ $(function()
oPositions[this.aNodes[k].id] = {x: this.aNodes[k].x, y: this.aNodes[k].y };
}
var sHtmlForm = '<div id="PDFExportDlg'+this.element.attr('id')+'"><form id="graph_'+this.element.attr('id')+'_export_as_pdf" target="_blank" action="'+this.options.export_as_pdf.url+'" method="post">';
sHtmlForm += '<input type="hidden" name="g" value="'+this.options.grouping_threshold+'">';
sHtmlForm += '<input type="hidden" name="positions" value="">';
for(k in this.options.excluded)
for(k in this.options.excluded_classes)
{
sHtmlForm += '<input type="hidden" name="excluded[]" value="'+this.options.excluded[k]+'">';
sHtmlForm += '<input type="hidden" name="excluded_classes[]" value="'+this.options.excluded_classes[k]+'">';
}
for(var k1 in this.options.sources)
{
for(var k2 in this.options.sources[k1])
{
sHtmlForm += '<input type="hidden" name="sources['+k1+'][]" value="'+this.options.sources[k1][k2]+'">';
}
}
for(var k1 in this.options.excluded)
{
for(var k2 in this.options.excluded[k1])
{
sHtmlForm += '<input type="hidden" name="excluded['+k1+'][]" value="'+this.options.excluded[k1][k2]+'">';
}
}
sHtmlForm += '<table>';
sHtmlForm += '<tr><td>'+this.options.page_format.label+'</td><td><select name="p">';
@@ -507,9 +540,16 @@ $(function()
{
this.options.load_from_url = sUrl;
var me = this;
var sId = this.element.attr('id');
this.options.grouping_threshold = $('#'+sId+'_grouping_threshold').val();
if (this.options.grouping_threshold < 2)
{
this.options.grouping_threshold = 2;
$('#'+sId+'_grouping_threshold').val(this.options.grouping_threshold);
}
this.element.closest('.ui-tabs').tabs({ heightStyle: "fill" });
this.oPaper.rect(0, 0, this.element.width(), this.element.height()).attr({fill: '#000', opacity: 0.4, 'stroke-width': 0});
$.post(sUrl, {excluded: this.options.excluded }, function(data) {
$.post(sUrl, {excluded_classes: this.options.excluded_classes, g: this.options.grouping_threshold, sources: this.options.sources, excluded: this.options.excluded }, function(data) {
me.load(data);
}, 'json');
},

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -262,163 +262,11 @@ function DisplayNavigatorGroupTab($oP, $aGroups, $sRelation, $oObj)
}
}
function DisplayNavigatorGraphicsTab($oP, $aResults, $oDisplayGraph, $sClass, $id, $sRelation, $oAppContext, $bDirectionDown)
{
$oP->SetCurrentTab(Dict::S('UI:RelationshipGraph'));
$oP->add("<div id=\"ds_flash\" class=\"SearchDrawer\" style=\"display:none;\">\n");
$oP->add_ready_script(
<<<EOF
$( "#tabbedContent_0" ).tabs({ heightStyle: "fill" });
$("#dh_flash").click( function() {
$("#ds_flash").slideToggle('normal', function() { $("#ds_flash").parent().resize(); } );
$("#dh_flash").toggleClass('open');
});
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");
$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
{
$oDisplayGraph->InitFromGraphviz();
$sExportAsPdfURL = '';
if (extension_loaded('gd'))
{
$sExportAsPdfURL = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=relation_pdf&relation='.$sRelation.'&direction='.($bDirectionDown ? 'down' : 'up').'&class='.$sClass.'&id='.$id.'&g='.$iGroupingThreshold;
}
$oAppcontext = new ApplicationContext();
$sContext = $oAppContext->GetForLink();
$sDrillDownURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=details&class=%1$s&id=%2$s&'.$sContext;
$sExportAsDocumentURL = '';
$sLoadFromURL = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=relation_json&relation='.$sRelation.'&direction='.($bDirectionDown ? 'down' : 'up').'&class='.$sClass.'&id='.$id.'&g='.$iGroupingThreshold;
$sId = 'graph';
$oP->add('<div id="'.$sId.'" class="simple-graph"></div>');
$aParams = array(
'source_url' => $sLoadFromURL,
'export_as_pdf' => array('url' => $sExportAsPdfURL, 'label' => Dict::S('UI:Relation:ExportAsPDF')),
//'export_as_document' => array('url' => $sExportAsDocumentURL, 'label' => Dict::S('UI:Relation:ExportAsDocument')),
'drill_down' => array('url' => $sDrillDownURL, 'label' => Dict::S('UI:Relation:DrillDown')),
'labels' => array(
'export_pdf_title' => Dict::S('UI:Relation:PDFExportOptions'),
'export' => Dict::S('UI:Button:Export'),
'cancel' => Dict::S('UI:Button:Cancel'),
'title' => Dict::S('UI:RelationOption:Title'),
'include_list' => Dict::S('UI:RelationOption:IncludeList'),
'comments' => Dict::S('UI:RelationOption:Comments'),
),
'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'),
),
),
);
$oP->add_ready_script("$('#$sId').simple_graph(".json_encode($aParams).");");
}
catch(Exception $e)
{
$oP->add('<div>'.$e->getMessage().'</div>');
}
$oP->add_script(
<<<EOF
function DoReload()
{
$('#ReloadMovieBtn').button('disable');
try
{
var aExcluded = [];
$('input[name^=excluded]').each( function() {
if (!$(this).prop('checked'))
{
aExcluded.push($(this).val());
}
} );
$('#graph').simple_graph('option', {excluded: aExcluded});
$('#graph').simple_graph('reload');
}
catch(err)
{
alert(err);
}
}
EOF
);
$oP->add_ready_script(
<<<EOF
var ajax_request = null;
$('#ReloadMovieBtn').button().button('disable');
function UpdateImpactedObjects(sClass, iId, sRelation)
{
var class_name = sClass; //$('select[name=class_name]').val();
if (class_name != '')
{
$('#impacted_objects').block();
// Make sure that we cancel any pending request before issuing another
// since responses may arrive in arbitrary order
if (ajax_request != null)
{
ajax_request.abort();
ajax_request = null;
}
ajax_request = $.get(GetAbsoluteUrlAppRoot()+'pages/xml.navigator.php', { 'class': sClass, id: iId, relation: sRelation, format: 'html' },
function(data)
{
alert('Not yet implemented');
$('#impacted_objects').unblock();
}
);
}
}
EOF
);
}
/***********************************************************************************
*
* Main user interface page, starts here
* Main user interface page starts here
*
* ***********************************************************************************/
***********************************************************************************/
try
{
require_once('../approot.inc.php');
@@ -1608,26 +1456,11 @@ EOF
}
$aResults = array();
$aGroups = array();
$iGroupIdx = 0;
$oIterator = new RelationTypeIterator($oRelGraph, 'Node');
foreach($oIterator as $oNode)
{
$oObj = $oNode->GetProperty('object'); // Some nodes (Redundancy Nodes and Group) do not contain an object
if ($oObj)
{
$sObjClass = get_class($oObj);
if (!array_key_exists($sObjClass, $aResults))
{
$aResults[$sObjClass] = array();
}
$aResults[$sObjClass][] = $oObj;
}
}
$aResults = $oRelGraph->GetObjectsByClass();
$oDisplayGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down'));
$aGroups = array();
$iGroupIdx = 0;
$oIterator = new RelationTypeIterator($oDisplayGraph, 'Node');
foreach($oIterator as $oNode)
{
@@ -1646,12 +1479,14 @@ EOF
if ($sFirstTab == 'list')
{
DisplayNavigatorListTab($oP, $aResults, $sRelation, $oObj);
DisplayNavigatorGraphicsTab($oP, $aResults, $oDisplayGraph, $sClass, $id, $sRelation, $oAppContext, ($sDirection == 'down'));
$oP->SetCurrentTab(Dict::S('UI:RelationshipGraph'));
$oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, ($sDirection == 'down'));
DisplayNavigatorGroupTab($oP, $aGroups, $sRelation, $oObj);
}
else
{
DisplayNavigatorGraphicsTab($oP, $aResults, $oDisplayGraph, $sClass, $id, $sRelation, $oAppContext, ($sDirection == 'down'));
$oP->SetCurrentTab(Dict::S('UI:RelationshipGraph'));
$oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, ($sDirection == 'down'));
DisplayNavigatorListTab($oP, $aResults, $sRelation, $oObj);
DisplayNavigatorGroupTab($oP, $aGroups, $sRelation, $oObj);
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2014 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -1731,9 +1731,7 @@ EOF
require_once(APPROOT.'core/simplegraph.class.inc.php');
require_once(APPROOT.'core/relationgraph.class.inc.php');
require_once(APPROOT.'core/displayablegraph.class.inc.php');
$sClass = utils::ReadParam('class', '', false, 'class');
$id = (int)utils::ReadParam('id', 0);
$sRelation = utils::ReadParam('relation', 'impact');
$sRelation = utils::ReadParam('relation', 'impacts');
$sDirection = utils::ReadParam('direction', 'down');
$iGroupingThreshold = utils::ReadParam('g', 5);
@@ -1741,7 +1739,7 @@ EOF
$sPageOrientation = utils::ReadParam('o', 'L');
$sTitle = utils::ReadParam('title', '', false, 'raw_data');
$sPositions = utils::ReadParam('positions', null, false, 'raw_data');
$aExcluded = utils::ReadParam('excluded', array(), false, 'raw_data');
$aExcludedClasses = utils::ReadParam('excluded_classes', array(), false, 'raw_data');
$bIncludeList = (bool)utils::ReadParam('include_list', false);
$sComments = utils::ReadParam('comments', '', false, 'raw_data');
$aPositions = null;
@@ -1750,26 +1748,52 @@ EOF
$aPositions = json_decode($sPositions, true);
}
$oObj = MetaModel::GetObject($sClass, $id);
// Get the list of source objects
$aSources = utils::ReadParam('sources', array(), false, 'raw_data');
$aSourceObjects = array();
foreach($aSources as $sClass => $aIDs)
{
$oSearch = new DBObjectSearch($sClass);
$oSearch->AddCondition('id', $aIDs, 'IN');
$oSet = new DBObjectSet($oSearch);
while($oObj = $oSet->Fetch())
{
$aSourceObjects[] = $oObj;
}
}
// Get the list of excluded objects
$aExcluded = utils::ReadParam('excluded', array(), false, 'raw_data');
$aExcludedObjects = array();
foreach($aExcluded as $sClass => $aIDs)
{
$oSearch = new DBObjectSearch($sClass);
$oSearch->AddCondition('id', $aIDs, 'IN');
$oSet = new DBObjectSet($oSearch);
while($oObj = $oSet->Fetch())
{
$aExcludedObjects[] = $oObj;
}
}
$iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth', 20);
$aSourceObjects = array($oObj);
if ($sDirection == 'up')
{
$oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth);
}
else
{
$oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth);
$oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects);
}
// Remove excluded classes from the graph
if (count($aExcluded) > 0)
if (count($aExcludedClasses) > 0)
{
$oIterator = new RelationTypeIterator($oRelGraph, 'Node');
foreach($oIterator as $oNode)
{
$oObj = $oNode->GetProperty('object');
if ($oObj && in_array(get_class($oObj), $aExcluded))
if ($oObj && in_array(get_class($oObj), $aExcludedClasses))
{
$oRelGraph->FilterNode($oNode);
}
@@ -1798,13 +1822,7 @@ EOF
}
// First page is the graph
$oGraph->RenderAsPDF($oPage, $sComments);
/*
// Experimental QR code at the bottom left of the page...
$sUrl = "r=$sRelation&d=$sDirection&c=$sClass&id=$id";
$oPdf = $oPage->get_tcpdf();
$aMargins = $oPdf->getMargins();
$oPdf->write2DBarcode($sUrl, 'QRCODE,H', $aMargins['left'], $oPdf->getPageHeight() - $aMargins['bottom'] - 35, 25, 25, array(), 'N');
*/
if ($bIncludeList)
{
// Then the lists of objects (one table per finalclass)
@@ -1864,39 +1882,64 @@ EOF
require_once(APPROOT.'core/simplegraph.class.inc.php');
require_once(APPROOT.'core/relationgraph.class.inc.php');
require_once(APPROOT.'core/displayablegraph.class.inc.php');
$sClass = utils::ReadParam('class', '', false, 'class');
$id = utils::ReadParam('id', 0);
$sRelation = utils::ReadParam('relation', 'impact');
$sRelation = utils::ReadParam('relation', 'impacts');
$sDirection = utils::ReadParam('direction', 'down');
$iGroupingThreshold = utils::ReadParam('g', 5);
$sPositions = utils::ReadParam('positions', null, false, 'raw_data');
$aExcluded = utils::ReadParam('excluded', array(), false, 'raw_data');
$aExcludedClasses = utils::ReadParam('excluded_classes', array(), false, 'raw_data');
$aPositions = null;
if ($sPositions != null)
{
$aPositions = json_decode($sPositions, true);
}
$oObj = MetaModel::GetObject($sClass, $id);
// Get the list of source objects
$aSources = utils::ReadParam('sources', array(), false, 'raw_data');
$aSourceObjects = array();
foreach($aSources as $sClass => $aIDs)
{
$oSearch = new DBObjectSearch($sClass);
$oSearch->AddCondition('id', $aIDs, 'IN');
$oSet = new DBObjectSet($oSearch);
while($oObj = $oSet->Fetch())
{
$aSourceObjects[] = $oObj;
}
}
// Get the list of excluded objects
$aExcluded = utils::ReadParam('excluded', array(), false, 'raw_data');
$aExcludedObjects = array();
foreach($aExcluded as $sClass => $aIDs)
{
$oSearch = new DBObjectSearch($sClass);
$oSearch->AddCondition('id', $aIDs, 'IN');
$oSet = new DBObjectSet($oSearch);
while($oObj = $oSet->Fetch())
{
$aExcludedObjects[] = $oObj;
}
}
// Compute the graph
$iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth', 20);
$aSourceObjects = array($oObj);
if ($sDirection == 'up')
{
$oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth);
}
else
{
$oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth);
$oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects);
}
// Remove excluded classes from the graph
if (count($aExcluded) > 0)
if (count($aExcludedClasses) > 0)
{
$oIterator = new RelationTypeIterator($oRelGraph, 'Node');
foreach($oIterator as $oNode)
{
$oObj = $oNode->GetProperty('object');
if ($oObj && in_array(get_class($oObj), $aExcluded))
if ($oObj && in_array(get_class($oObj), $aExcludedClasses))
{
$oRelGraph->FilterNode($oNode);
}
@@ -1913,6 +1956,57 @@ EOF
$oPage->SetContentType('application/json');
break;
case 'ticket_impact':
require_once(APPROOT.'core/simplegraph.class.inc.php');
require_once(APPROOT.'core/relationgraph.class.inc.php');
require_once(APPROOT.'core/displayablegraph.class.inc.php');
$sRelation = utils::ReadParam('relation', 'impacts');
$sDirection = utils::ReadParam('direction', 'down');
$iGroupingThreshold = utils::ReadParam('g', 5);
$sClass = utils::ReadParam('class', '', false, 'class');
$sAttCode = utils::ReadParam('attcode', 'functionalcis_list');
$sImpactAttCode = utils::ReadParam('impact_attcode', 'impact_code');
$sImpactAttCodeValue = utils::ReadParam('impact_attcode_value', 'manual');
$iId = (int)utils::ReadParam('id', 0, false, 'integer');
// Get the list of source objects
$oTicket = MetaModel::GetObject($sClass, $iId);
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
$oExtKeyToRemote = MetaModel::GetAttributeDef($oAttDef->GetLinkedClass(), $sExtKeyToRemote);
$sRemoteClass = $oExtKeyToRemote->GetTargetClass();
$oSet = $oTicket->Get($sAttCode);
$aSourceObjects = array();
$aExcludedObjects = array();
while($oLnk = $oSet->Fetch())
{
if ($oLnk->Get($sImpactAttCode) == 'manual')
{
$aSourceObjects[] = MetaModel::GetObject($sRemoteClass, $oLnk->Get($sExtKeyToRemote));
}
if ($oLnk->Get($sImpactAttCode) == 'not_impacted')
{
$aExcludedObjects[] = MetaModel::GetObject($sRemoteClass, $oLnk->Get($sExtKeyToRemote));
}
}
// Compute the graph
$iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth', 20);
if ($sDirection == 'up')
{
$oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth);
}
else
{
$oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, $aExcludedObjects);
}
$aResults = $oRelGraph->GetObjectsByClass();
$oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down'));
$oAppContext = new ApplicationContext();
$oGraph->Display($oPage, $aResults, $sRelation, $oAppContext, $aExcludedObjects);
break;
default:
$oPage->p("Invalid query.");
}