1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348 |
- <?php
- // Copyright (C) 2015 Combodo SARL
- //
- // This file is part of iTop.
- //
- // iTop is free software; you can redistribute it and/or modify
- // it under the terms of the GNU Affero General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // iTop is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Affero General Public License for more details.
- //
- // You should have received a copy of the GNU Affero General Public License
- // along with iTop. If not, see <http://www.gnu.org/licenses/>
- /**
- * Special kind of Graph for producing some nice output
- *
- * @copyright Copyright (C) 2015 Combodo SARL
- * @license http://opensource.org/licenses/AGPL-3.0
- */
- class DisplayableNode extends GraphNode
- {
- public $x;
- public $y;
-
- /**
- * Create a new node inside a graph
- * @param SimpleGraph $oGraph
- * @param string $sId The unique identifier of this node inside the graph
- * @param number $x Horizontal position
- * @param number $y Vertical position
- */
- public function __construct(SimpleGraph $oGraph, $sId, $x = 0, $y = 0)
- {
- parent::__construct($oGraph, $sId);
- $this->x = $x;
- $this->y = $y;
- $this->bFiltered = false;
- }
- public function GetIconURL()
- {
- return $this->GetProperty('icon_url', '');
- }
-
- public function GetLabel()
- {
- return $this->GetProperty('label', $this->sId);
- }
-
- public function GetWidth()
- {
- return max(32, 5*strlen($this->GetProperty('label'))); // approximation of the text's bounding box
- }
-
- public function GetHeight()
- {
- return 32;
- }
-
- public function Distance2(DisplayableNode $oNode)
- {
- $dx = $this->x - $oNode->x;
- $dy = $this->y - $oNode->y;
-
- $d2 = $dx*$dx + $dy*$dy - $this->GetHeight()*$this->GetHeight();
- if ($d2 < 40)
- {
- $d2 = 40;
- }
- return $d2;
- }
-
- public function Distance(DisplayableNode $oNode)
- {
- return sqrt($this->Distance2($oNode));
- }
-
- public function GetForRaphael($aContextDefs)
- {
- $aNode = array();
- $aNode['shape'] = 'icon';
- $aNode['icon_url'] = $this->GetIconURL();
- $aNode['width'] = 32;
- $aNode['source'] = ($this->GetProperty('source') == true);
- $aNode['obj_class'] = get_class($this->GetProperty('object'));
- $aNode['obj_key'] = $this->GetProperty('object')->GetKey();
- $aNode['sink'] = ($this->GetProperty('sink') == true);
- $aNode['x'] = $this->x;
- $aNode['y']= $this->y;
- $aNode['label'] = $this->GetLabel();
- $aNode['id'] = $this->GetId();
- $fOpacity = ($this->GetProperty('is_reached') ? 1 : 0.4);
- $aNode['icon_attr'] = array('opacity' => $fOpacity);
- $aNode['text_attr'] = array('opacity' => $fOpacity);
- $aNode['tooltip'] = $this->GetTooltip($aContextDefs);
- $aNode['context_icons'] = array();
- $aContextRootCauses = $this->GetProperty('context_root_causes');
- if (!is_null($aContextRootCauses))
- {
- foreach($aContextRootCauses as $key => $aObjects)
- {
- $aNode['context_icons'][] = utils::GetAbsoluteUrlModulesRoot().$aContextDefs[$key]['icon'];
- }
- }
- return $aNode;
- }
-
- public function RenderAsPDF(TCPDF $oPdf, DisplayableGraph $oGraph, $fScale, $aContextDefs)
- {
- $Alpha = 1.0;
- $oPdf->SetFillColor(200, 200, 200);
- $oPdf->setAlpha(1);
-
- $sIconUrl = $this->GetProperty('icon_url');
- $sIconPath = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-'.utils::GetCurrentEnvironment().'/', $sIconUrl);
-
- if ($this->GetProperty('source'))
- {
- $oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => array(204, 51, 51)));
- $oPdf->Circle($this->x * $fScale, $this->y * $fScale, 16 * 1.25 * $fScale, 0, 360, 'D');
- }
- else if ($this->GetProperty('sink'))
- {
- $oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => array(51, 51, 204)));
- $oPdf->Circle($this->x * $fScale, $this->y * $fScale, 16 * 1.25 * $fScale, 0, 360, 'D');
- }
-
- if (!$this->GetProperty('is_reached'))
- {
- $sTempImageName = $this->CreateWhiteIcon($oGraph, $sIconPath);
- if ($sTempImageName != null)
- {
- $oPdf->Image($sTempImageName, ($this->x - 16)*$fScale, ($this->y - 16)*$fScale, 32*$fScale, 32*$fScale, 'PNG');
- }
- $Alpha = 0.4;
- $oPdf->setAlpha($Alpha);
- }
-
- $oPdf->Image($sIconPath, ($this->x - 16)*$fScale, ($this->y - 16)*$fScale, 32*$fScale, 32*$fScale);
-
- $aContextRootCauses = $this->GetProperty('context_root_causes');
- if (!is_null($aContextRootCauses))
- {
- $idx = 0;
- foreach($aContextRootCauses as $key => $aObjects)
- {
- $sgn = 2*($idx %2) -1;
- $coef = floor((1+$idx)/2) * $sgn;
- $alpha = $coef*pi()/4 - pi()/2;
- $x = $this->x * $fScale + cos($alpha) * 16*1.25 * $fScale;
- $y = $this->y * $fScale + sin($alpha) * 16*1.25 * $fScale;
- $l = 32 * $fScale / 3;
- $sIconPath = APPROOT.'env-'.utils::GetCurrentEnvironment().'/'.$aContextDefs[$key]['icon'];
- $oPdf->Image($sIconPath, $x - $l/2, $y - $l/2, $l, $l);
- $idx++;
- }
- }
-
- $oPdf->SetFont('dejavusans', '', 24 * $fScale, '', true);
- $width = $oPdf->GetStringWidth($this->GetProperty('label'));
- $height = $oPdf->GetStringHeight(1000, $this->GetProperty('label'));
- $oPdf->setAlpha(0.6 * $Alpha);
- $oPdf->SetFillColor(255, 255, 255);
- $oPdf->SetDrawColor(255, 255, 255);
- $oPdf->Rect($this->x*$fScale - $width/2, ($this->y + 18)*$fScale, $width, $height, 'DF');
- $oPdf->setAlpha($Alpha);
- $oPdf->SetTextColor(0, 0, 0);
- $oPdf->Text($this->x*$fScale - $width/2, ($this->y + 18)*$fScale, $this->GetProperty('label'));
- }
-
- /**
- * Create a "whitened" version of the icon (retaining the transparency) to be used a background for masking the underlying lines
- * @param string $sIconFile The path to the file containing the icon
- * @return NULL|string The path to a temporary file containing the white version of the icon
- */
- protected function CreateWhiteIcon(DisplayableGraph $oGraph, $sIconFile)
- {
- $aInfo = getimagesize($sIconFile);
-
- $im = null;
- switch($aInfo['mime'])
- {
- case 'image/png':
- if (function_exists('imagecreatefrompng'))
- {
- $im = imagecreatefrompng($sIconFile);
- }
- break;
-
- case 'image/gif':
- if (function_exists('imagecreatefromgif'))
- {
- $im = imagecreatefromgif($sIconFile);
- }
- break;
-
- case 'image/jpeg':
- case 'image/jpg':
- if (function_exists('imagecreatefromjpeg'))
- {
- $im = imagecreatefromjpeg($sIconFile);
- }
- break;
-
- default:
- return null;
-
- }
- if($im && imagefilter($im, IMG_FILTER_COLORIZE, 255, 255, 255))
- {
- $sTempImageName = $oGraph->GetTempImageName();
- imagesavealpha($im, true);
- imagepng($im, $sTempImageName);
- imagedestroy($im);
- return $sTempImageName;
- }
- else
- {
- return null;
- }
- }
-
- /**
- * Group together (as a special kind of nodes) all the similar neighbours of the current node
- * @param DisplayableGraph $oGraph
- * @param int $iThresholdCount
- * @param boolean $bDirectionUp
- * @param boolean $bDirectionDown
- */
- public function GroupSimilarNeighbours(DisplayableGraph $oGraph, $iThresholdCount, $bDirectionUp = false, $bDirectionDown = true)
- {
- if ($this->GetProperty('grouped') === true) return;
- $this->SetProperty('grouped', true);
-
- if ($bDirectionDown)
- {
- $aNodesPerClass = array();
- foreach($this->GetOutgoingEdges() as $oEdge)
- {
- $oNode = $oEdge->GetSinkNode();
-
- if ($oNode->GetProperty('class') !== null)
- {
- $sClass = $oNode->GetProperty('class');
- if (($sClass!== null) && (!array_key_exists($sClass, $aNodesPerClass)))
- {
- $aNodesPerClass[$sClass] = array(
- 'reached' => array(
- 'count' => 0,
- 'nodes' => array(),
- 'icon_url' => $oNode->GetProperty('icon_url'),
- ),
- 'not_reached' => array(
- 'count' => 0,
- 'nodes' => array(),
- 'icon_url' => $oNode->GetProperty('icon_url'),
- )
- );
- }
- $sKey = $oNode->GetProperty('is_reached') ? 'reached' : 'not_reached';
- if (!array_key_exists($oNode->GetId(), $aNodesPerClass[$sClass][$sKey]['nodes']))
- {
- $aNodesPerClass[$sClass][$sKey]['nodes'][$oNode->GetId()] = $oNode;
- $aNodesPerClass[$sClass][$sKey]['count'] += (int)$oNode->GetProperty('count', 1);
- }
- }
- else
- {
- $oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
- }
- }
-
- foreach($aNodesPerClass as $sClass => $aDefs)
- {
- foreach($aDefs as $sStatus => $aGroupProps)
- {
- if (count($aGroupProps['nodes']) >= $iThresholdCount)
- {
- $sNewId = $this->GetId().'::'.(($sStatus == 'reached') ? '_reached': '');
- $oNewNode = $oGraph->GetNode($sNewId);
- if ($oNewNode == null)
- {
- $oNewNode = new DisplayableGroupNode($oGraph, $sNewId);
- $oNewNode->SetProperty('label', 'x'.$aGroupProps['count']);
- $oNewNode->SetProperty('icon_url', $aGroupProps['icon_url']);
- $oNewNode->SetProperty('class', $sClass);
- $oNewNode->SetProperty('is_reached', ($sStatus == 'reached'));
- $oNewNode->SetProperty('count', $aGroupProps['count']);
- }
- else
- {
- $oNewNode->SetProperty('count', $oNewNode->GetProperty('count')+$aGroupProps['count']);
- }
-
- try
- {
- $oIncomingEdge = new DisplayableEdge($oGraph, $this->GetId().'-'.$oNewNode->GetId(), $this, $oNewNode);
- }
- catch(Exception $e)
- {
- // Ignore this redundant egde
- }
-
- foreach($aGroupProps['nodes'] as $oNode)
- {
- foreach($oNode->GetIncomingEdges() as $oEdge)
- {
- if ($oEdge->GetSourceNode()->GetId() !== $this->GetId())
- {
- try
- {
- $oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oEdge->GetSourceNode(), $oNewNode);
- }
- catch(Exception $e)
- {
- // ignore this edge
- }
- }
- }
- foreach($oNode->GetOutgoingEdges() as $oEdge)
- {
- $aOutgoing[] = $oEdge->GetSinkNode();
- try
- {
- $oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oNewNode, $oEdge->GetSinkNode());
- }
- catch(Exception $e)
- {
- // ignore this edge
- }
- }
- if ($oGraph->GetNode($oNode->GetId()))
- {
- $oGraph->_RemoveNode($oNode);
- $oNewNode->AddObject($oNode->GetProperty('object'));
- }
- }
- $oNewNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
- }
- else
- {
- foreach($aGroupProps['nodes'] as $oNode)
- {
- $oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
- }
- }
- }
- }
- }
- }
-
- public function GetTooltip($aContextDefs)
- {
- $sHtml = '';
- $oCurrObj = $this->GetProperty('object');
- $sSubClass = get_class($oCurrObj);
- $sHtml .= $oCurrObj->GetHyperlink()."<hr/>";
- $aContextRootCauses = $this->GetProperty('context_root_causes');
- if (!is_null($aContextRootCauses))
- {
- foreach($aContextRootCauses as $key => $aObjects)
- {
- $aContext = $aContextDefs[$key];
- $aRootCauses = array();
- foreach($aObjects as $oRootCause)
- {
- $aRootCauses[] = $oRootCause->GetHyperlink();
- }
- $sHtml .= '<p><img style="max-height: 24px; vertical-align:bottom;" src="'.utils::GetAbsoluteUrlModulesRoot().$aContext['icon'].'" title="'.htmlentities(Dict::S($aContext['dict'])).'"> '.implode(', ', $aRootCauses).'</p>';
- }
- $sHtml .= '<hr/>';
- }
- $sHtml .= '<table><tbody>';
- foreach(MetaModel::GetZListItems($sSubClass, 'list') as $sAttCode)
- {
- $oAttDef = MetaModel::GetAttributeDef($sSubClass, $sAttCode);
- $sHtml .= '<tr><td>'.$oAttDef->GetLabel().': </td><td>'.$oCurrObj->GetAsHtml($sAttCode).'</td></tr>';
- }
- $sHtml .= '</tbody></table>';
- return $sHtml;
- }
- }
- class DisplayableRedundancyNode extends DisplayableNode
- {
- public function GetWidth()
- {
- return 24;
- }
-
- public function GetForRaphael($aContextDefs)
- {
- $aNode = array();
- $aNode['shape'] = 'disc';
- $aNode['icon_url'] = $this->GetIconURL();
- $aNode['source'] = ($this->GetProperty('source') == true);
- $aNode['width'] = $this->GetWidth();
- $aNode['x'] = $this->x;
- $aNode['y']= $this->y;
- $aNode['label'] = $this->GetLabel();
- $aNode['id'] = $this->GetId();
- $fDiscOpacity = ($this->GetProperty('is_reached') ? 1 : 0.2);
- $sColor = ($this->GetProperty('is_reached_count') > $this->GetProperty('threshold')) ? '#c33' : '#999';
- $aNode['disc_attr'] = array('stroke-width' => 3, 'stroke' => '#000', 'fill' => $sColor, 'opacity' => $fDiscOpacity);
- $fTextOpacity = ($this->GetProperty('is_reached') ? 1 : 0.4);
- $aNode['text_attr'] = array('fill' => '#fff', 'opacity' => $fTextOpacity);
- $aNode['tooltip'] = $this->GetTooltip($aContextDefs);
- return $aNode;
- }
- public function RenderAsPDF(TCPDF $oPdf, DisplayableGraph $oGraph, $fScale, $aContextDefs)
- {
- $oPdf->SetAlpha(1);
- if($this->GetProperty('is_reached_count') > $this->GetProperty('threshold'))
- {
- $oPdf->SetFillColor(200, 0, 0);
- }
- else
- {
- $oPdf->SetFillColor(144, 144, 144);
- }
- $oPdf->SetDrawColor(0, 0, 0);
- $oPdf->Circle($this->x*$fScale, $this->y*$fScale, 16*$fScale, 0, 360, 'DF');
- $oPdf->SetTextColor(255, 255, 255);
- $oPdf->SetFont('dejavusans', '', 28 * $fScale, '', true);
- $sLabel = (string)$this->GetProperty('label');
- $width = $oPdf->GetStringWidth($sLabel, 'dejavusans', 'B', 24*$fScale);
- $height = $oPdf->GetStringHeight(1000, $sLabel);
- $xPos = (float)$this->x*$fScale - $width/2;
- $yPos = (float)$this->y*$fScale - $height/2;
-
- $oPdf->SetXY(($this->x - 16)*$fScale, ($this->y - 16)*$fScale);
-
- $oPdf->Cell(32*$fScale, 32*$fScale, $sLabel, 0, 0, 'C', 0, '', 0, false, 'T', 'C');
- }
-
- /**
- * @see DisplayableNode::GroupSimilarNeighbours()
- */
- public function GroupSimilarNeighbours(DisplayableGraph $oGraph, $iThresholdCount, $bDirectionUp = false, $bDirectionDown = true)
- {
- parent::GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
-
- if ($bDirectionUp)
- {
- $aNodesPerClass = array();
- foreach($this->GetIncomingEdges() as $oEdge)
- {
- $oNode = $oEdge->GetSourceNode();
-
- if (($oNode->GetProperty('class') !== null) && (!$oNode->GetProperty('is_reached')))
- {
- $sClass = $oNode->GetProperty('class');
- if (!array_key_exists($sClass, $aNodesPerClass))
- {
- $aNodesPerClass[$sClass] = array('reached' => array(), 'not_reached' => array());
- }
- $aNodesPerClass[$sClass][$oNode->GetProperty('is_reached') ? 'reached' : 'not_reached'][] = $oNode;
- }
- else
- {
- //$oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
- }
- }
- foreach($aNodesPerClass as $sClass => $aDefs)
- {
- foreach($aDefs as $sStatus => $aNodes)
- {
- if (count($aNodes) >= $iThresholdCount)
- {
- $oNewNode = new DisplayableGroupNode($oGraph, '-'.$this->GetId().'::'.$sClass.'/'.$sStatus);
- $oNewNode->SetProperty('label', 'x'.count($aNodes));
- $oNewNode->SetProperty('icon_url', $aNodes[0]->GetProperty('icon_url'));
- $oNewNode->SetProperty('is_reached', $aNodes[0]->GetProperty('is_reached'));
-
- $oOutgoingEdge = new DisplayableEdge($oGraph, '-'.$this->GetId().'-'.$oNewNode->GetId().'/'.$sStatus, $oNewNode, $this);
-
- foreach($aNodes as $oNode)
- {
- foreach($oNode->GetIncomingEdges() as $oEdge)
- {
- $oNewEdge = new DisplayableEdge($oGraph, '-'.$oEdge->GetId().'::'.$sClass, $oEdge->GetSourceNode(), $oNewNode);
- }
- foreach($oNode->GetOutgoingEdges() as $oEdge)
- {
- if ($oEdge->GetSinkNode()->GetId() !== $this->GetId())
- {
- $aOutgoing[] = $oEdge->GetSinkNode();
- $oNewEdge = new DisplayableEdge($oGraph, '-'.$oEdge->GetId().'::'.$sClass.'/'.$sStatus, $oNewNode, $oEdge->GetSinkNode());
- }
- }
- $oGraph->_RemoveNode($oNode);
- $oNewNode->AddObject($oNode->GetProperty('object'));
- }
- //$oNewNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
- }
- else
- {
- foreach($aNodes as $oNode)
- {
- //$oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
- }
- }
- }
- }
- }
- }
-
- public function GetTooltip($aContextDefs)
- {
- $sHtml = '';
- $sHtml .= Dict::S('UI:RelationTooltip:Redundancy')."<hr>";
- $sHtml .= '<table><tbody>';
- $sHtml .= "<tr><td>".Dict::Format('UI:RelationTooltip:ImpactedItems_N_of_M' , $this->GetProperty('is_reached_count'), $this->GetProperty('min_up') + $this->GetProperty('threshold'))."</td></tr>";
- $sHtml .= "<tr><td>".Dict::Format('UI:RelationTooltip:CriticalThreshold_N_of_M' , $this->GetProperty('threshold'), $this->GetProperty('min_up') + $this->GetProperty('threshold'))."</td></tr>";
- $sHtml .= '</tbody></table>';
- return $sHtml;
- }
- }
- class DisplayableEdge extends GraphEdge
- {
- public function RenderAsPDF(TCPDF $oPdf, DisplayableGraph $oGraph, $fScale, $aContextDefs)
- {
- $xStart = $this->GetSourceNode()->x * $fScale;
- $yStart = $this->GetSourceNode()->y * $fScale;
- $xEnd = $this->GetSinkNode()->x * $fScale;
- $yEnd = $this->GetSinkNode()->y * $fScale;
-
- $bReached = ($this->GetSourceNode()->GetProperty('is_reached') && $this->GetSinkNode()->GetProperty('is_reached'));
-
- $oPdf->setAlpha(1);
- if ($bReached)
- {
- $aColor = array(100, 100, 100);
- }
- else
- {
- $aColor = array(200, 200, 200);
- }
- $oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => $aColor));
- $oPdf->Line($xStart, $yStart, $xEnd, $yEnd);
-
-
- $vx = $xEnd - $xStart;
- $vy = $yEnd - $yStart;
- $l = sqrt($vx*$vx + $vy*$vy);
- $vx = $vx / $l;
- $vy = $vy / $l;
- $ux = -$vy;
- $uy = $vx;
- $lPos = max($l/2, $l - 40*$fScale);
- $iArrowSize = 5*$fScale;
-
- $x = $xStart + $lPos * $vx;
- $y = $yStart + $lPos * $vy;
- $oPdf->Line($x, $y, $x + $iArrowSize * ($ux-$vx), $y + $iArrowSize * ($uy-$vy));
- $oPdf->Line($x, $y, $x - $iArrowSize * ($ux+$vx), $y - $iArrowSize * ($uy+$vy));
- }
- }
- class DisplayableGroupNode extends DisplayableNode
- {
- protected $aObjects;
-
- public function __construct(SimpleGraph $oGraph, $sId, $x = 0, $y = 0)
- {
- parent::__construct($oGraph, $sId, $x, $y);
- $this->aObjects = array();
- }
-
- public function AddObject(DBObject $oObj)
- {
- $this->aObjects[$oObj->GetKey()] = $oObj;
- }
-
- public function GetObjects()
- {
- return $this->aObjects;
- }
-
- public function GetWidth()
- {
- return 50;
- }
- public function GetForRaphael($aContextDefs)
- {
- $aNode = array();
- $aNode['shape'] = 'group';
- $aNode['icon_url'] = $this->GetIconURL();
- $aNode['source'] = ($this->GetProperty('source') == true);
- $aNode['width'] = $this->GetWidth();
- $aNode['x'] = $this->x;
- $aNode['y']= $this->y;
- $aNode['label'] = $this->GetLabel();
- $aNode['id'] = $this->GetId();
- $aNode['group_index'] = $this->GetProperty('group_index'); // if supplied
- $fDiscOpacity = ($this->GetProperty('is_reached') ? 1 : 0.2);
- $fTextOpacity = ($this->GetProperty('is_reached') ? 1 : 0.4);
- $aNode['icon_attr'] = array('opacity' => $fTextOpacity);
- $aNode['disc_attr'] = array('stroke-width' => 3, 'stroke' => '#000', 'fill' => '#fff', 'opacity' => $fDiscOpacity);
- $aNode['text_attr'] = array('fill' => '#000', 'opacity' => $fTextOpacity);
- $aNode['tooltip'] = $this->GetTooltip($aContextDefs);
- return $aNode;
- }
-
- public function RenderAsPDF(TCPDF $oPdf, DisplayableGraph $oGraph, $fScale, $aContextDefs)
- {
- $bReached = $this->GetProperty('is_reached');
- $oPdf->SetFillColor(255, 255, 255);
- if ($bReached)
- {
- $aBorderColor = array(100, 100, 100);
- }
- else
- {
- $aBorderColor = array(200, 200, 200);
- }
- $oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => $aBorderColor));
-
- $sIconUrl = $this->GetProperty('icon_url');
- $sIconPath = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-'.utils::GetCurrentEnvironment().'/', $sIconUrl);
- $oPdf->SetAlpha(1);
- $oPdf->Circle($this->x*$fScale, $this->y*$fScale, $this->GetWidth() / 2 * $fScale, 0, 360, 'DF');
-
- if ($bReached)
- {
- $oPdf->SetAlpha(1);
- }
- else
- {
- $oPdf->SetAlpha(0.4);
- }
- $oPdf->Image($sIconPath, ($this->x - 17)*$fScale, ($this->y - 17)*$fScale, 16*$fScale, 16*$fScale);
- $oPdf->Image($sIconPath, ($this->x + 1)*$fScale, ($this->y - 17)*$fScale, 16*$fScale, 16*$fScale);
- $oPdf->Image($sIconPath, ($this->x -8)*$fScale, ($this->y +1)*$fScale, 16*$fScale, 16*$fScale);
- $oPdf->SetFont('dejavusans', '', 24 * $fScale, '', true);
- $width = $oPdf->GetStringWidth($this->GetProperty('label'));
- $oPdf->SetTextColor(0, 0, 0);
- $oPdf->Text($this->x*$fScale - $width/2, ($this->y + 25)*$fScale, $this->GetProperty('label'));
- }
-
- public function GetTooltip($aContextDefs)
- {
- $sHtml = '';
- $iGroupIdx = $this->GetProperty('group_index');
- $sHtml .= Dict::Format('UI:RelationGroupNumber_N', (1+$iGroupIdx));
- return $sHtml;
- }
- }
- /**
- * A Graph that can be displayed interactively using Raphael JS or saved as a PDF document
- */
- class DisplayableGraph extends SimpleGraph
- {
- 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()
- {
- $sNewTempName = tempnam(APPROOT.'data', 'img-');
- $this->aTempImages[] = $sNewTempName;
- return $sNewTempName;
- }
-
- public function __destruct()
- {
- foreach($this->aTempImages as $sTempFile)
- {
- @unlink($sTempFile);
- }
- }
-
- /**
- * Build a DisplayableGraph from a RelationGraph
- * @param RelationGraph $oGraph
- * @param number $iGroupingThreshold
- * @param string $bDirectionDown
- * @return DisplayableGraph
- */
- 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)
- {
- switch(get_class($oNode))
- {
- 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);
- }
- $oNewNode->SetProperty('class', $sClass);
- $oNewNode->SetProperty('object', $oObj);
- $oNewNode->SetProperty('icon_url', $oObj->GetIcon(false));
- $oNewNode->SetProperty('label', $oObj->GetRawName());
- $oNewNode->SetProperty('is_reached', $bDirectionDown ? $oNode->GetProperty('is_reached') : true); // When going "up" is_reached does not matter
- $oNewNode->SetProperty('is_reached_allowed', $oNode->GetProperty('is_reached_allowed'));
- $oNewNode->SetProperty('context_root_causes', $oNode->GetProperty('context_root_causes'));
- break;
-
- default:
- $oNewNode = new DisplayableRedundancyNode($oNewGraph, $oNode->GetId(), 0, 0);
- $iNbReached = (is_null($oNode->GetProperty('is_reached_count'))) ? 0 : $oNode->GetProperty('is_reached_count');
- $oNewNode->SetProperty('label', $iNbReached."/".($oNode->GetProperty('min_up') + $oNode->GetProperty('threshold')));
- $oNewNode->SetProperty('min_up', $oNode->GetProperty('min_up'));
- $oNewNode->SetProperty('threshold', $oNode->GetProperty('threshold'));
- $oNewNode->SetProperty('is_reached_count', $iNbReached);
- $oNewNode->SetProperty('is_reached', true);
- }
- }
- $oEdgesIter = new RelationTypeIterator($oGraph, 'Edge');
- foreach($oEdgesIter as $oEdge)
- {
- $oSourceNode = $oNewGraph->GetNode($oEdge->GetSourceNode()->GetId());
- $oSinkNode = $oNewGraph->GetNode($oEdge->GetSinkNode()->GetId());
- $oNewEdge = new DisplayableEdge($oNewGraph, $oEdge->GetId(), $oSourceNode, $oSinkNode);
- }
-
- // Remove duplicate edges between two nodes
- $oEdgesIter = new RelationTypeIterator($oNewGraph, 'Edge');
- $aEdgeKeys = array();
- foreach($oEdgesIter as $oEdge)
- {
- $sSourceId = $oEdge->GetSourceNode()->GetId();
- $sSinkId = $oEdge->GetSinkNode()->GetId();
- if ($sSourceId == $sSinkId)
- {
- // Remove self referring edges
- $oNewGraph->_RemoveEdge($oEdge);
- }
- else
- {
- $sKey = $sSourceId.'//'.$sSinkId;
- if (array_key_exists($sKey, $aEdgeKeys))
- {
- // Remove duplicate edges
- $oNewGraph->_RemoveEdge($oEdge);
- }
- else
- {
- $aEdgeKeys[$sKey] = true;
- }
- }
- }
-
- $oNodesIter = new RelationTypeIterator($oNewGraph, 'Node');
- foreach($oNodesIter as $oNode)
- {
- if ($oNode->GetProperty('source'))
- {
- $oNode->GroupSimilarNeighbours($oNewGraph, $iGroupingThreshold, true, true);
- }
- }
- // Groups numbering
- $oIterator = new RelationTypeIterator($oNewGraph, 'Node');
- $iGroupIdx = 0;
- foreach($oIterator as $oNode)
- {
- if ($oNode instanceof DisplayableGroupNode)
- {
- $aGroups[] = $oNode->GetObjects();
- $oNode->SetProperty('group_index', $iGroupIdx);
- $iGroupIdx++;
- }
- }
-
- // Remove duplicate edges between two nodes
- $oEdgesIter = new RelationTypeIterator($oNewGraph, 'Edge');
- $aEdgeKeys = array();
- foreach($oEdgesIter as $oEdge)
- {
- $sSourceId = $oEdge->GetSourceNode()->GetId();
- $sSinkId = $oEdge->GetSinkNode()->GetId();
- if ($sSourceId == $sSinkId)
- {
- // Remove self referring edges
- $oNewGraph->_RemoveEdge($oEdge);
- }
- else
- {
- $sKey = $sSourceId.'//'.$sSinkId;
- if (array_key_exists($sKey, $aEdgeKeys))
- {
- // Remove duplicate edges
- $oNewGraph->_RemoveEdge($oEdge);
- }
- else
- {
- $aEdgeKeys[$sKey] = true;
- }
- }
- }
- return $oNewGraph;
- }
-
- /**
- * Initializes the positions by rendering using Graphviz in xdot format
- * and parsing the output.
- * @throws Exception
- */
- public function InitFromGraphviz()
- {
- $sDot = $this->DumpAsXDot();
- if (strpos($sDot, 'digraph') === false)
- {
- throw new Exception($sDot);
- }
-
- $aChunks = explode(";", $sDot);
- foreach($aChunks as $sChunk)
- {
- if(preg_match('/"([^"]+)".+pos="([0-9\\.]+),([0-9\\.]+)"/ms', $sChunk, $aMatches))
- {
- $sId = $aMatches[1];
- $xPos = $aMatches[2];
- $yPos = $aMatches[3];
-
- $oNode = $this->GetNode($sId);
- $oNode->x = (float)$xPos;
- $oNode->y = (float)$yPos;
- }
- }
- }
-
- public function GetBoundingBox()
- {
- $xMin = null;
- $xMax = null;
- $yMin = null;
- $yMax = null;
- $oIterator = new RelationTypeIterator($this, 'Node');
- foreach($oIterator as $sId => $oNode)
- {
- if ($xMin === null) // First element in the loop
- {
- $xMin = $oNode->x - $oNode->GetWidth();
- $xMax = $oNode->x + $oNode->GetWidth();
- $yMin = $oNode->y - $oNode->GetHeight();
- $yMax = $oNode->y + $oNode->GetHeight();
- }
- else
- {
- $xMin = min($xMin, $oNode->x - $oNode->GetWidth() / 2);
- $xMax = max($xMax, $oNode->x + $oNode->GetWidth() / 2);
- $yMin = min($yMin, $oNode->y - $oNode->GetHeight() / 2);
- $yMax = max($yMax, $oNode->y + $oNode->GetHeight() / 2);
- }
- }
-
- return array('xmin' => $xMin, 'xmax' => $xMax, 'ymin' => $yMin, 'ymax' => $yMax);
- }
-
- function Translate($dx, $dy)
- {
- $oIterator = new RelationTypeIterator($this, 'Node');
- foreach($oIterator as $sId => $oNode)
- {
- $oNode->x += $dx;
- $oNode->y += $dy;
- }
- }
-
- public function UpdatePositions($aPositions)
- {
- foreach($aPositions as $sNodeId => $aPos)
- {
- $oNode = $this->GetNode($sNodeId);
- if ($oNode != null)
- {
- $oNode->x = $aPos['x'];
- $oNode->y = $aPos['y'];
- }
- }
- }
- /**
- * Renders as JSON string suitable for loading into the simple_graph widget
- */
- function GetAsJSON($sContextKey)
- {
- $aContextDefs = $this->GetContextDefinitions($sContextKey, false);
-
- $aData = array('nodes' => array(), 'edges' => array());
- $iGroupIdx = 0;
- $oIterator = new RelationTypeIterator($this, 'Node');
- foreach($oIterator as $sId => $oNode)
- {
- if ($oNode instanceof DisplayableGroupNode)
- {
- $aGroups[] = $oNode->GetObjects();
- $oNode->SetProperty('group_index', $iGroupIdx);
- $iGroupIdx++;
- }
- $aData['nodes'][] = $oNode->GetForRaphael($aContextDefs);
- }
-
- $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');
- $aData['edges'][] = $aEdge;
- }
-
- return json_encode($aData);
- }
-
- /**
- * Renders the graph in a PDF document: centered in the current page
- * @param PDFPage $oPage The PDFPage representing the PDF document to draw into
- * @param string $sComments An optional comment to display next to the graph (HTML entities will be escaped, \n replaced by <br/>)
- * @param string $sContextKey The key to fetch the queries in the configuration. Example: itop-tickets/relation_context/UserRequest/impacts/down
- * @param float $xMin Left coordinate of the bounding box to display the graph
- * @param float $xMax Right coordinate of the bounding box to display the graph
- * @param float $yMin Top coordinate of the bounding box to display the graph
- * @param float $yMax Bottom coordinate of the bounding box to display the graph
- */
- function RenderAsPDF(PDFPage $oPage, $sComments = '', $sContextKey, $xMin = -1, $xMax = -1, $yMin = -1, $yMax = -1)
- {
- $aContextDefs = $this->GetContextDefinitions($sContextKey, false); // No need to develop the parameters
- $oPdf = $oPage->get_tcpdf();
-
- $aBB = $this->GetBoundingBox();
- $this->Translate(-$aBB['xmin'], -$aBB['ymin']);
-
- $aMargins = $oPdf->getMargins();
-
- if ($xMin == -1)
- {
- $xMin = $aMargins['left'];
- }
- if ($xMax == -1)
- {
- $xMax = $oPdf->getPageWidth() - $aMargins['right'];
- }
- if ($yMin == -1)
- {
- $yMin = $aMargins['top'];
- }
- if ($yMax == -1)
- {
- $yMax = $oPdf->getPageHeight() - $aMargins['bottom'];
- }
-
- $fBreakMargin = $oPdf->getBreakMargin();
- $oPdf->SetAutoPageBreak(false);
- $aRemainingArea = $this->RenderKey($oPdf, $sComments, $xMin, $yMin, $xMax, $yMax, $aContextDefs);
- $xMin = $aRemainingArea['xmin'];
- $xMax = $aRemainingArea['xmax'];
- $yMin = $aRemainingArea['ymin'];
- $yMax = $aRemainingArea['ymax'];
-
- //$oPdf->Rect($xMin, $yMin, $xMax - $xMin, $yMax - $yMin, 'D', array(), array(225, 50, 50));
-
- $fPageW = $xMax - $xMin;
- $fPageH = $yMax - $yMin;
-
- $w = $aBB['xmax'] - $aBB['xmin'];
- $h = $aBB['ymax'] - $aBB['ymin'] + 10; // Extra space for the labels which may appear "below" the icons
-
- $fScale = min($fPageW / $w, $fPageH / $h);
- $dx = ($fPageW - $fScale * $w) / 2;
- $dy = ($fPageH - $fScale * $h) / 2;
-
- $this->Translate(($xMin + $dx)/$fScale, ($yMin + $dy)/$fScale);
- $oIterator = new RelationTypeIterator($this, 'Edge');
- $iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
- foreach($oIterator as $sId => $oEdge)
- {
- set_time_limit($iLoopTimeLimit);
- $oEdge->RenderAsPDF($oPdf, $this, $fScale, $aContextDefs);
- }
- $oIterator = new RelationTypeIterator($this, 'Node');
- foreach($oIterator as $sId => $oNode)
- {
- set_time_limit($iLoopTimeLimit);
- $oNode->RenderAsPDF($oPdf, $this, $fScale, $aContextDefs);
- }
- $oIterator = new RelationTypeIterator($this, 'Node');
- $oPdf->SetAutoPageBreak(true, $fBreakMargin);
- $oPdf->SetAlpha(1);
- }
-
- /**
- * Renders (in PDF) the key (legend) of the graphics vertically to the left of the specified zone (xmin,ymin, xmax,ymax),
- * and the comment (if any) at the bottom of the page. Returns the position of remaining area.
- * @param TCPDF $oPdf
- * @param string $sComments
- * @param float $xMin
- * @param float $yMin
- * @param float $xMax
- * @param float $yMax
- * @param hash $aContextDefs
- * @return hash An array ('xmin' => , 'xmax' => ,'ymin' => , 'ymax' => ) of the remaining available area to paint the graph
- */
- protected function RenderKey(TCPDF $oPdf, $sComments, $xMin, $yMin, $xMax, $yMax, $aContextDefs)
- {
- $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')) - $fIconSize, $oPdf->GetStringWidth(Dict::S('UI:Relation:Comments')) - $fIconSize);
- $aClasses = array();
- $aIcons = array();
- $aContexts = array();
- $aContextIcons = array();
- $oPdf->SetFont('dejavusans', '', $fFontSize, '', true);
- foreach($oIterator as $sId => $oNode)
- {
- if ($sClass = $oNode->GetProperty('class'))
- {
- if (!array_key_exists($sClass, $aClasses))
- {
- $sClassLabel = MetaModel::GetName($sClass);
- $width = $oPdf->GetStringWidth($sClassLabel);
- $fMaxWidth = max($width, $fMaxWidth);
- $aClasses[$sClass] = $sClassLabel;
- $sIconUrl = $oNode->GetProperty('icon_url');
- $sIconPath = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-'.utils::GetCurrentEnvironment().'/', $sIconUrl);
- $aIcons[$sClass] = $sIconPath;
- }
- }
- $aContextRootCauses = $oNode->GetProperty('context_root_causes');
- if (!is_null($aContextRootCauses))
- {
- foreach($aContextRootCauses as $key => $aObjects)
- {
- $aContexts[$key] = Dict::S($aContextDefs[$key]['dict']);
- $aContextIcons[$key] = APPROOT.'env-'.utils::GetCurrentEnvironment().'/'.$aContextDefs[$key]['icon'];
- }
- }
- }
- $oPdf->SetXY($xMin + $fPadding, $yMin + $fPadding);
- $yPos = $yMin + $fPadding;
- $oPdf->SetFillColor(225, 225, 225);
- $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 + $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;
- }
- foreach($aContexts as $key => $sLabel)
- {
- $oPdf->SetX($xMin + $fIconSize + $fPadding);
- $oPdf->Cell(0, $fIconSize + 2*$fPadding, $sLabel, 0 /* border */, 1 /* ln */);
- $oPdf->Image($aContextIcons[$key], $xMin+1+$fIconSize*0.125, $yPos+$fIconSize*0.125, $fIconSize*0.75, $fIconSize*0.75);
- $yPos += $fIconSize + 2*$fPadding;
- }
- $oPdf->Rect($xMin, $yMin, $fMaxWidth + $fIconSize + 3*$fPadding, $yMax - $yMin, 'D');
-
- if ($sComments != '')
- {
- // Draw the comment text (surrounded by a rectangle)
- $xPos = $xMin + $fMaxWidth + $fIconSize + 4*$fPadding;
- $w = $xMax - $xPos - 2*$fPadding;
- $iNbLines = 1;
- $sText = '<p>'.str_replace("\n", '<br/>', htmlentities($sComments, ENT_QUOTES, 'UTF-8'), $iNbLines).'</p>';
- $fLineHeight = $oPdf->getStringHeight($w, $sText);
- $h = (1+$iNbLines) * $fLineHeight;
- $yPos = $yMax - 2*$fPadding - $h;
- $oPdf->writeHTMLCell($w, $h, $xPos + $fPadding, $yPos + $fPadding, $sText, 0 /* border */, 1 /* ln */);
- $oPdf->Rect($xPos, $yPos, $w + 2*$fPadding, $h + 2*$fPadding, 'D');
- $yMax = $yPos - $fPadding;
- }
-
- return array('xmin' => $xMin + $fMaxWidth + $fIconSize + 4*$fPadding, 'xmax' => $xMax, 'ymin' => $yMin, 'ymax' => $yMax);
- }
-
- /**
- * Get the context definitions from the parameters / configuration. The format of the "key" string is:
- * <module>/relation_context/<class>/<relation>/<direction>
- * The values will be retrieved for the given class and all its parents and merged together as a single array.
- * Entries with an invalid query are removed from the list.
- * @param string $sContextKey The key to fetch the queries in the configuration. Example: itop-tickets/relation_context/UserRequest/impacts/down
- * @param bool $bDevelopParams Whether or not to substitute the parameters inside the queries with the supplied "context params"
- * @param array $aContextParams Arguments for the queries (via ToArgs()) if $bDevelopParams == true
- * @return multitype:multitype:string
- */
- public function GetContextDefinitions($sContextKey, $bDevelopParams = true, $aContextParams = array())
- {
- $aLevels = explode('/', $sContextKey);
- $sLeafClass = $aLevels[2];
-
- $aRelationContext = MetaModel::GetConfig()->GetModuleSetting($aLevels[0], $aLevels[1], array());
- $aContextDefs = array();
- foreach(MetaModel::EnumParentClasses($sLeafClass, ENUM_PARENT_CLASSES_ALL) as $sClass)
- {
- if (isset($aRelationContext[$sClass][$aLevels[3]][$aLevels[4]]['items']))
- {
- $aContextDefs = array_merge($aContextDefs, $aRelationContext[$sClass][$aLevels[3]][$aLevels[4]]['items']);
- }
- }
-
- // Check if the queries are valid
- foreach($aContextDefs as $sKey => $sDefs)
- {
- $sOQL = $aContextDefs[$sKey]['oql'];
- try
- {
- // Expand the parameters. If anything goes wrong, then the query is considered as invalid and removed from the list
- $oSearch = DBObjectSearch::FromOQL($sOQL);
- $aContextDefs[$sKey]['oql'] = $oSearch->ToOQL($bDevelopParams, $aContextParams);
- }
- catch(Exception $e)
- {
- unset($aContextDefs[$sKey]);
- }
- }
- return $aContextDefs;
- }
-
- /**
- * 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 = $this->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");
- $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\"> ".MetaModel::GetClassIcon($sSubClass)." $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']);
- }
-
- $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 = 'max-width: 17cm; margin-left:auto; margin-right:auto;';
- }
- $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
-
- 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
- );
- }
-
- }
|