Files
iTop/pages/schema.php
Romain Quetiez 7dbbb1c299 #439 Record and display changes in the link sets (ex: Members of a team)
#439 Make sure that changes made by a plugin get recorded
+ simplified the change tracking for the plugins. Simply call DBObject::DBInsert (resp. Update and Delete) and the change will be recorded for the current page. This is compatible with the old (not mandatory anymore) way that was requiring DBInsertTracked APIs (resp. Update, Delete).

SVN:trunk[2236]
2012-10-08 12:17:56 +00:00

554 lines
20 KiB
PHP

<?php
// Copyright (C) 2010 Combodo SARL
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 3 of the License.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
/**
* Presentation of the data model
*
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
* @author Denis Flaven <denis.flaven@combodo.com>
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
require_once('../approot.inc.php');
require_once(APPROOT.'/application/application.inc.php');
require_once(APPROOT.'/application/itopwebpage.class.inc.php');
require_once(APPROOT.'/application/startup.inc.php');
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (must be admin)
/**
* Helper for this page -> link to a class
*/
function MakeClassHLink($sClass, $sContext)
{
return "<a href=\"schema.php?operation=details_class&class=$sClass{$sContext}\" title=\"".MetaModel::GetClassDescription($sClass)."\">".MetaModel::GetName($sClass)."</a>";
}
/**
* Helper for this page -> link to a class
*/
function MakeRelationHLink($sRelCode, $sContext)
{
$sDesc = MetaModel::GetRelationDescription($sRelCode);
return "<a href=\"schema.php?operation=details_relation&relcode=$sRelCode{$sContext}\" title=\"$sDesc\">".$sRelCode."</a>";
}
/**
* Helper for the global list and the details of a given class
*/
function DisplaySubclasses($oPage, $sClass, $sContext)
{
$aChildClasses = MetaModel::EnumChildClasses($sClass);
if (count($aChildClasses) != 0)
{
$oPage->add("<ul>\n");
$aOrderedClasses = array();
foreach($aChildClasses as $sClassName)
{
// Skip indirect childs, they will be handled somewhere else
if (MetaModel::GetParentPersistentClass($sClassName) == $sClass)
{
$aOrderedClasses[$sClassName] = MetaModel::GetName($sClassName);
}
}
// Sort on the display name
asort($aOrderedClasses);
foreach($aOrderedClasses as $sClassName => $sDisplayName)
{
// Skip indirect childs, they will be handled somewhere else
if (MetaModel::GetParentPersistentClass($sClassName) == $sClass)
{
$oPage->add("<li>".MakeClassHLink($sClassName, $sContext)."\n");
DisplaySubclasses($oPage, $sClassName, $sContext);
$oPage->add("</li>\n");
}
}
$oPage->add("</ul>\n");
}
}
/**
* Helper for the global list and the details of a given class
*/
function DisplayReferencingClasses($oPage, $sClass, $sContext)
{
$bSkipLinkingClasses = false;
$aRefs = MetaModel::EnumReferencingClasses($sClass, $bSkipLinkingClasses);
if (count($aRefs) != 0)
{
$oPage->add("<ul>\n");
foreach ($aRefs as $sRemoteClass => $aRemoteKeys)
{
foreach ($aRemoteKeys as $sExtKeyAttCode => $oExtKeyAttDef)
{
$oPage->add("<li>".Dict::Format('UI:Schema:Class_ReferencingClasses_From_By', $sClass, MakeClassHLink($sRemoteClass, $sContext), $sExtKeyAttCode)."</li>\n");
}
}
$oPage->add("</ul>\n");
}
}
/**
* Helper for the global list and the details of a given class
*/
function DisplayLinkingClasses($oPage, $sClass, $sContext)
{
$bSkipLinkingClasses = false;
$aRefs = MetaModel::EnumLinkingClasses($sClass);
if (count($aRefs) != 0)
{
$oPage->add("<ul>\n");
foreach ($aRefs as $sLinkClass => $aRemoteClasses)
{
foreach($aRemoteClasses as $sExtKeyAttCode => $sRemoteClass)
{
$oPage->add("<li>".Dict::Format('UI:Schema:Class_IsLinkedTo_Class_Via_ClassAndAttribute', $sClass, MakeClassHLink($sRemoteClass, $sContext), MakeClassHLink($sLinkClass, $sContext), $sExtKeyAttCode));
}
}
$oPage->add("</ul>\n");
}
}
/**
* Helper for the global list and the details of a given class
*/
function DisplayRelatedClassesBestInClass($oPage, $sClass, $iLevels = 20, &$aVisitedClasses = array(), $bSubtree = true, $sContext)
{
if ($iLevels <= 0) return;
$iLevels--;
if (array_key_exists($sClass, $aVisitedClasses)) return;
$aVisitedClasses[$sClass] = true;
if ($bSubtree) $oPage->add("<ul class=\"treeview\">\n");
foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass)
{
DisplayRelatedClassesBestInClass($oPage, $sParentClass, $iLevels, $aVisitedClasses, false, $sContext);
}
////$oPage->add("<div style=\"background-color:#ccc; border: 1px dashed #333;\">");
foreach (MetaModel::EnumReferencedClasses($sClass) as $sExtKeyAttCode => $sRemoteClass)
{
$sVisited = (array_key_exists($sRemoteClass, $aVisitedClasses)) ? " ..." : "";
if (MetaModel::GetAttributeOrigin($sClass, $sExtKeyAttCode) == $sClass)
{
$oPage->add("<li>$sClass| <em>$sExtKeyAttCode</em> =&gt;".MakeClassHLink($sRemoteClass, $sContext)."$sVisited</li>\n");
DisplayRelatedClassesBestInClass($oPage, $sRemoteClass, $iLevels, $aVisitedClasses, true, $sContext);
}
}
foreach (MetaModel::EnumReferencingClasses($sClass) as $sRemoteClass => $aRemoteKeys)
{
foreach ($aRemoteKeys as $sExtKeyAttCode => $oExtKeyAttDef)
{
$sVisited = (array_key_exists($sRemoteClass, $aVisitedClasses)) ? " ..." : "";
$oPage->add("<li>$sClass| &lt;=".MakeClassHLink($sRemoteClass, $sContext)."::<em>$sExtKeyAttCode</em>$sVisited</li>\n");
DisplayRelatedClassesBestInClass($oPage, $sRemoteClass, $iLevels, $aVisitedClasses, true, $sContext);
}
}
////$oPage->add("</div>");
if ($bSubtree) $oPage->add("</ul>\n");
}
/**
* Helper for the list of classes related to the given class
*/
function DisplayRelatedClasses($oPage, $sClass, $sContext)
{
$oPage->add("<h3>".Dict::Format('UI:Schema:Links:1-n', $sClass)."</h3>\n");
DisplayReferencingClasses($oPage, $sClass, $sContext);
$oPage->add("<h3>".Dict::Format('UI:Schema:Links:n-n', $sClass)."</h3>\n");
DisplayLinkingClasses($oPage, $sClass, $sContext);
$oPage->add("<h3>".Dict::S('UI:Schema:Links:All')."</h3>\n");
$aEmpty = array();
DisplayRelatedClassesBestInClass($oPage, $sClass, 4, $aEmpty, true, $sContext);
}
/**
* Helper for the lifecycle details of a given class
*/
function DisplayLifecycle($oPage, $sClass)
{
$sStateAttCode = MetaModel::GetStateAttributeCode($sClass);
if (empty($sStateAttCode))
{
$oPage->p(Dict::S('UI:Schema:NoLifeCyle'));
}
else
{
$aStates = MetaModel::EnumStates($sClass);
$aStimuli = MetaModel::EnumStimuli($sClass);
$oPage->add("<img src=\"".utils::GetAbsoluteUrlAppRoot()."pages/graphviz.php?class=$sClass\">\n");
$oPage->add("<h3>".Dict::S('UI:Schema:LifeCycleTransitions')."</h3>\n");
$oPage->add("<ul>\n");
foreach ($aStates as $sStateCode => $aStateDef)
{
$sStateLabel = MetaModel::GetStateLabel($sClass, $sStateCode);
$sStateDescription = MetaModel::GetStateDescription($sClass, $sStateCode);
$oPage->add("<li title=\"code: $sStateCode\">$sStateLabel <span style=\"color:grey;\">($sStateCode) $sStateDescription</span></li>\n");
$oPage->add("<ul>\n");
foreach(MetaModel::EnumTransitions($sClass, $sStateCode) as $sStimulusCode => $aTransitionDef)
{
$sStimulusLabel = $aStimuli[$sStimulusCode]->GetLabel();
$sTargetStateLabel = MetaModel::GetStateLabel($sClass, $aTransitionDef['target_state']);
if (count($aTransitionDef['actions']) > 0)
{
$sActions = " <em>(".implode(', ', $aTransitionDef['actions']).")</em>";
}
else
{
$sActions = "";
}
$oPage->add("<li><span style=\"color:red;font-weight=bold;\">$sStimulusLabel</span> =&gt; $sTargetStateLabel $sActions</li>\n");
}
$oPage->add("</ul>\n");
}
$oPage->add("</ul>\n");
$oPage->add("<h3>".Dict::S('UI:Schema:LifeCyleAttributeOptions')."</h3>\n");
$oPage->add("<ul>\n");
foreach ($aStates as $sStateCode => $aStateDef)
{
$sStateLabel = MetaModel::GetStateLabel($sClass, $sStateCode);
$sStateDescription = MetaModel::GetStateDescription($sClass, $sStateCode);
$oPage->add("<li title=\"code: $sStateCode\">$sStateLabel <span style=\"color:grey;\">($sStateCode) $sStateDescription</span></li>\n");
if (count($aStates[$sStateCode]['attribute_list']) > 0)
{
$oPage->add("<ul>\n");
foreach($aStates[$sStateCode]['attribute_list'] as $sAttCode => $iOptions)
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$sAttLabel = $oAttDef->GetLabel();
$aOptions = array();
if ($iOptions & OPT_ATT_HIDDEN) $aOptions[] = Dict::S('UI:Schema:LifeCycleHiddenAttribute');
if ($iOptions & OPT_ATT_READONLY) $aOptions[] = Dict::S('UI:Schema:LifeCycleReadOnlyAttribute');
if ($iOptions & OPT_ATT_MANDATORY) $aOptions[] = Dict::S('UI:Schema:LifeCycleMandatoryAttribute');
if ($iOptions & OPT_ATT_MUSTCHANGE) $aOptions[] = Dict::S('UI:Schema:LifeCycleAttributeMustChange');
if ($iOptions & OPT_ATT_MUSTPROMPT) $aOptions[] = Dict::S('UI:Schema:LifeCycleAttributeMustPrompt');
if (count($aOptions))
{
$sOptions = implode(', ', $aOptions);
}
else
{
$sOptions = "";
}
$oPage->add("<li><span style=\"color:purple;font-weight=bold;\">$sAttLabel</span> $sOptions</li>\n");
}
$oPage->add("</ul>\n");
}
else
{
$oPage->p("<em>".Dict::S('UI:Schema:LifeCycleEmptyList')."</em>");
}
}
$oPage->add("</ul>\n");
}
}
/**
* Helper for the trigger
*/
function DisplayTriggers($oPage, $sClass)
{
$sClassList = implode("', '", MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
$oSet = new CMDBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObject WHERE target_class IN ('$sClassList')"));
cmdbAbstractObject::DisplaySet($oPage, $oSet, array('block_id' => 'triggers'));
}
/**
* Display the list of classes from the business model
*/
function DisplayClassesList($oPage, $sContext)
{
$oPage->add("<h1>".Dict::S('UI:Schema:Title')."</h1>\n");
$oPage->add("<ul id=\"ClassesList\" class=\"treeview fileview\">\n");
// Get all the "root" classes for display
$aRootClasses = array();
foreach(MetaModel::GetClasses() as $sClassName)
{
if (MetaModel::IsRootClass($sClassName))
{
$aRootClasses[$sClassName] = MetaModel::GetName($sClassName);
}
elseif (MetaModel::IsStandaloneClass($sClassName))
{
$aRootClasses[$sClassName] = MetaModel::GetName($sClassName);
}
}
// Sort them alphabetically on their display name
asort($aRootClasses);
foreach($aRootClasses as $sClassName => $sDisplayName)
{
if (MetaModel::IsRootClass($sClassName))
{
$oPage->add("<li class=\"closed\">".MakeClassHLink($sClassName, $sContext)."\n");
DisplaySubclasses($oPage, $sClassName, $sContext);
$oPage->add("</li>\n");
}
elseif (MetaModel::IsStandaloneClass($sClassName))
{
$oPage->add("<li>".MakeClassHLink($sClassName, $sContext)."</li>\n");
}
}
$oPage->add("</ul>\n");
$oPage->add("<h1>".Dict::S('UI:Schema:Relationships')."</h1>\n");
$oPage->add("<ul id=\"ClassesRelationships\" class=\"treeview\">\n");
foreach (MetaModel::EnumRelations() as $sRelCode)
{
$oPage->add("<li>".MakeRelationHLink($sRelCode, $sContext)."\n");
$oPage->add("<ul>\n");
$oPage->add("<li>Description: ".htmlentities(MetaModel::GetRelationDescription($sRelCode), ENT_QUOTES, 'UTF-8')."</li>\n");
$oPage->add("<li>Verb up: ".htmlentities(MetaModel::GetRelationVerbUp($sRelCode), ENT_QUOTES, 'UTF-8')."</li>\n");
$oPage->add("<li>Verb down: ".htmlentities(MetaModel::GetRelationVerbDown($sRelCode), ENT_QUOTES, 'UTF-8')."</li>\n");
$oPage->add("</ul>\n");
$oPage->add("</li>\n");
}
$oPage->add("</ul>\n");
$oPage->add_ready_script('$("#ClassesList").treeview();');
$oPage->add_ready_script('$("#ClassesRelationships").treeview();');
}
/**
* Display the details of a given class of objects
*/
function DisplayClassDetails($oPage, $sClass, $sContext)
{
$oPage->add("<h2>$sClass - ".MetaModel::GetClassDescription($sClass)."</h2>\n");
if (MetaModel::IsAbstract($sClass))
{
$oPage->p(Dict::S('UI:Schema:AbstractClass'));
}
else
{
$oPage->p(Dict::S('UI:Schema:NonAbstractClass'));
}
// $oPage->p("<h3>".Dict::S('UI:Schema:ClassHierarchyTitle')."</h3>");
$aParentClasses = array();
foreach(MetaModel::EnumParentClasses($sClass) as $sParentClass)
{
$aParentClasses[] = MakeClassHLink($sParentClass, $sContext);
}
if (count($aParentClasses) > 0)
{
$sParents = implode(' &gt;&gt; ', $aParentClasses)." &gt;&gt; <b>$sClass</b>";
}
else
{
$sParents = '';
}
$oPage->p("[<a href=\"schema.php?operation=list{$sContext}\">".Dict::S('UI:Schema:AllClasses')."</a>] $sParents");
if (MetaModel::HasChildrenClasses($sClass))
{
$oPage->add("<ul id=\"ClassHierarchy\">");
$oPage->add("<li class=\"closed\">".$sClass."\n");
DisplaySubclasses($oPage, $sClass,$sContext);
$oPage->add("</li>\n");
$oPage->add("</ul>\n");
$oPage->add_ready_script('$("#ClassHierarchy").treeview();');
}
$oPage->p('');
$oPage->AddTabContainer('details');
$oPage->SetCurrentTabContainer('details');
// List the attributes of the object
$aForwardChangeTracking = MetaModel::GetTrackForwardExternalKeys($sClass);
$aDetails = array();
foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef)
{
if ($oAttDef->IsExternalKey())
{
$sValue = Dict::Format('UI:Schema:ExternalKey_To',MakeClassHLink($oAttDef->GetTargetClass(), $sContext));
if (array_key_exists($sAttCode, $aForwardChangeTracking))
{
$oLinkSet = $aForwardChangeTracking[$sAttCode];
$sRemoteClass = $oLinkSet->GetHostClass();
$sValue = $sValue."<span title=\"Forward changes to $sRemoteClass\">*</span>";
}
}
elseif ($oAttDef->IsLinkSet())
{
$sValue = MakeClassHLink($oAttDef->GetLinkedClass(), $sContext);
}
else
{
$sValue = $oAttDef->GetDescription();
}
$sType = $oAttDef->GetType().' ('.$oAttDef->GetTypeDesc().')';
$sOrigin = MetaModel::GetAttributeOrigin($sClass, $sAttCode);
$sAllowedValues = "";
$oAllowedValuesDef = $oAttDef->GetValuesDef();
$sMoreInfo = "";
$aCols = array();
foreach($oAttDef->GetSQLColumns() as $sCol => $sFieldDesc)
{
$aCols[] = "$sCol: $sFieldDesc";
}
if (count($aCols) > 0)
{
$sCols = implode(', ', $aCols);
$aMoreInfo = array();
$aMoreInfo[] = Dict::Format('UI:Schema:Columns_Description', $sCols);
$aMoreInfo[] = Dict::Format('UI:Schema:Default_Description', $oAttDef->GetDefaultValue());
$aMoreInfo[] = $oAttDef->IsNullAllowed() ? Dict::S('UI:Schema:NullAllowed') : Dict::S('UI:Schema:NullNotAllowed');
$sMoreInfo .= implode(', ', $aMoreInfo);
}
if (is_object($oAllowedValuesDef)) $sAllowedValues = $oAllowedValuesDef->GetValuesDescription();
else $sAllowedValues = '';
$aDetails[] = array('code' => $oAttDef->GetCode(), 'type' => $sType, 'origin' => $sOrigin, 'label' => $oAttDef->GetLabel(), 'description' => $sValue, 'values' => $sAllowedValues, 'moreinfo' => $sMoreInfo);
}
$oPage->SetCurrentTab(Dict::S('UI:Schema:Attributes'));
$aConfig = array( 'code' => array('label' => Dict::S('UI:Schema:AttributeCode'), 'description' => Dict::S('UI:Schema:AttributeCode+')),
'label' => array('label' => Dict::S('UI:Schema:Label'), 'description' => Dict::S('UI:Schema:Label+')),
'type' => array('label' => Dict::S('UI:Schema:Type'), 'description' => Dict::S('UI:Schema:Type+')),
'origin' => array('label' => Dict::S('UI:Schema:Origin'), 'description' => Dict::S('UI:Schema:Origin+')),
'description' => array('label' => Dict::S('UI:Schema:Description'), 'description' => Dict::S('UI:Schema:Description+')),
'values' => array('label' => Dict::S('UI:Schema:AllowedValues'), 'description' => Dict::S('UI:Schema:AllowedValues+')),
'moreinfo' => array('label' => Dict::S('UI:Schema:MoreInfo'), 'description' => Dict::S('UI:Schema:MoreInfo+')),
);
$oPage->table($aConfig, $aDetails);
// List the search criteria for this object
$aDetails = array();
foreach (MetaModel::GetClassFilterDefs($sClass) as $sFilterCode => $oFilterDef)
{
$aOpDescs = array();
foreach ($oFilterDef->GetOperators() as $sOpCode => $sOpDescription)
{
$sIsTheLooser = ($sOpCode == $oFilterDef->GetLooseOperator()) ? " (loose search)" : "";
$aOpDescs[] = "$sOpCode ($sOpDescription)$sIsTheLooser";
}
$aDetails[] = array( 'code' => $sFilterCode, 'description' => $oFilterDef->GetLabel(),'operators' => implode(" / ", $aOpDescs));
}
$oPage->SetCurrentTab(Dict::S('UI:Schema:SearchCriteria'));
$aConfig = array( 'code' => array('label' => Dict::S('UI:Schema:FilterCode'), 'description' => Dict::S('UI:Schema:FilterCode+')),
'description' => array('label' => Dict::S('UI:Schema:FilterDescription'), 'description' => Dict::S('UI:Schema:FilterDescription+')),
'operators' => array('label' => Dict::S('UI:Schema:AvailOperators'), 'description' => Dict::S('UI:Schema:AvailOperators+'))
);
$oPage->table($aConfig, $aDetails);
$oPage->SetCurrentTab(Dict::S('UI:Schema:ChildClasses'));
DisplaySubclasses($oPage, $sClass, $sContext);
$oPage->SetCurrentTab(Dict::S('UI:Schema:ReferencingClasses'));
DisplayReferencingClasses($oPage, $sClass, $sContext);
$oPage->SetCurrentTab(Dict::S('UI:Schema:RelatedClasses'));
DisplayRelatedClasses($oPage, $sClass, $sContext);
$oPage->SetCurrentTab(Dict::S('UI:Schema:LifeCycle'));
DisplayLifecycle($oPage, $sClass, $sContext);
$oPage->SetCurrentTab(Dict::S('UI:Schema:Triggers'));
DisplayTriggers($oPage, $sClass, $sContext);
$oPage->SetCurrentTab();
$oPage->SetCurrentTabContainer();
}
/**
* Display the details of a given relation (e.g. "impacts")
*/
function DisplayRelationDetails($oPage, $sRelCode, $sContext)
{
$sDesc = MetaModel::GetRelationDescription($sRelCode);
$sVerbDown = MetaModel::GetRelationVerbDown($sRelCode);
$sVerbUp = MetaModel::GetRelationVerbUp($sRelCode);
$oPage->add("<h1>".Dict::Format('UI:Schema:Relation_Code_Description', $sRelCode, $sDesc)."</h1>");
$oPage->p(Dict::Format('UI:Schema:RelationDown_Description', $sVerbDown));
$oPage->p(Dict::Format('UI:Schema:RelationUp_Description', $sVerbUp));
$oPage->add("<ul id=\"RelationshipDetails\" class=\"treeview\">\n");
foreach(MetaModel::GetClasses() as $sClass)
{
$aRelQueries = MetaModel::EnumRelationQueries($sClass, $sRelCode);
if (count($aRelQueries) > 0)
{
$oPage->add("<li>class ".MakeClassHLink($sClass, $sContext)."\n");
$oPage->add("<ul>\n");
foreach ($aRelQueries as $sRelKey => $aQuery)
{
$sQuery = $aQuery['sQuery'];
$iDistance = $aQuery['iDistance'];
if ($aQuery['bPropagate'])
{
$oPage->add("<li>".Dict::Format('UI:Schema:RelationPropagates', $sRelKey, $iDistance, $sQuery)."</li>\n");
}
else
{
$oPage->add("<li>".Dict::Format('UI:Schema:RelationDoesNotPropagate', $sRelKey, $iDistance, $sQuery)."</li>\n");
}
}
$oPage->add("</ul>\n");
$oPage->add("</li>\n");
}
}
$oPage->add_ready_script('$("#RelationshipDetails").treeview();');
}
// Display the menu on the left
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink();
if (!empty($sContext))
{
$sContext = '&'.$sContext;
}
$operation = utils::ReadParam('operation', '');
$oPage = new iTopWebPage(Dict::S('UI:Schema:Title'));
$oPage->no_cache();
$operation = utils::ReadParam('operation', '');
switch($operation)
{
case 'details_class':
$sClass = utils::ReadParam('class', 'logRealObject', false, 'class');
DisplayClassDetails($oPage, $sClass, $sContext);
break;
case 'details_relation':
$sRelCode = utils::ReadParam('relcode', '');
DisplayRelationDetails($oPage, $sRelCode, $sContext);
break;
case 'list':
default:
DisplayClassesList($oPage, $sContext);
}
$oPage->output();
?>