diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 63d242ca2..a5b145878 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1091,11 +1091,37 @@ abstract class MetaModel return $aClassRelations; } - final static public function GetRelationDescription($sRelCode) + /** + * @param string $sRelCode Relation code + * @param bool $bDown Relation direction, is it downstream (true) or upstream (false). Default is true. + * + * @return string + */ + final static public function GetRelationDescription($sRelCode, $bDown = true) { - return Dict::S("Relation:$sRelCode/Description"); + // Legacy convention had only one description describing the relation. + // Now, as the relation is bidirectional, we have a description for each directions. + $sLegacy = Dict::S("Relation:$sRelCode/Description"); + + if($bDown) + { + $sKey = "Relation:$sRelCode/DownStream+"; + } + else + { + $sKey = "Relation:$sRelCode/UpStream+"; + } + $sRet = Dict::S($sKey, $sLegacy); + + return $sRet; } + /** + * @param string $sRelCode Relation code + * @param bool $bDown Relation direction, is it downstream (true) or upstream (false). Default is true. + * + * @return string + */ final static public function GetRelationLabel($sRelCode, $bDown = true) { if ($bDown) diff --git a/datamodels/2.x/itop-config-mgmt/en.dict.itop-config-mgmt.php b/datamodels/2.x/itop-config-mgmt/en.dict.itop-config-mgmt.php index d3ffc577a..d94f8b2dd 100755 --- a/datamodels/2.x/itop-config-mgmt/en.dict.itop-config-mgmt.php +++ b/datamodels/2.x/itop-config-mgmt/en.dict.itop-config-mgmt.php @@ -32,7 +32,10 @@ Dict::Add('EN US', 'English', 'English', array( 'Relation:impacts/Description' => 'Elements impacted by', 'Relation:impacts/DownStream' => 'Impacts...', + 'Relation:impacts/DownStream+' => 'Elements impacted by', 'Relation:impacts/UpStream' => 'Depends on......', + 'Relation:impacts/UpStream+' => 'Elements impacting', + // Legacy entries 'Relation:depends on/Description' => 'Elements impacting', 'Relation:depends on/DownStream' => 'Depends on...', 'Relation:depends on/UpStream' => 'Impacts...', diff --git a/datamodels/2.x/itop-config-mgmt/es_cr.dict.itop-config-mgmt.php b/datamodels/2.x/itop-config-mgmt/es_cr.dict.itop-config-mgmt.php index 641705a77..7bcd8ad08 100755 --- a/datamodels/2.x/itop-config-mgmt/es_cr.dict.itop-config-mgmt.php +++ b/datamodels/2.x/itop-config-mgmt/es_cr.dict.itop-config-mgmt.php @@ -33,7 +33,10 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'Relation:impacts/Description' => 'Elementos Impactados por', 'Relation:impacts/DownStream' => 'Impacto...', + 'Relation:impacts/DownStream+' => 'Elementos Impactados por', 'Relation:impacts/UpStream' => 'Depende de...', + 'Relation:impacts/UpStream+' => 'Elementos de los cuales depende', + // Legacy entries 'Relation:depends on/Description' => 'Elementos de los cuales depende', 'Relation:depends on/DownStream' => 'Depende de...', 'Relation:depends on/UpStream' => 'Impactos...', diff --git a/datamodels/2.x/itop-config-mgmt/fr.dict.itop-config-mgmt.php b/datamodels/2.x/itop-config-mgmt/fr.dict.itop-config-mgmt.php index a70f536d4..887eb07c5 100755 --- a/datamodels/2.x/itop-config-mgmt/fr.dict.itop-config-mgmt.php +++ b/datamodels/2.x/itop-config-mgmt/fr.dict.itop-config-mgmt.php @@ -1836,7 +1836,10 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Menu:UI_WelcomeMenu_AllConfigItems' => 'Résumé', 'Relation:impacts/Description' => 'Eléments impactés par', 'Relation:impacts/DownStream' => 'Impacte...', + 'Relation:impacts/DownStream+' => 'Eléments impactés par', 'Relation:impacts/UpStream' => 'Dépend de...', + 'Relation:impacts/UpStream+' => 'Eléments dont dépend', + // Legacy entries 'Relation:depends on/Description' => 'Eléments dont dépend', 'Relation:depends on/DownStream' => 'Dépend de...', 'Relation:depends on/UpStream' => 'Impacte...', diff --git a/datamodels/2.x/itop-config-mgmt/hu.dict.itop-config-mgmt.php b/datamodels/2.x/itop-config-mgmt/hu.dict.itop-config-mgmt.php index 2584ebf22..f36c07524 100755 --- a/datamodels/2.x/itop-config-mgmt/hu.dict.itop-config-mgmt.php +++ b/datamodels/2.x/itop-config-mgmt/hu.dict.itop-config-mgmt.php @@ -384,7 +384,10 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array( 'Class:lnkProcessToSolution/Attribute:reason+' => '', 'Relation:impacts/Description' => 'Konfigurációs elem működését befolyásolják', 'Relation:impacts/DownStream' => 'Hatás', + 'Relation:impacts/DownStream+' => 'Konfigurációs elem működését befolyásolják', 'Relation:impacts/UpStream' => 'Függőségek', + 'Relation:impacts/UpStream+' => 'Konfigurációs elemtől függnek', + // Legacy entries 'Relation:depends on/Description' => 'Konfigurációs elemtől függnek', 'Relation:depends on/DownStream' => 'Függőségek', 'Relation:depends on/UpStream' => 'Hatások', diff --git a/datamodels/2.x/itop-config-mgmt/it.dict.itop-config-mgmt.php b/datamodels/2.x/itop-config-mgmt/it.dict.itop-config-mgmt.php index 07333109f..4303837f4 100755 --- a/datamodels/2.x/itop-config-mgmt/it.dict.itop-config-mgmt.php +++ b/datamodels/2.x/itop-config-mgmt/it.dict.itop-config-mgmt.php @@ -384,7 +384,10 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array( 'Class:lnkProcessToSolution/Attribute:reason+' => 'Più informazioni tra il processo di business e la soluzione applicativa', 'Relation:impacts/Description' => 'Elementi impattati da...', 'Relation:impacts/DownStream' => 'Impatto...', + 'Relation:impacts/DownStream+' => 'Elementi impattati da...', 'Relation:impacts/UpStream' => 'Dipende da...', + 'Relation:impacts/UpStream+' => 'Elementi di questo elemento dipende da', + // Legacy entries 'Relation:depends on/Description' => 'Elementi di questo elemento dipende da', 'Relation:depends on/DownStream' => 'Dipende da...', 'Relation:depends on/UpStream' => 'Impatto...', diff --git a/datamodels/2.x/itop-config-mgmt/ja.dict.itop-config-mgmt.php b/datamodels/2.x/itop-config-mgmt/ja.dict.itop-config-mgmt.php index b6ef124f0..c8a66212f 100755 --- a/datamodels/2.x/itop-config-mgmt/ja.dict.itop-config-mgmt.php +++ b/datamodels/2.x/itop-config-mgmt/ja.dict.itop-config-mgmt.php @@ -596,7 +596,10 @@ Dict::Add('JA JP', 'Japanese', '日本語', array( 'Class:LogicalInterface/Attribute:virtualmachine_id+' => '', 'Relation:impacts/Description' => 'インパクトを受ける要素', 'Relation:impacts/DownStream' => 'インパクト...', + 'Relation:impacts/DownStream+' => 'インパクトを受ける要素', 'Relation:impacts/UpStream' => '依存...', + 'Relation:impacts/UpStream+' => 'この要素が依存している要素', + // Legacy entries 'Relation:depends on/Description' => 'この要素が依存している要素', 'Relation:depends on/DownStream' => '依存...', 'Relation:depends on/UpStream' => 'インパクト...', diff --git a/datamodels/2.x/itop-config-mgmt/nl.dict.itop-config-mgmt.php b/datamodels/2.x/itop-config-mgmt/nl.dict.itop-config-mgmt.php index 687fdcde4..b6faea5fb 100644 --- a/datamodels/2.x/itop-config-mgmt/nl.dict.itop-config-mgmt.php +++ b/datamodels/2.x/itop-config-mgmt/nl.dict.itop-config-mgmt.php @@ -35,7 +35,10 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'Relation:impacts/Description' => 'Elementen hebben impact op', 'Relation:impacts/DownStream' => 'Impact...', + 'Relation:impacts/DownStream+' => 'Elementen hebben impact op', 'Relation:impacts/UpStream' => 'Is afhankelijk van...', + 'Relation:impacts/UpStream+' => 'Elementen waarvan dit element afhankelijk van is', + // Legacy entries 'Relation:depends on/Description' => 'Elementen waarvan dit element afhankelijk van is', 'Relation:depends on/DownStream' => 'Is afhankelijk van...', 'Relation:depends on/UpStream' => 'Impacts...', diff --git a/datamodels/2.x/itop-config-mgmt/pt_br.dict.itop-config-mgmt.php b/datamodels/2.x/itop-config-mgmt/pt_br.dict.itop-config-mgmt.php index 30c566170..eb5f57951 100755 --- a/datamodels/2.x/itop-config-mgmt/pt_br.dict.itop-config-mgmt.php +++ b/datamodels/2.x/itop-config-mgmt/pt_br.dict.itop-config-mgmt.php @@ -32,7 +32,10 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Relation:impacts/Description' => 'Elementos impactados por', 'Relation:impacts/DownStream' => 'Impacto...', + 'Relation:impacts/DownStream+' => 'Elementos impactados por', 'Relation:impacts/UpStream' => 'Depende de...', + 'Relation:impacts/UpStream+' => 'Elementos estes, que dependem deste elemento', + // Legacy entries 'Relation:depends on/Description' => 'Elementos estes, que dependem deste elemento', 'Relation:depends on/DownStream' => 'Depende de...', 'Relation:depends on/UpStream' => 'Impactos...', diff --git a/datamodels/2.x/itop-config-mgmt/ru.dict.itop-config-mgmt.php b/datamodels/2.x/itop-config-mgmt/ru.dict.itop-config-mgmt.php index 0c9e9145a..dee1092d7 100755 --- a/datamodels/2.x/itop-config-mgmt/ru.dict.itop-config-mgmt.php +++ b/datamodels/2.x/itop-config-mgmt/ru.dict.itop-config-mgmt.php @@ -18,7 +18,10 @@ Dict::Add('RU RU', 'Russian', 'Русский', array( 'Relation:impacts/Description' => 'Элементы, на которые влияет', 'Relation:impacts/DownStream' => 'Влияет на...', + 'Relation:impacts/DownStream+' => 'Элементы, на которые влияет', 'Relation:impacts/UpStream' => 'Зависит от...', + 'Relation:impacts/UpStream+' => 'Элементы, от которых зависит', + // Legacy entries 'Relation:depends on/Description' => 'Элементы, от которых зависит', 'Relation:depends on/DownStream' => 'Зависит от...', 'Relation:depends on/UpStream' => 'Влияет на...', diff --git a/datamodels/2.x/itop-config-mgmt/tr.dict.itop-config-mgmt.php b/datamodels/2.x/itop-config-mgmt/tr.dict.itop-config-mgmt.php index fdb1e08d2..c6105c64c 100755 --- a/datamodels/2.x/itop-config-mgmt/tr.dict.itop-config-mgmt.php +++ b/datamodels/2.x/itop-config-mgmt/tr.dict.itop-config-mgmt.php @@ -33,7 +33,10 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'Relation:impacts/Description' => 'Etkilenen kalemler', 'Relation:impacts/DownStream' => 'Etkiler...', + 'Relation:impacts/DownStream+' => 'Etkilenen kalemler', 'Relation:impacts/UpStream' => 'Bağımlı olanlar...', + 'Relation:impacts/UpStream+' => 'Bu kaleme bağımlı olan kalemler', + // Legacy entries 'Relation:depends on/Description' => 'Bu kaleme bağımlı olan kalemler', 'Relation:depends on/DownStream' => 'Bağımlı olanlar...', 'Relation:depends on/UpStream' => 'Etkiledikleri...', diff --git a/datamodels/2.x/itop-config-mgmt/zh.dict.itop-config-mgmt.php b/datamodels/2.x/itop-config-mgmt/zh.dict.itop-config-mgmt.php index dce13915c..0585315f5 100755 --- a/datamodels/2.x/itop-config-mgmt/zh.dict.itop-config-mgmt.php +++ b/datamodels/2.x/itop-config-mgmt/zh.dict.itop-config-mgmt.php @@ -33,7 +33,10 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Relation:impacts/Description' => '被影响的元素', 'Relation:impacts/DownStream' => '影响...', + 'Relation:impacts/DownStream+' => '被影响的元素', 'Relation:impacts/UpStream' => '依赖于...', + 'Relation:impacts/UpStream+' => '该元素依赖的元素...', + // Legacy entries 'Relation:depends on/Description' => '该元素依赖的元素...', 'Relation:depends on/DownStream' => '依赖于...', 'Relation:depends on/UpStream' => '影响...', diff --git a/pages/UI.php b/pages/UI.php index b57dfed2f..1c4092aaf 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -1,1790 +1,1792 @@ - - - -/** - * Main page of iTop - * - * @copyright Copyright (C) 2010-2017 Combodo SARL - * @license http://opensource.org/licenses/AGPL-3.0 - */ - - -/** - * Displays a popup welcome message, once per session at maximum - * until the user unchecks the "Display welcome at startup" - * @param WebPage $oP The current web page for the display - * @return void - */ -function DisplayWelcomePopup(WebPage $oP) -{ - if (!isset($_SESSION['welcome'])) - { - // Check, only once per session, if the popup should be displayed... - // If the user did not already ask for hiding it forever - $bPopup = appUserPreferences::GetPref('welcome_popup', true); - if ($bPopup) - { - $sTemplate = @file_get_contents('../application/templates/welcome_popup.html'); - if ($sTemplate !== false) - { - $oTemplate = new DisplayTemplate($sTemplate); - $oP->add("
"); - $oTemplate->Render($oP, array()); - $oP->add("

\n"); - $oP->add("

\n"); - $oP->add("

\n"); - $sTitle = addslashes(Dict::S('UI:WelcomeMenu:Title')); - $oP->add_ready_script( -<< ($(window).height()-70)) - { - $('#welcome_popup').height($(window).height()-70); - } -EOF -); - $_SESSION['welcome'] = 'ok'; - } - } - } -} - -/** - * Apply the 'next-action' to the given object or redirect to the page that prompts for additional information if needed - * @param $oP WebPage The page for the output - * @param $oObj CMDBObject The object to process - * @param $sNextAction string The code of the stimulus for the 'action' (i.e. Transition) to apply - */ -function ApplyNextAction(Webpage $oP, CMDBObject $oObj, $sNextAction) -{ - // Here handle the apply stimulus - $aTransitions = $oObj->EnumTransitions(); - $aStimuli = MetaModel::EnumStimuli(get_class($oObj)); - if (!isset($aTransitions[$sNextAction])) - { - // Invalid stimulus - throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sNextAction, $oObj->GetName(), $oObj->GetStateLabel())); - } - // Get the list of missing mandatory fields for the target state, considering only the changes from the previous form (i.e don't prompt twice) - $aExpectedAttributes = $oObj->GetTransitionAttributes($sNextAction); - - if (count($aExpectedAttributes) == 0) - { - // If all the mandatory fields are already present, just apply the transition silently... - if ($oObj->ApplyStimulus($sNextAction)) - { - $oObj->DBUpdate(); - } - ReloadAndDisplay($oP, $oObj); - } - else - { - // redirect to the 'stimulus' action - $oAppContext = new ApplicationContext(); -//echo "

Missing Attributes

".print_r($aExpectedAttributes, true)."

\n"; - - $oP->add_header('Location: '.utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=stimulus&class='.get_class($oObj).'&stimulus='.$sNextAction.'&id='.$oObj->getKey().'&'.$oAppContext->GetForLink()); - } -} - -function ReloadAndDisplay($oPage, $oObj, $sMessageId = '', $sMessage = '', $sSeverity = null) -{ - $oAppContext = new ApplicationContext(); - if ($sMessageId != '') - { - cmdbAbstractObject::SetSessionMessage(get_class($oObj), $oObj->GetKey(), $sMessageId, $sMessage, $sSeverity, 0, true /* must not exist */); - } - $oPage->add_header('Location: '.utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=details&class='.get_class($oObj).'&id='.$oObj->getKey().'&'.$oAppContext->GetForLink()); -} -/** - * Displays the details of an object - * @param $oP WebPage Page for the output - * @param $sClass string The name of the class of the object - * @param $oObj DBObject The object to display - * @param $id mixed Identifier of the object (name or ID) - */ -function DisplayDetails($oP, $sClass, $oObj, $id) -{ - $sClassLabel = MetaModel::GetName($sClass); - $oSearch = new DBObjectSearch($sClass); - $oBlock = new DisplayBlock($oSearch, 'search', false); - $oBlock->Display($oP, 0); - - // The object could be listed, check if it is actually allowed to view it - $oSet = CMDBObjectSet::FromObject($oObj); - if (UserRights::IsActionAllowed($sClass, UR_ACTION_READ, $oSet) == UR_ALLOWED_NO) - { - throw new SecurityException('User not allowed to view this object', array('class' => $sClass, 'id' => $id)); - } - $oP->set_title(Dict::Format('UI:DetailsPageTitle', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding - $oObj->DisplayDetails($oP); -} - -/** - * Display the session messages relative to the object identified by its "message key" (class::id) - * @param string $sMessageKey - * @param WebPage $oPage - */ -function DisplayMessages($sMessageKey, WebPage $oPage) -{ - if (array_key_exists('obj_messages', $_SESSION) && array_key_exists($sMessageKey, $_SESSION['obj_messages'])) - { - $aMessages = array(); - $aRanks = array(); - foreach ($_SESSION['obj_messages'][$sMessageKey] as $sMessageId => $aMessageData) - { - $sMsgClass = 'message_'.$aMessageData['severity']; - $aMessages[] = "
".$aMessageData['message']."
"; - $aRanks[] = $aMessageData['rank']; - } - unset($_SESSION['obj_messages'][$sMessageKey]); - array_multisort($aRanks, $aMessages); - foreach ($aMessages as $sMessage) - { - $oPage->add($sMessage); - } - } -} - -/** - * Helper to update the breadrumb for the current object - * @param DBObject $oObj - * @param WebPage $oPage - */ -function SetObjectBreadCrumbEntry(DBObject $oObj, WebPage $oPage) -{ - $sClass = get_class($oObj); // get the leaf class - $sIcon = MetaModel::GetClassIcon($sClass, false); - if ($sIcon == '') - { - $sIcon = utils::GetAbsoluteUrlAppRoot().'images/breadcrumb_object.png'; - } - $oPage->SetBreadCrumbEntry("ui-details-$sClass-".$oObj->GetKey(), $oObj->Get('friendlyname'), MetaModel::GetName($sClass).': '.$oObj->Get('friendlyname'), '', $sIcon); -} - -/** - * Displays the result of a search request - * @param $oP WebPage Web page for the output - * @param $oFilter DBSearch The search of objects to display - * @param $bSearchForm boolean Whether or not to display the search form at the top the page - * @param $sBaseClass string The base class for the search (can be different from the actual class of the results) - * @param $sFormat string The format to use for the output: csv or html - * @param $bDoSearch bool True to display the search results below the search form - * @param $bSearchFormOpen bool True to display the search form fully expanded (only if $bSearchForm of course) - */ -function DisplaySearchSet($oP, $oFilter, $bSearchForm = true, $sBaseClass = '', $sFormat = '', $bDoSearch = true, $bSearchFormOpen = false) -{ - if ($bSearchForm) - { - $aParams = array('open' => $bSearchFormOpen); - if (!empty($sBaseClass)) - { - $aParams['baseClass'] = $sBaseClass; - } - $oBlock = new DisplayBlock($oFilter, 'search', false /* Asynchronous */, $aParams); - $oBlock->Display($oP, 0); - } - if ($bDoSearch) - { - if (strtolower($sFormat) == 'csv') - { - $oBlock = new DisplayBlock($oFilter, 'csv', false); - $oBlock->Display($oP, 1); - // Adjust the size of the Textarea containing the CSV to fit almost all the remaining space - $oP->add_ready_script(" $('#1>textarea').height($('#1').parent().height() - $('#0').outerHeight() - 30).width( $('#1').parent().width() - 20);"); // adjust the size of the block - } - else - { - $oBlock = new DisplayBlock($oFilter, 'list', false); - $oBlock->Display($oP, 1); - - // Breadcrumb - //$iCount = $oBlock->GetDisplayedCount(); - $sPageId = "ui-search-".$oFilter->GetClass(); - $sLabel = MetaModel::GetName($oFilter->GetClass()); - $oP->SetBreadCrumbEntry($sPageId, $sLabel, '', '', '../images/breadcrumb-search.png'); - } - } -} - -/** - * Displays a form (checkboxes) to select the objects for which to apply a given action - * Only the objects for which the action is valid can be checked. By default all valid objects are checked - * @param $oP WebPage The page for output - * @param $oFilter DBSearch The filter that defines the list of objects - * @param $sNextOperation string The next operation (code) to be executed when the form is submitted - * @param $oChecker ActionChecker The helper class/instance used to check for which object the action is valid - * @return none - */ -function DisplayMultipleSelectionForm($oP, $oFilter, $sNextOperation, $oChecker, $aExtraFormParams = array()) -{ - $oAppContext = new ApplicationContext(); - $iBulkActionAllowed = $oChecker->IsAllowed(); - $sClass = $oFilter->GetClass(); - $aExtraParams = array('selection_type' => 'multiple', 'selection_mode' => true, 'display_limit' => false, 'menu' => false); - if ($iBulkActionAllowed == UR_ALLOWED_DEPENDS) - { - $aAllowed = array(); - $aExtraParams['selection_enabled'] = $oChecker->GetAllowedIDs(); - } - else if(UR_ALLOWED_NO) - { - throw new ApplicationException(Dict::Format('UI:ActionNotAllowed')); - } - - $oBlock = new DisplayBlock($oFilter, 'list', false); - $oP->add("
\n"); - $oP->add("\n"); - $oP->add("GetClass()."\">\n"); - $oP->add("Serialize()."\">\n"); - $oP->add("\n"); - foreach($aExtraFormParams as $sName => $sValue) - { - $oP->add("\n"); - } - $oP->add($oAppContext->GetForForm()); - $oBlock->Display($oP, 1, $aExtraParams); - $oP->add("  \n"); - $oP->add("
\n"); - $oP->add_ready_script("$('#1 table.listResults').trigger('check_all');"); -} - -function DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj) -{ - $oP->SetCurrentTab(Dict::S('UI:RelationshipList')); - $oP->add("
"); - $sOldRelation = $sRelation; - if (($sRelation == 'impacts') && ($sDirection == 'up')) - { - $sOldRelation = 'depends on'; - } - $oP->add("

".MetaModel::GetRelationDescription($sOldRelation).' '.$oObj->GetName()."

\n"); - $oP->add("
"); - $oP->add(''); - /* - * Content is rendered asynchronously via pages/ajax.render.php?operation=relation_lists - */ - /* - $iBlock = 1; // Zero is not a valid blockid - foreach($aResults as $sListClass => $aObjects) - { - $oSet = CMDBObjectSet::FromArray($sListClass, $aObjects); - $oP->add("
\n"); - $oP->add("

".MetaModel::GetClassIcon($sListClass)." ".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aObjects), Metamodel::GetName($sListClass))."

\n"); - $oP->add("
\n"); - $oBlock = DisplayBlock::FromObjectSet($oSet, 'list'); - $oBlock->Display($oP, $iBlock++, array('table_id' => get_class($oObj).'_'.$sRelation.'_'.$sDirection.'_'.$sListClass)); - $oP->P(' '); // Some space ? - } - */ - $oP->add("
"); - $oP->add("
"); -} - -function DisplayNavigatorGroupTab($oP) -{ - $oP->SetCurrentTab(Dict::S('UI:RelationGroups')); - $oP->add("
"); - $oP->add(''); - /* - * Content is rendered asynchronously via pages/ajax.render.php?operation=relation_groups - */ - $oP->add("
"); -} - -/*********************************************************************************** - * - * Main user interface page starts here - * - ***********************************************************************************/ -require_once('../approot.inc.php'); -require_once(APPROOT.'/application/application.inc.php'); -require_once(APPROOT.'/application/itopwebpage.class.inc.php'); -require_once(APPROOT.'/application/wizardhelper.class.inc.php'); - -require_once(APPROOT.'/application/startup.inc.php'); - -try -{ - $operation = utils::ReadParam('operation', ''); - $bPrintable = (utils::ReadParam('printable', 0) == '1'); - - $oKPI = new ExecutionKPI(); - $oKPI->ComputeAndReport('Data model loaded'); - - $oKPI = new ExecutionKPI(); - - require_once(APPROOT.'/application/loginwebpage.class.inc.php'); - $sLoginMessage = LoginWebPage::DoLogin(); // Check user rights and prompt if needed - $oAppContext = new ApplicationContext(); - - $oKPI->ComputeAndReport('User login'); - - $oP = new iTopWebPage(Dict::S('UI:WelcomeToITop'), $bPrintable); - $oP->SetMessage($sLoginMessage); - - - // All the following actions use advanced forms that require more javascript to be loaded - switch($operation) - { - case 'new': // Form to create a new object - case 'modify': // Form to modify an object - case 'apply_new': // Creation of a new object - case 'apply_modify': // Applying the modifications to an existing object - case 'form_for_modify_all': // Form to modify multiple objects (bulk modify) - case 'bulk_stimulus': // For to apply a stimulus to multiple objects - case 'stimulus': // Form displayed when applying a stimulus (state change) - case 'apply_stimulus': // Form displayed when applying a stimulus (state change) - $oP->add_linked_script("../js/json.js"); - $oP->add_linked_script("../js/forms-json-utils.js"); - $oP->add_linked_script("../js/wizardhelper.js"); - $oP->add_linked_script("../js/wizard.utils.js"); - $oP->add_linked_script("../js/linkswidget.js"); - $oP->add_linked_script("../js/linksdirectwidget.js"); - $oP->add_linked_script("../js/extkeywidget.js"); - $oP->add_linked_script("../js/jquery.blockUI.js"); - break; - } - - switch($operation) - { - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'details': // Details of an object - $sClass = utils::ReadParam('class', ''); - $id = utils::ReadParam('id', ''); - if ( empty($sClass) || empty($id)) - { - throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); - } - - if (is_numeric($id)) - { - $oObj = MetaModel::GetObject($sClass, $id, false /* MustBeFound */); - } - else - { - $oObj = MetaModel::GetObjectByName($sClass, $id, false /* MustBeFound */); - } - if (is_null($oObj)) - { - // Check anyhow if there is a message for this object (like you've just created it) - $sMessageKey = $sClass.'::'.$id; - DisplayMessages($sMessageKey, $oP); - $oP->set_title(Dict::S('UI:ErrorPageTitle')); - - // Attempt to load the object in archive mode - utils::PushArchiveMode(true); - if (is_numeric($id)) - { - $oObj = MetaModel::GetObject($sClass, $id, false /* MustBeFound */); - } - else - { - $oObj = MetaModel::GetObjectByName($sClass, $id, false /* MustBeFound */); - } - utils::PopArchiveMode(); - if (is_null($oObj)) - { - $oP->P(Dict::S('UI:ObjectDoesNotExist')); - } - else - { - SetObjectBreadCrumbEntry($oObj, $oP); - $oP->P(Dict::S('UI:ObjectArchived')); - } - } - else - { - try - { - $oObj->Reload(); - } - catch(Exception $e) - { - // Probably not allowed to see this instance of a derived class - - // Check anyhow if there is a message for this object (like you've just created it) - $sMessageKey = $sClass.'::'.$id; - DisplayMessages($sMessageKey, $oP); - - $oObj = null; - $oP->set_title(Dict::S('UI:ErrorPageTitle')); - $oP->P(Dict::S('UI:ObjectDoesNotExist')); - } - if (!is_null($oObj)) - { - SetObjectBreadCrumbEntry($oObj, $oP); - DisplayDetails($oP, $sClass, $oObj, $id); - } - } - break; - - case 'release_lock_and_details': - $oP->DisableBreadCrumb(); - $sClass = utils::ReadParam('class', ''); - $id = utils::ReadParam('id', ''); - $oObj = MetaModel::GetObject($sClass, $id); - $sToken = utils::ReadParam('token', ''); - if ($sToken != '') - { - iTopOwnershipLock::ReleaseLock($sClass, $id, $sToken); - } - cmdbAbstractObject::ReloadAndDisplay($oP, $oObj, array('operation' => 'details')); - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'search_oql': // OQL query - $sOQLClass = utils::ReadParam('oql_class', '', false, 'class'); - $sBaseClass = utils::ReadParam('base_class', $sOQLClass, false, 'class'); - $sOQLClause = utils::ReadParam('oql_clause', '', false, 'raw_data'); - $sFormat = utils::ReadParam('format', ''); - $bSearchForm = utils::ReadParam('search_form', true); - $sTitle = utils::ReadParam('title', 'UI:SearchResultsPageTitle'); - if (empty($sOQLClass)) - { - throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'oql_class')); - } - $oP->set_title(Dict::S($sTitle)); - $oP->add('

'.Dict::S($sTitle).'

'); - $sOQL = "SELECT $sOQLClass $sOQLClause"; - try - { - $oFilter = DBObjectSearch::FromOQL($sOQL); - DisplaySearchSet($oP, $oFilter, $bSearchForm, $sBaseClass, $sFormat); - } - catch(CoreException $e) - { - $oFilter = new DBObjectSearch($sOQLClass); - $oSet = new DBObjectSet($oFilter); - if ($bSearchForm) - { - $oBlock = new DisplayBlock($oFilter, 'search', false); - $oBlock->Display($oP, 0); - } - $oP->P(''.Dict::Format('UI:Error:IncorrectOQLQuery_Message', $e->getHtmlDesc()).''); - } - catch(Exception $e) - { - $oP->P(''.Dict::Format('UI:Error:AnErrorOccuredWhileRunningTheQuery_Message', $e->getMessage()).''); - } - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'search_form': // Search form - $sClass = utils::ReadParam('class', '', false, 'class'); - $sFormat = utils::ReadParam('format', 'html'); - $bSearchForm = utils::ReadParam('search_form', true); - $bDoSearch = utils::ReadParam('do_search', true); - if (empty($sClass)) - { - throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); - } - $oP->set_title(Dict::S('UI:SearchResultsPageTitle')); - $oFilter = new DBObjectSearch($sClass); - DisplaySearchSet($oP, $oFilter, $bSearchForm, '' /* sBaseClass */, $sFormat, $bDoSearch, true /* Search Form Expanded */); - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'search': // Serialized DBSearch - $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); - $sFormat = utils::ReadParam('format', ''); - $bSearchForm = utils::ReadParam('search_form', true); - if (empty($sFilter)) - { - throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'filter')); - } - $oP->set_title(Dict::S('UI:SearchResultsPageTitle')); - $oFilter = DBSearch::unserialize($sFilter); // TO DO : check that the filter is valid - $oFilter->UpdateContextFromUser(); - DisplaySearchSet($oP, $oFilter, $bSearchForm, '' /* sBaseClass */, $sFormat); - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'full_text': // Global "google-like" search - $oP->DisableBreadCrumb(); - $sFullText = trim(utils::ReadParam('text', '', false, 'raw_data')); - $iTune = utils::ReadParam('tune', 0); - if (empty($sFullText)) - { - $oP->p(Dict::S('UI:Search:NoSearch')); - } - else - { - $iErrors = 0; - - // Check if a class name/label is supplied to limit the search - $sClassName = ''; - if (preg_match('/^([^\"]+):(.+)$/', $sFullText, $aMatches)) - { - $sClassName = $aMatches[1]; - if (MetaModel::IsValidClass($sClassName)) - { - $sFullText = trim($aMatches[2]); - } - elseif ($sClassName = MetaModel::GetClassFromLabel($sClassName, false /* => not case sensitive */)) - { - $sFullText = trim($aMatches[2]); - } - } - - if (preg_match('/^"(.*)"$/', $sFullText, $aMatches)) - { - // The text is surrounded by double-quotes, remove the quotes and treat it as one single expression - $aFullTextNeedles = array($aMatches[1]); - } - else - { - // Split the text on the blanks and treat this as a search for AND AND - $aFullTextNeedles = explode(' ', $sFullText); - } - - // Check the needle length - $iMinLenth = MetaModel::GetConfig()->Get('full_text_needle_min'); - foreach ($aFullTextNeedles as $sNeedle) - { - if (strlen($sNeedle) < $iMinLenth) - { - $oP->p(Dict::Format('UI:Search:NeedleTooShort', $sNeedle, $iMinLenth)); - $iErrors++; - } - } - - // Sanity check of the accelerators - $aAccelerators = MetaModel::GetConfig()->Get('full_text_accelerators'); - foreach ($aAccelerators as $sClass => $aAccelerator) - { - try - { - $bSkip = array_key_exists('skip', $aAccelerator) ? $aAccelerator['skip'] : false; - if (!$bSkip) - { - $oSearch = DBObjectSearch::FromOQL($aAccelerator['query']); - if ($sClass != $oSearch->GetClass()) - { - $oP->p("Full text accelerator for class '$sClass': searched class mismatch (".$oSearch->GetClass().")"); - $iErrors++; - } - } - } - catch (OqlException $e) - { - $oP->p("Full text accelerator for class '$sClass': ".$e->getHtmlDesc()); - $iErrors++; - } - } - - if ($iErrors == 0) - { - $oP->set_title(Dict::S('UI:SearchResultsPageTitle')); - $sPageId = "ui-global-search"; - $sLabel = Dict::S('UI:SearchResultsTitle'); - $sDescription = Dict::S('UI:SearchResultsTitle+'); - $oP->SetBreadCrumbEntry($sPageId, $sLabel, $sDescription, '', utils::GetAbsoluteUrlAppRoot().'images/search.png'); - $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/tabularfieldsselector.js'); - $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.dragtable.js'); - $oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/dragtable.css'); - $oP->add("
\n"); - $oP->add("
\n"); - $oP->add(' '.Dict::Format('UI:Search:Ongoing', htmlentities($sFullText, ENT_QUOTES, 'UTF-8')).''); - $oP->add("
\n"); - $oP->add("
\n"); - $oP->add("
 
\n"); - $oP->add("

".Dict::Format('UI:FullTextSearchTitle_Text', htmlentities($sFullText, ENT_QUOTES, 'UTF-8'))."

"); - $oP->add("
\n"); - $oP->add("
\n"); - $sJSClass = addslashes($sClassName); - $sJSNeedles = json_encode($aFullTextNeedles); - $oP->add_ready_script( -<< 0) - { - $oP->add_script("var oTimeStatistics = {};"); - } - } - } - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'modify': // Form to modify an object - $oP->DisableBreadCrumb(); - $sClass = utils::ReadParam('class', '', false, 'class'); - $id = utils::ReadParam('id', ''); - if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! - { - throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); - } - // Check if the user can modify this object - $oObj = MetaModel::GetObject($sClass, $id, false /* MustBeFound */); - if (is_null($oObj)) - { - $oP->set_title(Dict::S('UI:ErrorPageTitle')); - $oP->P(Dict::S('UI:ObjectDoesNotExist')); - } - else - { - // The object could be read - check if it is allowed to modify it - $oSet = CMDBObjectSet::FromObject($oObj); - if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_NO) - { - throw new SecurityException('User not allowed to modify this object', array('class' => $sClass, 'id' => $id)); - } - // Note: code duplicated to the case 'apply_modify' when a data integrity issue has been found - $oObj->DisplayModifyForm($oP, array('wizard_container' => 1)); // wizard_container: Display the blue borders and the title above the form - } - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'select_for_modify_all': // Select the list of objects to be modified (bulk modify) - $oP->DisableBreadCrumb(); - $oP->set_title(Dict::S('UI:ModifyAllPageTitle')); - $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); - if (empty($sFilter)) - { - throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'filter')); - } - $oFilter = DBObjectSearch::unserialize($sFilter); // TO DO : check that the filter is valid - // Add user filter - $oFilter->UpdateContextFromUser(); - $sClass = $oFilter->GetClass(); - $oChecker = new ActionChecker($oFilter, UR_ACTION_BULK_MODIFY); - $oP->add("

".Dict::S('UI:ModifyAllPageTitle')."

\n"); - - DisplayMultipleSelectionForm($oP, $oFilter, 'form_for_modify_all', $oChecker); - break; - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'form_for_modify_all': // Form to modify multiple objects (bulk modify) - $oP->DisableBreadCrumb(); - $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); - $sClass = utils::ReadParam('class', '', false, 'class'); - $oFullSetFilter = DBObjectSearch::unserialize($sFilter); - // Add user filter - $oFullSetFilter->UpdateContextFromUser(); - $aSelectedObj = utils::ReadMultipleSelection($oFullSetFilter); - $sCancelUrl = "./UI.php?operation=search&filter=".urlencode($sFilter)."&".$oAppContext->GetForLink(); - $aContext = array('filter' => $sFilter); - cmdbAbstractObject::DisplayBulkModifyForm($oP, $sClass, $aSelectedObj, 'preview_or_modify_all', $sCancelUrl, array(), $aContext); - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'preview_or_modify_all': // Preview or apply bulk modify - $oP->DisableBreadCrumb(); - $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); - $oFilter = DBObjectSearch::unserialize($sFilter); // TO DO : check that the filter is valid - // Add user filter - $oFilter->UpdateContextFromUser(); - $oChecker = new ActionChecker($oFilter, UR_ACTION_BULK_MODIFY); - - $sClass = utils::ReadParam('class', '', false, 'class'); - $bPreview = utils::ReadParam('preview_mode', ''); - $sSelectedObj = utils::ReadParam('selectObj', '', false, 'raw_data'); - if ( empty($sClass) || empty($sSelectedObj)) // TO DO: check that the class name is valid ! - { - throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'selectObj')); - } - $aSelectedObj = explode(',', $sSelectedObj); - $sCancelUrl = "./UI.php?operation=search&filter=".urlencode($sFilter)."&".$oAppContext->GetForLink(); - $aContext = array( - 'filter' => $sFilter, - 'selectObj' => $sSelectedObj, - ); - cmdbAbstractObject::DoBulkModify($oP, $sClass, $aSelectedObj, 'preview_or_modify_all', $bPreview, $sCancelUrl, $aContext); - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'new': // Form to create a new object - $oP->DisableBreadCrumb(); - $sClass = utils::ReadParam('class', '', false, 'class'); - $sStateCode = utils::ReadParam('state', ''); - $bCheckSubClass = utils::ReadParam('checkSubclass', true); - if ( empty($sClass) ) - { - throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); - } - -/* - $aArgs = utils::ReadParam('default', array(), false, 'raw_data'); - $aContext = $oAppContext->GetAsHash(); - foreach( $oAppContext->GetNames() as $key) - { - $aArgs[$key] = $oAppContext->GetCurrentValue($key); - } -*/ - // If the specified class has subclasses, ask the user an instance of which class to create - $aSubClasses = MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself - $aPossibleClasses = array(); - $sRealClass = ''; - if ($bCheckSubClass) - { - foreach($aSubClasses as $sCandidateClass) - { - if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) - { - $aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass); - } - } - // Only one of the subclasses can be instantiated... - if (count($aPossibleClasses) == 1) - { - $aKeys = array_keys($aPossibleClasses); - $sRealClass = $aKeys[0]; - } - } - else - { - $sRealClass = $sClass; - } - - if (!empty($sRealClass)) - { - // Display the creation form - $sClassLabel = MetaModel::GetName($sRealClass); - // Note: some code has been duplicated to the case 'apply_new' when a data integrity issue has been found - $oP->set_title(Dict::Format('UI:CreationPageTitle_Class', $sClassLabel)); - $oP->add("

".MetaModel::GetClassIcon($sRealClass)." ".Dict::Format('UI:CreationTitle_Class', $sClassLabel)."

\n"); - $oP->add("
\n"); - - // Set all the default values in an object and clone this "default" object - $oObjToClone = MetaModel::NewObject($sRealClass); - - // 1st - set context values - $oAppContext->InitObjectFromContext($oObjToClone); - - // 2nd - set values from the page argument 'default' - $oObjToClone->UpdateObjectFromArg('default'); - - cmdbAbstractObject::DisplayCreationForm($oP, $sRealClass, $oObjToClone, array()); - $oP->add("
\n"); - } - else - { - // Select the derived class to create - $sClassLabel = MetaModel::GetName($sClass); - $oP->add("

".MetaModel::GetClassIcon($sClass)." ".Dict::Format('UI:CreationTitle_Class', $sClassLabel)."

\n"); - $oP->add("
\n"); - $oP->add('
'); - $oP->add('

'.Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel)); - $aDefaults = utils::ReadParam('default', array(), false, 'raw_data'); - $oP->add($oAppContext->GetForForm()); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - foreach($aDefaults as $key => $value) - { - if (is_array($value)) - { - foreach($value as $key2 => $value2) - { - if (is_array($value2)) - { - foreach($value2 as $key3 => $value3) - { - $sValue = htmlentities($value3, ENT_QUOTES, 'UTF-8'); - $oP->add("\n"); - } - } - else - { - $sValue = htmlentities($value2, ENT_QUOTES, 'UTF-8'); - $oP->add("\n"); - } - } - } - else - { - $sValue = htmlentities($value, ENT_QUOTES, 'UTF-8'); - $oP->add("\n"); - } - } - $oP->add(''); - $oP->add(" 

"); - $oP->add('
'); - $oP->add("
\n"); - } - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'apply_modify': // Applying the modifications to an existing object - $oP->DisableBreadCrumb(); - $sClass = utils::ReadPostedParam('class', ''); - $sClassLabel = MetaModel::GetName($sClass); - $id = utils::ReadPostedParam('id', ''); - $sTransactionId = utils::ReadPostedParam('transaction_id', ''); - if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! - { - throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); - } - $bDisplayDetails = true; - $oObj = MetaModel::GetObject($sClass, $id, false); - if ($oObj == null) - { - $bDisplayDetails = false; - $oP->set_title(Dict::S('UI:ErrorPageTitle')); - $oP->P(Dict::S('UI:ObjectDoesNotExist')); - } - elseif (!utils::IsTransactionValid($sTransactionId, false)) - { - $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding - $oP->p("".Dict::S('UI:Error:ObjectAlreadyUpdated')."\n"); - } - else - { - $oObj->UpdateObjectFromPostedForm(); - $sMessage = ''; - $sSeverity = 'ok'; - - if (!$oObj->IsModified()) - { - $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding - $sMessage = Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); - $sSeverity = 'info'; - } - else - { - list($bRes, $aIssues) = $oObj->CheckToWrite(); - if ($bRes) - { - try - { - CMDBSource::Query('START TRANSACTION'); - $oObj->DBUpdate(); - CMDBSource::Query('COMMIT'); - $sMessage = Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); - $sSeverity = 'ok'; - } - catch(DeleteException $e) - { - CMDBSource::Query('ROLLBACK'); - // Say two things: 1) Don't be afraid nothing was modified - $sMessage = Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); - $sSeverity = 'info'; - cmdbAbstractObject::SetSessionMessage(get_class($oObj), $oObj->GetKey(), 'UI:Class_Object_NotUpdated', $sMessage, $sSeverity, 0, true /* must not exist */); - // 2) Ok, there was some trouble indeed - $sMessage = $e->getMessage(); - $sSeverity = 'error'; - $bDisplayDetails = true; - } - utils::RemoveTransaction($sTransactionId); - - } - else - { - $bDisplayDetails = false; - // Found issues, explain and give the user a second chance - // - $oObj->DisplayModifyForm($oP, array('wizard_container' => true)); // wizard_container: display the wizard border and the title - $sIssueDesc = Dict::Format('UI:ObjectCouldNotBeWritten', implode(', ', $aIssues)); - $oP->add_ready_script("alert('".addslashes($sIssueDesc)."');"); - } - } - } - if ($bDisplayDetails) - { - $oObj = MetaModel::GetObject(get_class($oObj), $oObj->GetKey()); //Workaround: reload the object so that the linkedset are displayed properly - $sNextAction = utils::ReadPostedParam('next_action', ''); - if (!empty($sNextAction)) - { - ApplyNextAction($oP, $oObj, $sNextAction); - } - else - { - // Nothing more to do - ReloadAndDisplay($oP, $oObj, 'update', $sMessage, $sSeverity); - } - - $bLockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled'); - if ($bLockEnabled) - { - // Release the concurrent lock, if any - $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, false, 'raw_data'); - if ($sOwnershipToken !== null) - { - // We're done, let's release the lock - iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken); - } - } - } - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'select_for_deletion': // Select multiple objects for deletion - $oP->DisableBreadCrumb(); - $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); - if (empty($sFilter)) - { - throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'filter')); - } - $oP->set_title(Dict::S('UI:BulkDeletePageTitle')); - $oP->add("

".Dict::S('UI:BulkDeleteTitle')."

\n"); - $oFilter = DBSearch::unserialize($sFilter); // TO DO : check that the filter is valid - $oFilter->UpdateContextFromUser(); - $oChecker = new ActionChecker($oFilter, UR_ACTION_BULK_DELETE); - DisplayMultipleSelectionForm($oP, $oFilter, 'bulk_delete', $oChecker); - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'bulk_delete_confirmed': // Confirm bulk deletion of objects - $oP->DisableBreadCrumb(); - $sTransactionId = utils::ReadPostedParam('transaction_id', ''); - if (!utils::IsTransactionValid($sTransactionId)) - { - throw new ApplicationException(Dict::S('UI:Error:ObjectsAlreadyDeleted')); - } - // Fall through - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'delete': - case 'bulk_delete': // Actual bulk deletion (if confirmed) - $oP->DisableBreadCrumb(); - $sClass = utils::ReadParam('class', '', false, 'class'); - $sClassLabel = MetaModel::GetName($sClass); - $aObjects = array(); - if ($operation == 'delete') - { - // Single object - $id = utils::ReadParam('id', ''); - $oObj = MetaModel::GetObject($sClass, $id); - $aObjects[] = $oObj; - if (!UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, DBObjectSet::FromObject($oObj))) - { - throw new SecurityException(Dict::Format('UI:Error:DeleteNotAllowedOn_Class', $sClassLabel)); - } - } - else - { - // Several objects - $sFilter = utils::ReadPostedParam('filter', ''); - $oFullSetFilter = DBObjectSearch::unserialize($sFilter); - // Add user filter - $oFullSetFilter->UpdateContextFromUser(); - $aSelectObject = utils::ReadMultipleSelection($oFullSetFilter); - if ( empty($sClass) || empty($aSelectObject)) // TO DO: check that the class name is valid ! - { - throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'selectObject[]')); - } - foreach($aSelectObject as $iId) - { - $aObjects[] = MetaModel::GetObject($sClass, $iId); - } - if (count($aObjects) == 1) - { - if (!UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, DBObjectSet::FromArray($sClass, $aObjects))) - { - throw new SecurityException(Dict::Format('UI:Error:BulkDeleteNotAllowedOn_Class', $sClassLabel)); - } - } - else - { - if (!UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, DBObjectSet::FromArray($sClass, $aObjects))) - { - throw new SecurityException(Dict::Format('UI:Error:BulkDeleteNotAllowedOn_Class', $sClassLabel)); - } - $oP->set_title(Dict::S('UI:BulkDeletePageTitle')); - } - } - // Go for the common part... (delete single, delete bulk, delete confirmed) - cmdbAbstractObject::DeleteObjects($oP, $sClass, $aObjects, ($operation != 'bulk_delete_confirmed'), 'bulk_delete_confirmed'); - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'apply_new': // Creation of a new object - $oP->DisableBreadCrumb(); - $sClass = utils::ReadPostedParam('class', '', 'class'); - $sClassLabel = MetaModel::GetName($sClass); - $sTransactionId = utils::ReadPostedParam('transaction_id', ''); - if ( empty($sClass) ) // TO DO: check that the class name is valid ! - { - throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); - } - if (!utils::IsTransactionValid($sTransactionId, false)) - { - $oP->p("".Dict::S('UI:Error:ObjectAlreadyCreated')."\n"); - } - else - { - $oObj = MetaModel::NewObject($sClass); - $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); - if (!empty($sStateAttCode)) - { - $sTargetState = utils::ReadPostedParam('obj_state', ''); - if ($sTargetState != '') - { - $oObj->Set($sStateAttCode, $sTargetState); - } - } - $oObj->UpdateObjectFromPostedForm(); - } - if (isset($oObj) && is_object($oObj)) - { - $sClass = get_class($oObj); - $sClassLabel = MetaModel::GetName($sClass); - - list($bRes, $aIssues) = $oObj->CheckToWrite(); - if ($bRes) - { - $oObj->DBInsertNoReload(); // No need to reload - utils::RemoveTransaction($sTransactionId); - $oP->set_title(Dict::S('UI:PageTitle:ObjectCreated')); - - // Compute the name, by reloading the object, even if it disappeared from the silo - $oObj = MetaModel::GetObject($sClass, $oObj->GetKey(), true /* Must be found */, true /* Allow All Data*/); - $sName = $oObj->GetName(); - $sMessage = Dict::Format('UI:Title:Object_Of_Class_Created', $sName, $sClassLabel); - - $sNextAction = utils::ReadPostedParam('next_action', ''); - if (!empty($sNextAction)) - { - $oP->add("

$sMessage

"); - ApplyNextAction($oP, $oObj, $sNextAction); - } - else - { - // Nothing more to do - ReloadAndDisplay($oP, $oObj, 'create', $sMessage, 'ok'); - } - } - else - { - // Found issues, explain and give the user a second chance - // - $oP->set_title(Dict::Format('UI:CreationPageTitle_Class', $sClassLabel)); - $oP->add("

".MetaModel::GetClassIcon($sClass)." ".Dict::Format('UI:CreationTitle_Class', $sClassLabel)."

\n"); - $oP->add("
\n"); - cmdbAbstractObject::DisplayCreationForm($oP, $sClass, $oObj); - $oP->add("
\n"); - $sIssueDesc = Dict::Format('UI:ObjectCouldNotBeWritten', implode(', ', $aIssues)); - $oP->add_ready_script("alert('".addslashes($sIssueDesc)."');"); - } - } - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'select_bulk_stimulus': // Form displayed when applying a stimulus to many objects - $oP->DisableBreadCrumb(); - $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); - $sStimulus = utils::ReadParam('stimulus', ''); - $sState = utils::ReadParam('state', ''); - if (empty($sFilter) || empty($sStimulus) || empty($sState)) - { - throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'filter', 'stimulus', 'state')); - } - $oFilter = DBObjectSearch::unserialize($sFilter); - $oFilter->UpdateContextFromUser(); - $sClass = $oFilter->GetClass(); - $aStimuli = MetaModel::EnumStimuli($sClass); - $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); - $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); - $oP->set_title($sActionLabel); - $oP->add(''); - - $oChecker = new StimulusChecker($oFilter, $sState, $sStimulus); - $aExtraFormParams = array('stimulus' => $sStimulus, 'state' => $sState); - DisplayMultipleSelectionForm($oP, $oFilter, 'bulk_stimulus', $oChecker, $aExtraFormParams); - break; - - case 'bulk_stimulus': - $oP->DisableBreadCrumb(); - $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); - $sStimulus = utils::ReadParam('stimulus', ''); - $sState = utils::ReadParam('state', ''); - if (empty($sFilter) || empty($sStimulus) || empty($sState)) - { - throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'filter', 'stimulus', 'state')); - } - $oFilter = DBObjectSearch::unserialize($sFilter); - // Add user filter - $oFilter->UpdateContextFromUser(); - $sClass = $oFilter->GetClass(); - $aSelectObject = utils::ReadMultipleSelection($oFilter); - if (count($aSelectObject) == 0) - { - // Nothing to do, no object was selected ! - throw new ApplicationException(Dict::S('UI:BulkAction:NoObjectSelected')); - } - else - { - $aTransitions = MetaModel::EnumTransitions($sClass, $sState); - $aStimuli = MetaModel::EnumStimuli($sClass); - - $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); - $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); - $sTargetState = $aTransitions[$sStimulus]['target_state']; - $aStates = MetaModel::EnumStates($sClass); - $aTargetStateDef = $aStates[$sTargetState]; - - $oP->set_title(Dict::Format('UI:StimulusModify_N_ObjectsOf_Class', $sActionLabel, count($aSelectObject), $sClass)); - $oP->add(''); - - $aExpectedAttributes = MetaModel::GetTransitionAttributes($sClass, $sStimulus, $sState); - $aDetails = array(); - $iFieldIndex = 0; - $aFieldsMap = array(); - $aValues = array(); - $aObjects = array(); - foreach($aSelectObject as $iId) - { - $aObjects[] = MetaModel::GetObject($sClass, $iId); - } - $oSet = DBObjectSet::FromArray($sClass, $aObjects); - $oObj = $oSet->ComputeCommonObject($aValues); - $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); - $oObj->Set($sStateAttCode,$sTargetState); - $sReadyScript = ''; - foreach($aExpectedAttributes as $sAttCode => $iExpectCode) - { - // Prompt for an attribute if - // - the attribute must be changed or must be displayed to the user for confirmation - // - or the field is mandatory and currently empty - if ( ($iExpectCode & (OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) || - (($iExpectCode & OPT_ATT_MANDATORY) && ($oObj->Get($sAttCode) == '')) ) - { - $aAttributesDef = MetaModel::ListAttributeDefs($sClass); - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - $aPrerequisites = MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode); // List of attributes that are needed for the current one - if (count($aPrerequisites) > 0) - { - // When 'enabling' a field, all its prerequisites must be enabled too - $sFieldList = "['".implode("','", $aPrerequisites)."']"; - $oP->add_ready_script("$('#enable_{$sAttCode}').bind('change', function(evt, sFormId) { return PropagateCheckBox( this.checked, $sFieldList, true); } );\n"); - } - $aDependents = MetaModel::GetDependentAttributes($sClass, $sAttCode); // List of attributes that are needed for the current one - if (count($aDependents) > 0) - { - // When 'disabling' a field, all its dependent fields must be disabled too - $sFieldList = "['".implode("','", $aDependents)."']"; - $oP->add_ready_script("$('#enable_{$sAttCode}').bind('change', function(evt, sFormId) { return PropagateCheckBox( this.checked, $sFieldList, false); } );\n"); - } - $aArgs = array('this' => $oObj); - $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $oObj->Get($sAttCode), $oObj->GetEditValue($sAttCode), $sAttCode, '', $iExpectCode, $aArgs); - $sComments = ''; - if (!isset($aValues[$sAttCode])) - { - $aValues[$sAttCode] = array(); - } - if (count($aValues[$sAttCode]) == 1) - { - $sComments .= '
1
'; - } - else - { - // Non-homogenous value - $iMaxCount = 5; - $sTip = "

".Dict::Format('UI:BulkModify_Count_DistinctValues', count($aValues[$sAttCode]))."

    "; - $index = 0; - foreach($aValues[$sAttCode] as $sCurrValue => $aVal) - { - $sDisplayValue = empty($aVal['display']) ? ''.Dict::S('Enum:Undefined').'' : str_replace(array("\n", "\r"), " ", $aVal['display']); - $sTip .= "
  • ".Dict::Format('UI:BulkModify:Value_Exists_N_Times', $sDisplayValue, $aVal['count'])."
  • "; - $index++; - if ($iMaxCount == $index) - { - $sTip .= "
  • ".Dict::Format('UI:BulkModify:N_MoreValues', count($aValues[$sAttCode]) - $iMaxCount)."
  • "; - break; - } - } - $sTip .= "

"; - $sTip = addslashes($sTip); - $sReadyScript .= "$('#multi_values_$sAttCode').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );\n"; - $sComments .= '
'.count($aValues[$sAttCode]).'
'; - } - $aDetails[] = array('label' => ''.$oAttDef->GetLabel().'', 'value' => "$sHTMLValue", 'comments' => $sComments); - $aFieldsMap[$sAttCode] = $sAttCode; - $iFieldIndex++; - } - } - $sButtonsPosition = MetaModel::GetConfig()->Get('buttons_position'); - if ($sButtonsPosition == 'bottom') - { - // bottom: Displays the ticket details BEFORE the actions - $oP->add('
'); - $oObj->DisplayBareProperties($oP); - $oP->add('
'); - } - $oP->add("
\n"); - $oP->add("
\n"); - $oP->add("
\n"); - $oP->details($aDetails); - $oP->add("
\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add($oAppContext->GetForForm()); - $oP->add("\n"); - $sURL = "./UI.php?operation=search&filter=".urlencode($sFilter)."&".$oAppContext->GetForLink(); - $oP->add("    \n"); - $oP->add("\n"); - $oP->add("
\n"); - $oP->add("
\n"); - if ($sButtonsPosition != 'bottom') - { - // top or both: Displays the ticket details AFTER the actions - $oP->add('
'); - $oObj->DisplayBareProperties($oP); - $oP->add('
'); - } - $iFieldsCount = count($aFieldsMap); - $sJsonFieldsMap = json_encode($aFieldsMap); - - $oP->add_script( -<<add_ready_script( -<<DisableBreadCrumb(); - $bPreviewMode = utils::ReadPostedParam('preview_mode', false); - $sFilter = utils::ReadPostedParam('filter', '', false, 'raw_data'); - $sStimulus = utils::ReadPostedParam('stimulus', ''); - $sState = utils::ReadPostedParam('state', ''); - $sSelectObject = utils::ReadPostedParam('selectObject', '', false, 'raw_data'); - $aSelectObject = explode(',', $sSelectObject); - - if (empty($sFilter) || empty($sStimulus) || empty($sState)) - { - throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'filter', 'stimulus', 'state')); - } - $sTransactionId = utils::ReadPostedParam('transaction_id', ''); - if (!utils::IsTransactionValid($sTransactionId)) - { - $oP->p(Dict::S('UI:Error:ObjectAlreadyUpdated')); - } - else - { - // For archiving the modification - $oFilter = DBObjectSearch::unserialize($sFilter); - // Add user filter - $oFilter->UpdateContextFromUser(); - $sClass = $oFilter->GetClass(); - $aObjects = array(); - foreach($aSelectObject as $iId) - { - $aObjects[] = MetaModel::GetObject($sClass, $iId); - } - - $aTransitions = MetaModel::EnumTransitions($sClass, $sState); - $aStimuli = MetaModel::EnumStimuli($sClass); - - $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); - $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); - - $oP->set_title(Dict::Format('UI:StimulusModify_N_ObjectsOf_Class', $sActionLabel, count($aObjects), $sClass)); - $oP->add(''); - - $oSet = DBObjectSet::FromArray($sClass, $aObjects); - - // For reporting - $aHeaders = array( - 'object' => array('label' => MetaModel::GetName($sClass), 'description' => Dict::S('UI:ModifiedObject')), - 'status' => array('label' => Dict::S('UI:BulkModifyStatus'), 'description' => Dict::S('UI:BulkModifyStatus+')), - 'errors' => array('label' => Dict::S('UI:BulkModifyErrors'), 'description' => Dict::S('UI:BulkModifyErrors+')), - ); - $aRows = array(); - while ($oObj = $oSet->Fetch()) - { - $sError = Dict::S('UI:BulkModifyStatusOk'); - try - { - $aTransitions = $oObj->EnumTransitions(); - $aStimuli = MetaModel::EnumStimuli($sClass); - if (!isset($aTransitions[$sStimulus])) - { - throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, $oObj->GetName(), $oObj->GetStateLabel())); - } - else - { - $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); - $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); - $sTargetState = $aTransitions[$sStimulus]['target_state']; - $aExpectedAttributes = $oObj->GetTransitionAttributes($sStimulus /* cureent state */); - $aDetails = array(); - $aErrors = array(); - foreach($aExpectedAttributes as $sAttCode => $iExpectCode) - { - $iFlags = $oObj->GetTransitionFlags($sAttCode, $sStimulus); - if (($iExpectCode & (OPT_ATT_MUSTCHANGE|OPT_ATT_MUSTPROMPT)) || ($oObj->Get($sAttCode) == '') ) - { - $paramValue = utils::ReadPostedParam("attr_$sAttCode", '', 'raw_data'); - if ( ($iFlags & OPT_ATT_SLAVE) && ($paramValue != $oObj->Get($sAttCode)) ) - { - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - $aErrors[] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $oAttDef->GetLabel()); - unset($aExpectedAttributes[$sAttCode]); - } - } - } - - $oObj->UpdateObjectFromPostedForm('', array_keys($aExpectedAttributes), $aExpectedAttributes); - - if (count($aErrors) == 0) - { - if ($oObj->ApplyStimulus($sStimulus)) - { - list($bResult, $aErrors) = $oObj->CheckToWrite(); - $sStatus = $bResult ? Dict::S('UI:BulkModifyStatusModified') : Dict::S('UI:BulkModifyStatusSkipped'); - if ($bResult) - { - $oObj->DBUpdate(); - } - else - { - $sError = '

'.implode('

',$aErrors)."

\n"; - } - } - else - { - $sStatus = Dict::S('UI:BulkModifyStatusSkipped'); - $sError = '

'.Dict::S('UI:FailedToApplyStimuli')."

\n"; - } - } - else - { - $sStatus = Dict::S('UI:BulkModifyStatusSkipped'); - $sError = '

'.implode('

',$aErrors)."

\n"; - } - } - } - catch(Exception $e) - { - $sError = $e->getMessage(); - $sStatus = Dict::S('UI:BulkModifyStatusSkipped'); - } - $aRows[] = array( - 'object' => $oObj->GetHyperlink(), - 'status' => $sStatus, - 'errors' => $sError, - ); - } - $oP->Table($aHeaders, $aRows); - // Back to the list - $sURL = "./UI.php?operation=search&filter=".urlencode($sFilter)."&".$oAppContext->GetForLink(); - $oP->add(''); - } - break; - - case 'stimulus': // Form displayed when applying a stimulus (state change) - $oP->DisableBreadCrumb(); - $sClass = utils::ReadParam('class', '', false, 'class'); - $id = utils::ReadParam('id', ''); - $sStimulus = utils::ReadParam('stimulus', ''); - if ( empty($sClass) || empty($id) || empty($sStimulus) ) // TO DO: check that the class name is valid ! - { - throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus')); - } - $oObj = MetaModel::GetObject($sClass, $id, false); - if ($oObj != null) - { - $oObj->DisplayStimulusForm($oP, $sStimulus); - } - else - { - $oP->set_title(Dict::S('UI:ErrorPageTitle')); - $oP->P(Dict::S('UI:ObjectDoesNotExist')); - } - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'apply_stimulus': // Actual state change - $oP->DisableBreadCrumb(); - $sClass = utils::ReadPostedParam('class', ''); - $id = utils::ReadPostedParam('id', ''); - $sTransactionId = utils::ReadPostedParam('transaction_id', ''); - $sStimulus = utils::ReadPostedParam('stimulus', ''); - if ( empty($sClass) || empty($id) || empty($sStimulus) ) // TO DO: check that the class name is valid ! - { - throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus')); - } - $oObj = MetaModel::GetObject($sClass, $id, false); - if ($oObj != null) - { - $aTransitions = $oObj->EnumTransitions(); - $aStimuli = MetaModel::EnumStimuli($sClass); - $sMessage = ''; - $sSeverity = 'ok'; - $bDisplayDetails = true; - if (!isset($aTransitions[$sStimulus])) - { - throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, $oObj->GetName(), $oObj->GetStateLabel())); - } - if (!utils::IsTransactionValid($sTransactionId)) - { - $sMessage = Dict::S('UI:Error:ObjectAlreadyUpdated'); - $sSeverity = 'info'; - } - else - { - $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); - $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); - $sTargetState = $aTransitions[$sStimulus]['target_state']; - $aExpectedAttributes = $oObj->GetTransitionAttributes($sStimulus /*, current state*/); - $aDetails = array(); - $aErrors = array(); - foreach($aExpectedAttributes as $sAttCode => $iExpectCode) - { - $iFlags = $oObj->GetTransitionFlags($sAttCode, $sStimulus); - if (($iExpectCode & (OPT_ATT_MUSTCHANGE|OPT_ATT_MUSTPROMPT)) || ($oObj->Get($sAttCode) == '') ) - { - $paramValue = utils::ReadPostedParam("attr_$sAttCode", '', 'raw_data'); - if ( ($iFlags & OPT_ATT_SLAVE) && ($paramValue != $oObj->Get($sAttCode))) - { - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - $aErrors[] = Dict::Format('UI:AttemptingToChangeASlaveAttribute_Name', $oAttDef->GetLabel()); - unset($aExpectedAttributes[$sAttCode]); - } - } - } - - $oObj->UpdateObjectFromPostedForm('', array_keys($aExpectedAttributes), $aExpectedAttributes); - - if (count($aErrors) == 0) - { - $sIssues = ''; - $bApplyStimulus = true; - list($bRes, $aIssues) = $oObj->CheckToWrite(); // Check before trying to write the object - if ($bRes) - { - try - { - $bApplyStimulus = $oObj->ApplyStimulus($sStimulus); // will write the object in the DB - } - catch(CoreException $e) - { - // Rollback to the previous state... by reloading the object from the database and applying the modifications again - $oObj = MetaModel::GetObject(get_class($oObj), $oObj->GetKey()); - $oObj->UpdateObjectFromPostedForm('', array_keys($aExpectedAttributes), $aExpectedAttributes); - $sIssues = $e->getMessage(); - } - } - else - { - $sIssues = implode(' ', $aIssues); - } - - if (!$bApplyStimulus) - { - $sMessage = Dict::S('UI:FailedToApplyStimuli'); - $sSeverity = 'error'; - } - else if ($sIssues != '') - { - - $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, false, 'raw_data'); - if ($sOwnershipToken !== null) - { - // Release the concurrent lock, if any, a new lock will be re-acquired by DisplayStimulusForm below - iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken); - } - - $bDisplayDetails = false; - // Found issues, explain and give the user a second chance - // - $oObj->DisplayStimulusForm($oP, $sStimulus); - $sIssueDesc = Dict::Format('UI:ObjectCouldNotBeWritten',$sIssues); - $oP->add_ready_script("alert('".addslashes($sIssueDesc)."');"); - } - else - { - $sMessage = Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); - $sSeverity = 'ok'; - utils::RemoveTransaction($sTransactionId); - $bLockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled'); - if ($bLockEnabled) - { - // Release the concurrent lock, if any - $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, false, 'raw_data'); - if ($sOwnershipToken !== null) - { - // We're done, let's release the lock - iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken); - } - } - } - } - else - { - $sMessage = implode('

', $aErrors); - $sSeverity = 'error'; - } - } - if ($bDisplayDetails) - { - ReloadAndDisplay($oP, $oObj, 'apply_stimulus', $sMessage, $sSeverity); - } - } - else - { - $oP->set_title(Dict::S('UI:ErrorPageTitle')); - $oP->P(Dict::S('UI:ObjectDoesNotExist')); - } - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'swf_navigator': // Graphical display of the relations "impact" / "depends on" - 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'); - $sDirection = utils::ReadParam('direction', 'down'); - $iGroupingThreshold = utils::ReadParam('g', 5); - - $oObj = MetaModel::GetObject($sClass, $id); - $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth', 20); - $aSourceObjects = array($oObj); - - $oP->set_title(MetaModel::GetRelationDescription($sRelation).' '.$oObj->GetName()); - - $sPageId = "ui-relation-graph-".$sClass.'::'.$id; - $sLabel = $oObj->GetName().' '.MetaModel::GetRelationLabel($sRelation); - $sDescription = MetaModel::GetRelationDescription($sRelation).' '.$oObj->GetName(); - $oP->SetBreadCrumbEntry($sPageId, $sLabel, $sDescription); - - if ($sRelation == 'depends on') - { - $sRelation = 'impacts'; - $sDirection = 'up'; - } - if ($sDirection == 'up') - { - $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth); - } - else - { - $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth); - } - - - $aResults = $oRelGraph->GetObjectsByClass(); - $oDisplayGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down')); - - $oP->AddTabContainer('Navigator'); - $oP->SetCurrentTabContainer('Navigator'); - - $sFirstTab = MetaModel::GetConfig()->Get('impact_analysis_first_tab'); - $sContextKey = "itop-config-mgmt/relation_context/$sClass/$sRelation/$sDirection"; - - // Check if the current object supports Attachments, similar to AttachmentPlugin::IsTargetObject - $sClassForAttachment = null; - $iIdForAttachment = null; - if (class_exists('Attachment')) - { - $aAllowedClasses = MetaModel::GetModuleSetting('itop-attachments', 'allowed_classes', array('Ticket')); - foreach($aAllowedClasses as $sAllowedClass) - { - if ($oObj instanceof $sAllowedClass) - { - $iIdForAttachment = $id; - $sClassForAttachment = $sClass; - } - } - } - $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/tabularfieldsselector.js'); - $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.dragtable.js'); - $oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/dragtable.css'); - - // Display the tabs - if ($sFirstTab == 'list') - { - DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj); - $oP->SetCurrentTab(Dict::S('UI:RelationshipGraph')); - $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, array(), $sClassForAttachment, $iIdForAttachment, $sContextKey, array('this' => $oObj)); - DisplayNavigatorGroupTab($oP); - } - else - { - $oP->SetCurrentTab(Dict::S('UI:RelationshipGraph')); - $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, array(), $sClassForAttachment, $iIdForAttachment, $sContextKey, array('this' => $oObj)); - DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj); - DisplayNavigatorGroupTab($oP); - } - - $oP->SetCurrentTab(''); - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'kill_lock': - $oP->DisableBreadCrumb(); - $sClass = utils::ReadParam('class', ''); - $id = utils::ReadParam('id', ''); - iTopOwnershipLock::KillLock($sClass, $id); - $oObj = MetaModel::GetObject($sClass, $id); - ReloadAndDisplay($oP, $oObj, 'concurrent_lock_killed', Dict::S('UI:ConcurrentLockKilled'), 'info'); - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'cancel': // An action was cancelled - $oP->DisableBreadCrumb(); - $oP->set_title(Dict::S('UI:OperationCancelled')); - $oP->add('

'.Dict::S('UI:OperationCancelled').'

'); - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - default: // Menu node rendering (templates) - ApplicationMenu::LoadAdditionalMenus(); - $oMenuNode = ApplicationMenu::GetMenuNode(ApplicationMenu::GetMenuIndexById(ApplicationMenu::GetActiveNodeId())); - if (is_object($oMenuNode)) - { - $oMenuNode->RenderContent($oP, $oAppContext->GetAsHash()); - $oP->set_title($oMenuNode->GetLabel()); - } - - /////////////////////////////////////////////////////////////////////////////////////////// - - } - DisplayWelcomePopup($oP); - $oP->output(); -} -catch(CoreException $e) -{ - require_once(APPROOT.'/setup/setuppage.class.inc.php'); - $oP = new SetupPage(Dict::S('UI:PageTitle:FatalError')); - if ($e instanceof SecurityException) - { - $oP->add("

".Dict::S('UI:SystemIntrusion')."

\n"); - } - else - { - $oP->add("

".Dict::S('UI:FatalErrorMessage')."

\n"); - } - $oP->error(Dict::Format('UI:Error_Details', $e->getHtmlDesc())); - $oP->output(); - - if (MetaModel::IsLogEnabledIssue()) - { - if (MetaModel::IsValidClass('EventIssue')) - { - try - { - $oLog = new EventIssue(); - - $oLog->Set('message', $e->getMessage()); - $oLog->Set('userinfo', ''); - $oLog->Set('issue', $e->GetIssue()); - $oLog->Set('impact', 'Page could not be displayed'); - $oLog->Set('callstack', $e->getTrace()); - $oLog->Set('data', $e->getContextData()); - $oLog->DBInsertNoReload(); - } - catch(Exception $e) - { - IssueLog::Error("Failed to log issue into the DB"); - } - } - - IssueLog::Error($e->getMessage()); - } - - // For debugging only - //throw $e; -} -catch(Exception $e) -{ - require_once(APPROOT.'/setup/setuppage.class.inc.php'); - $oP = new SetupPage(Dict::S('UI:PageTitle:FatalError')); - $oP->add("

".Dict::S('UI:FatalErrorMessage')."

\n"); - $oP->error(Dict::Format('UI:Error_Details', $e->getMessage())); - $oP->output(); - - if (MetaModel::IsLogEnabledIssue()) - { - if (MetaModel::IsValidClass('EventIssue')) - { - try - { - $oLog = new EventIssue(); - - $oLog->Set('message', $e->getMessage()); - $oLog->Set('userinfo', ''); - $oLog->Set('issue', 'PHP Exception'); - $oLog->Set('impact', 'Page could not be displayed'); - $oLog->Set('callstack', $e->getTrace()); - $oLog->Set('data', array()); - $oLog->DBInsertNoReload(); - } - catch(Exception $e) - { - IssueLog::Error("Failed to log issue into the DB"); - } - } - - IssueLog::Error($e->getMessage()); - } + + + +/** + * Main page of iTop + * + * @copyright Copyright (C) 2010-2017 Combodo SARL + * @license http://opensource.org/licenses/AGPL-3.0 + */ + + +/** + * Displays a popup welcome message, once per session at maximum + * until the user unchecks the "Display welcome at startup" + * @param WebPage $oP The current web page for the display + * @return void + */ +function DisplayWelcomePopup(WebPage $oP) +{ + if (!isset($_SESSION['welcome'])) + { + // Check, only once per session, if the popup should be displayed... + // If the user did not already ask for hiding it forever + $bPopup = appUserPreferences::GetPref('welcome_popup', true); + if ($bPopup) + { + $sTemplate = @file_get_contents('../application/templates/welcome_popup.html'); + if ($sTemplate !== false) + { + $oTemplate = new DisplayTemplate($sTemplate); + $oP->add("
"); + $oTemplate->Render($oP, array()); + $oP->add("

\n"); + $oP->add("

\n"); + $oP->add("

\n"); + $sTitle = addslashes(Dict::S('UI:WelcomeMenu:Title')); + $oP->add_ready_script( +<< ($(window).height()-70)) + { + $('#welcome_popup').height($(window).height()-70); + } +EOF +); + $_SESSION['welcome'] = 'ok'; + } + } + } +} + +/** + * Apply the 'next-action' to the given object or redirect to the page that prompts for additional information if needed + * @param $oP WebPage The page for the output + * @param $oObj CMDBObject The object to process + * @param $sNextAction string The code of the stimulus for the 'action' (i.e. Transition) to apply + */ +function ApplyNextAction(Webpage $oP, CMDBObject $oObj, $sNextAction) +{ + // Here handle the apply stimulus + $aTransitions = $oObj->EnumTransitions(); + $aStimuli = MetaModel::EnumStimuli(get_class($oObj)); + if (!isset($aTransitions[$sNextAction])) + { + // Invalid stimulus + throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sNextAction, $oObj->GetName(), $oObj->GetStateLabel())); + } + // Get the list of missing mandatory fields for the target state, considering only the changes from the previous form (i.e don't prompt twice) + $aExpectedAttributes = $oObj->GetTransitionAttributes($sNextAction); + + if (count($aExpectedAttributes) == 0) + { + // If all the mandatory fields are already present, just apply the transition silently... + if ($oObj->ApplyStimulus($sNextAction)) + { + $oObj->DBUpdate(); + } + ReloadAndDisplay($oP, $oObj); + } + else + { + // redirect to the 'stimulus' action + $oAppContext = new ApplicationContext(); +//echo "

Missing Attributes

".print_r($aExpectedAttributes, true)."

\n"; + + $oP->add_header('Location: '.utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=stimulus&class='.get_class($oObj).'&stimulus='.$sNextAction.'&id='.$oObj->getKey().'&'.$oAppContext->GetForLink()); + } +} + +function ReloadAndDisplay($oPage, $oObj, $sMessageId = '', $sMessage = '', $sSeverity = null) +{ + $oAppContext = new ApplicationContext(); + if ($sMessageId != '') + { + cmdbAbstractObject::SetSessionMessage(get_class($oObj), $oObj->GetKey(), $sMessageId, $sMessage, $sSeverity, 0, true /* must not exist */); + } + $oPage->add_header('Location: '.utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=details&class='.get_class($oObj).'&id='.$oObj->getKey().'&'.$oAppContext->GetForLink()); +} +/** + * Displays the details of an object + * @param $oP WebPage Page for the output + * @param $sClass string The name of the class of the object + * @param $oObj DBObject The object to display + * @param $id mixed Identifier of the object (name or ID) + */ +function DisplayDetails($oP, $sClass, $oObj, $id) +{ + $sClassLabel = MetaModel::GetName($sClass); + $oSearch = new DBObjectSearch($sClass); + $oBlock = new DisplayBlock($oSearch, 'search', false); + $oBlock->Display($oP, 0); + + // The object could be listed, check if it is actually allowed to view it + $oSet = CMDBObjectSet::FromObject($oObj); + if (UserRights::IsActionAllowed($sClass, UR_ACTION_READ, $oSet) == UR_ALLOWED_NO) + { + throw new SecurityException('User not allowed to view this object', array('class' => $sClass, 'id' => $id)); + } + $oP->set_title(Dict::Format('UI:DetailsPageTitle', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding + $oObj->DisplayDetails($oP); +} + +/** + * Display the session messages relative to the object identified by its "message key" (class::id) + * @param string $sMessageKey + * @param WebPage $oPage + */ +function DisplayMessages($sMessageKey, WebPage $oPage) +{ + if (array_key_exists('obj_messages', $_SESSION) && array_key_exists($sMessageKey, $_SESSION['obj_messages'])) + { + $aMessages = array(); + $aRanks = array(); + foreach ($_SESSION['obj_messages'][$sMessageKey] as $sMessageId => $aMessageData) + { + $sMsgClass = 'message_'.$aMessageData['severity']; + $aMessages[] = "
".$aMessageData['message']."
"; + $aRanks[] = $aMessageData['rank']; + } + unset($_SESSION['obj_messages'][$sMessageKey]); + array_multisort($aRanks, $aMessages); + foreach ($aMessages as $sMessage) + { + $oPage->add($sMessage); + } + } +} + +/** + * Helper to update the breadrumb for the current object + * @param DBObject $oObj + * @param WebPage $oPage + */ +function SetObjectBreadCrumbEntry(DBObject $oObj, WebPage $oPage) +{ + $sClass = get_class($oObj); // get the leaf class + $sIcon = MetaModel::GetClassIcon($sClass, false); + if ($sIcon == '') + { + $sIcon = utils::GetAbsoluteUrlAppRoot().'images/breadcrumb_object.png'; + } + $oPage->SetBreadCrumbEntry("ui-details-$sClass-".$oObj->GetKey(), $oObj->Get('friendlyname'), MetaModel::GetName($sClass).': '.$oObj->Get('friendlyname'), '', $sIcon); +} + +/** + * Displays the result of a search request + * @param $oP WebPage Web page for the output + * @param $oFilter DBSearch The search of objects to display + * @param $bSearchForm boolean Whether or not to display the search form at the top the page + * @param $sBaseClass string The base class for the search (can be different from the actual class of the results) + * @param $sFormat string The format to use for the output: csv or html + * @param $bDoSearch bool True to display the search results below the search form + * @param $bSearchFormOpen bool True to display the search form fully expanded (only if $bSearchForm of course) + */ +function DisplaySearchSet($oP, $oFilter, $bSearchForm = true, $sBaseClass = '', $sFormat = '', $bDoSearch = true, $bSearchFormOpen = false) +{ + if ($bSearchForm) + { + $aParams = array('open' => $bSearchFormOpen); + if (!empty($sBaseClass)) + { + $aParams['baseClass'] = $sBaseClass; + } + $oBlock = new DisplayBlock($oFilter, 'search', false /* Asynchronous */, $aParams); + $oBlock->Display($oP, 0); + } + if ($bDoSearch) + { + if (strtolower($sFormat) == 'csv') + { + $oBlock = new DisplayBlock($oFilter, 'csv', false); + $oBlock->Display($oP, 1); + // Adjust the size of the Textarea containing the CSV to fit almost all the remaining space + $oP->add_ready_script(" $('#1>textarea').height($('#1').parent().height() - $('#0').outerHeight() - 30).width( $('#1').parent().width() - 20);"); // adjust the size of the block + } + else + { + $oBlock = new DisplayBlock($oFilter, 'list', false); + $oBlock->Display($oP, 1); + + // Breadcrumb + //$iCount = $oBlock->GetDisplayedCount(); + $sPageId = "ui-search-".$oFilter->GetClass(); + $sLabel = MetaModel::GetName($oFilter->GetClass()); + $oP->SetBreadCrumbEntry($sPageId, $sLabel, '', '', '../images/breadcrumb-search.png'); + } + } +} + +/** + * Displays a form (checkboxes) to select the objects for which to apply a given action + * Only the objects for which the action is valid can be checked. By default all valid objects are checked + * + * @param \WebPage $oP WebPage The page for output + * @param \DBSearch $oFilter DBSearch The filter that defines the list of objects + * @param string $sNextOperation string The next operation (code) to be executed when the form is submitted + * @param ActionChecker $oChecker ActionChecker The helper class/instance used to check for which object the action is valid + * @param array $aExtraFormParams + * + * @throws \ApplicationException + */ +function DisplayMultipleSelectionForm($oP, $oFilter, $sNextOperation, $oChecker, $aExtraFormParams = array()) +{ + $oAppContext = new ApplicationContext(); + $iBulkActionAllowed = $oChecker->IsAllowed(); + $aExtraParams = array('selection_type' => 'multiple', 'selection_mode' => true, 'display_limit' => false, 'menu' => false); + if ($iBulkActionAllowed == UR_ALLOWED_DEPENDS) + { + $aExtraParams['selection_enabled'] = $oChecker->GetAllowedIDs(); + } + else if(UR_ALLOWED_NO) + { + throw new ApplicationException(Dict::Format('UI:ActionNotAllowed')); + } + + $oBlock = new DisplayBlock($oFilter, 'list', false); + $oP->add("
\n"); + $oP->add("\n"); + $oP->add("GetClass()."\">\n"); + $oP->add("Serialize()."\">\n"); + $oP->add("\n"); + foreach($aExtraFormParams as $sName => $sValue) + { + $oP->add("\n"); + } + $oP->add($oAppContext->GetForForm()); + $oBlock->Display($oP, 1, $aExtraParams); + $oP->add("  \n"); + $oP->add("
\n"); + $oP->add_ready_script("$('#1 table.listResults').trigger('check_all');"); +} + +function DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj) +{ + $oP->SetCurrentTab(Dict::S('UI:RelationshipList')); + $oP->add("
"); + $sOldRelation = $sRelation; + if (($sRelation == 'impacts') && ($sDirection == 'up')) + { + $sOldRelation = 'depends on'; + } + $oP->add("

".MetaModel::GetRelationDescription($sOldRelation).' '.$oObj->GetName()."

\n"); + $oP->add("
"); + $oP->add(''); + /* + * Content is rendered asynchronously via pages/ajax.render.php?operation=relation_lists + */ + /* + $iBlock = 1; // Zero is not a valid blockid + foreach($aResults as $sListClass => $aObjects) + { + $oSet = CMDBObjectSet::FromArray($sListClass, $aObjects); + $oP->add("
\n"); + $oP->add("

".MetaModel::GetClassIcon($sListClass)." ".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aObjects), Metamodel::GetName($sListClass))."

\n"); + $oP->add("
\n"); + $oBlock = DisplayBlock::FromObjectSet($oSet, 'list'); + $oBlock->Display($oP, $iBlock++, array('table_id' => get_class($oObj).'_'.$sRelation.'_'.$sDirection.'_'.$sListClass)); + $oP->P(' '); // Some space ? + } + */ + $oP->add("
"); + $oP->add("
"); +} + +function DisplayNavigatorGroupTab($oP) +{ + $oP->SetCurrentTab(Dict::S('UI:RelationGroups')); + $oP->add("
"); + $oP->add(''); + /* + * Content is rendered asynchronously via pages/ajax.render.php?operation=relation_groups + */ + $oP->add("
"); +} + +/*********************************************************************************** + * + * Main user interface page starts here + * + ***********************************************************************************/ +require_once('../approot.inc.php'); +require_once(APPROOT.'/application/application.inc.php'); +require_once(APPROOT.'/application/itopwebpage.class.inc.php'); +require_once(APPROOT.'/application/wizardhelper.class.inc.php'); + +require_once(APPROOT.'/application/startup.inc.php'); + +try +{ + $operation = utils::ReadParam('operation', ''); + $bPrintable = (utils::ReadParam('printable', 0) == '1'); + + $oKPI = new ExecutionKPI(); + $oKPI->ComputeAndReport('Data model loaded'); + + $oKPI = new ExecutionKPI(); + + require_once(APPROOT.'/application/loginwebpage.class.inc.php'); + $sLoginMessage = LoginWebPage::DoLogin(); // Check user rights and prompt if needed + $oAppContext = new ApplicationContext(); + + $oKPI->ComputeAndReport('User login'); + + $oP = new iTopWebPage(Dict::S('UI:WelcomeToITop'), $bPrintable); + $oP->SetMessage($sLoginMessage); + + + // All the following actions use advanced forms that require more javascript to be loaded + switch($operation) + { + case 'new': // Form to create a new object + case 'modify': // Form to modify an object + case 'apply_new': // Creation of a new object + case 'apply_modify': // Applying the modifications to an existing object + case 'form_for_modify_all': // Form to modify multiple objects (bulk modify) + case 'bulk_stimulus': // For to apply a stimulus to multiple objects + case 'stimulus': // Form displayed when applying a stimulus (state change) + case 'apply_stimulus': // Form displayed when applying a stimulus (state change) + $oP->add_linked_script("../js/json.js"); + $oP->add_linked_script("../js/forms-json-utils.js"); + $oP->add_linked_script("../js/wizardhelper.js"); + $oP->add_linked_script("../js/wizard.utils.js"); + $oP->add_linked_script("../js/linkswidget.js"); + $oP->add_linked_script("../js/linksdirectwidget.js"); + $oP->add_linked_script("../js/extkeywidget.js"); + $oP->add_linked_script("../js/jquery.blockUI.js"); + break; + } + + switch($operation) + { + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'details': // Details of an object + $sClass = utils::ReadParam('class', ''); + $id = utils::ReadParam('id', ''); + if ( empty($sClass) || empty($id)) + { + throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); + } + + if (is_numeric($id)) + { + $oObj = MetaModel::GetObject($sClass, $id, false /* MustBeFound */); + } + else + { + $oObj = MetaModel::GetObjectByName($sClass, $id, false /* MustBeFound */); + } + if (is_null($oObj)) + { + // Check anyhow if there is a message for this object (like you've just created it) + $sMessageKey = $sClass.'::'.$id; + DisplayMessages($sMessageKey, $oP); + $oP->set_title(Dict::S('UI:ErrorPageTitle')); + + // Attempt to load the object in archive mode + utils::PushArchiveMode(true); + if (is_numeric($id)) + { + $oObj = MetaModel::GetObject($sClass, $id, false /* MustBeFound */); + } + else + { + $oObj = MetaModel::GetObjectByName($sClass, $id, false /* MustBeFound */); + } + utils::PopArchiveMode(); + if (is_null($oObj)) + { + $oP->P(Dict::S('UI:ObjectDoesNotExist')); + } + else + { + SetObjectBreadCrumbEntry($oObj, $oP); + $oP->P(Dict::S('UI:ObjectArchived')); + } + } + else + { + try + { + $oObj->Reload(); + } + catch(Exception $e) + { + // Probably not allowed to see this instance of a derived class + + // Check anyhow if there is a message for this object (like you've just created it) + $sMessageKey = $sClass.'::'.$id; + DisplayMessages($sMessageKey, $oP); + + $oObj = null; + $oP->set_title(Dict::S('UI:ErrorPageTitle')); + $oP->P(Dict::S('UI:ObjectDoesNotExist')); + } + if (!is_null($oObj)) + { + SetObjectBreadCrumbEntry($oObj, $oP); + DisplayDetails($oP, $sClass, $oObj, $id); + } + } + break; + + case 'release_lock_and_details': + $oP->DisableBreadCrumb(); + $sClass = utils::ReadParam('class', ''); + $id = utils::ReadParam('id', ''); + $oObj = MetaModel::GetObject($sClass, $id); + $sToken = utils::ReadParam('token', ''); + if ($sToken != '') + { + iTopOwnershipLock::ReleaseLock($sClass, $id, $sToken); + } + cmdbAbstractObject::ReloadAndDisplay($oP, $oObj, array('operation' => 'details')); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'search_oql': // OQL query + $sOQLClass = utils::ReadParam('oql_class', '', false, 'class'); + $sBaseClass = utils::ReadParam('base_class', $sOQLClass, false, 'class'); + $sOQLClause = utils::ReadParam('oql_clause', '', false, 'raw_data'); + $sFormat = utils::ReadParam('format', ''); + $bSearchForm = utils::ReadParam('search_form', true); + $sTitle = utils::ReadParam('title', 'UI:SearchResultsPageTitle'); + if (empty($sOQLClass)) + { + throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'oql_class')); + } + $oP->set_title(Dict::S($sTitle)); + $oP->add('

'.Dict::S($sTitle).'

'); + $sOQL = "SELECT $sOQLClass $sOQLClause"; + try + { + $oFilter = DBObjectSearch::FromOQL($sOQL); + DisplaySearchSet($oP, $oFilter, $bSearchForm, $sBaseClass, $sFormat); + } + catch(CoreException $e) + { + $oFilter = new DBObjectSearch($sOQLClass); + $oSet = new DBObjectSet($oFilter); + if ($bSearchForm) + { + $oBlock = new DisplayBlock($oFilter, 'search', false); + $oBlock->Display($oP, 0); + } + $oP->P(''.Dict::Format('UI:Error:IncorrectOQLQuery_Message', $e->getHtmlDesc()).''); + } + catch(Exception $e) + { + $oP->P(''.Dict::Format('UI:Error:AnErrorOccuredWhileRunningTheQuery_Message', $e->getMessage()).''); + } + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'search_form': // Search form + $sClass = utils::ReadParam('class', '', false, 'class'); + $sFormat = utils::ReadParam('format', 'html'); + $bSearchForm = utils::ReadParam('search_form', true); + $bDoSearch = utils::ReadParam('do_search', true); + if (empty($sClass)) + { + throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); + } + $oP->set_title(Dict::S('UI:SearchResultsPageTitle')); + $oFilter = new DBObjectSearch($sClass); + DisplaySearchSet($oP, $oFilter, $bSearchForm, '' /* sBaseClass */, $sFormat, $bDoSearch, true /* Search Form Expanded */); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'search': // Serialized DBSearch + $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); + $sFormat = utils::ReadParam('format', ''); + $bSearchForm = utils::ReadParam('search_form', true); + if (empty($sFilter)) + { + throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'filter')); + } + $oP->set_title(Dict::S('UI:SearchResultsPageTitle')); + $oFilter = DBSearch::unserialize($sFilter); // TO DO : check that the filter is valid + $oFilter->UpdateContextFromUser(); + DisplaySearchSet($oP, $oFilter, $bSearchForm, '' /* sBaseClass */, $sFormat); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'full_text': // Global "google-like" search + $oP->DisableBreadCrumb(); + $sFullText = trim(utils::ReadParam('text', '', false, 'raw_data')); + $iTune = utils::ReadParam('tune', 0); + if (empty($sFullText)) + { + $oP->p(Dict::S('UI:Search:NoSearch')); + } + else + { + $iErrors = 0; + + // Check if a class name/label is supplied to limit the search + $sClassName = ''; + if (preg_match('/^([^\"]+):(.+)$/', $sFullText, $aMatches)) + { + $sClassName = $aMatches[1]; + if (MetaModel::IsValidClass($sClassName)) + { + $sFullText = trim($aMatches[2]); + } + elseif ($sClassName = MetaModel::GetClassFromLabel($sClassName, false /* => not case sensitive */)) + { + $sFullText = trim($aMatches[2]); + } + } + + if (preg_match('/^"(.*)"$/', $sFullText, $aMatches)) + { + // The text is surrounded by double-quotes, remove the quotes and treat it as one single expression + $aFullTextNeedles = array($aMatches[1]); + } + else + { + // Split the text on the blanks and treat this as a search for AND AND + $aFullTextNeedles = explode(' ', $sFullText); + } + + // Check the needle length + $iMinLenth = MetaModel::GetConfig()->Get('full_text_needle_min'); + foreach ($aFullTextNeedles as $sNeedle) + { + if (strlen($sNeedle) < $iMinLenth) + { + $oP->p(Dict::Format('UI:Search:NeedleTooShort', $sNeedle, $iMinLenth)); + $iErrors++; + } + } + + // Sanity check of the accelerators + $aAccelerators = MetaModel::GetConfig()->Get('full_text_accelerators'); + foreach ($aAccelerators as $sClass => $aAccelerator) + { + try + { + $bSkip = array_key_exists('skip', $aAccelerator) ? $aAccelerator['skip'] : false; + if (!$bSkip) + { + $oSearch = DBObjectSearch::FromOQL($aAccelerator['query']); + if ($sClass != $oSearch->GetClass()) + { + $oP->p("Full text accelerator for class '$sClass': searched class mismatch (".$oSearch->GetClass().")"); + $iErrors++; + } + } + } + catch (OqlException $e) + { + $oP->p("Full text accelerator for class '$sClass': ".$e->getHtmlDesc()); + $iErrors++; + } + } + + if ($iErrors == 0) + { + $oP->set_title(Dict::S('UI:SearchResultsPageTitle')); + $sPageId = "ui-global-search"; + $sLabel = Dict::S('UI:SearchResultsTitle'); + $sDescription = Dict::S('UI:SearchResultsTitle+'); + $oP->SetBreadCrumbEntry($sPageId, $sLabel, $sDescription, '', utils::GetAbsoluteUrlAppRoot().'images/search.png'); + $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/tabularfieldsselector.js'); + $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.dragtable.js'); + $oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/dragtable.css'); + $oP->add("
\n"); + $oP->add("
\n"); + $oP->add(' '.Dict::Format('UI:Search:Ongoing', htmlentities($sFullText, ENT_QUOTES, 'UTF-8')).''); + $oP->add("
\n"); + $oP->add("
\n"); + $oP->add("
 
\n"); + $oP->add("

".Dict::Format('UI:FullTextSearchTitle_Text', htmlentities($sFullText, ENT_QUOTES, 'UTF-8'))."

"); + $oP->add("
\n"); + $oP->add("
\n"); + $sJSClass = addslashes($sClassName); + $sJSNeedles = json_encode($aFullTextNeedles); + $oP->add_ready_script( +<< 0) + { + $oP->add_script("var oTimeStatistics = {};"); + } + } + } + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'modify': // Form to modify an object + $oP->DisableBreadCrumb(); + $sClass = utils::ReadParam('class', '', false, 'class'); + $id = utils::ReadParam('id', ''); + if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! + { + throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); + } + // Check if the user can modify this object + $oObj = MetaModel::GetObject($sClass, $id, false /* MustBeFound */); + if (is_null($oObj)) + { + $oP->set_title(Dict::S('UI:ErrorPageTitle')); + $oP->P(Dict::S('UI:ObjectDoesNotExist')); + } + else + { + // The object could be read - check if it is allowed to modify it + $oSet = CMDBObjectSet::FromObject($oObj); + if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_NO) + { + throw new SecurityException('User not allowed to modify this object', array('class' => $sClass, 'id' => $id)); + } + // Note: code duplicated to the case 'apply_modify' when a data integrity issue has been found + $oObj->DisplayModifyForm($oP, array('wizard_container' => 1)); // wizard_container: Display the blue borders and the title above the form + } + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'select_for_modify_all': // Select the list of objects to be modified (bulk modify) + $oP->DisableBreadCrumb(); + $oP->set_title(Dict::S('UI:ModifyAllPageTitle')); + $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); + if (empty($sFilter)) + { + throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'filter')); + } + $oFilter = DBObjectSearch::unserialize($sFilter); //TODO : check that the filter is valid + // Add user filter + $oFilter->UpdateContextFromUser(); + $sClass = $oFilter->GetClass(); + $oChecker = new ActionChecker($oFilter, UR_ACTION_BULK_MODIFY); + $oP->add("

".Dict::S('UI:ModifyAllPageTitle')."

\n"); + + DisplayMultipleSelectionForm($oP, $oFilter, 'form_for_modify_all', $oChecker); + break; + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'form_for_modify_all': // Form to modify multiple objects (bulk modify) + $oP->DisableBreadCrumb(); + $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); + $sClass = utils::ReadParam('class', '', false, 'class'); + $oFullSetFilter = DBObjectSearch::unserialize($sFilter); + // Add user filter + $oFullSetFilter->UpdateContextFromUser(); + $aSelectedObj = utils::ReadMultipleSelection($oFullSetFilter); + $sCancelUrl = "./UI.php?operation=search&filter=".urlencode($sFilter)."&".$oAppContext->GetForLink(); + $aContext = array('filter' => $sFilter); + cmdbAbstractObject::DisplayBulkModifyForm($oP, $sClass, $aSelectedObj, 'preview_or_modify_all', $sCancelUrl, array(), $aContext); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'preview_or_modify_all': // Preview or apply bulk modify + $oP->DisableBreadCrumb(); + $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); + $oFilter = DBObjectSearch::unserialize($sFilter); // TO DO : check that the filter is valid + // Add user filter + $oFilter->UpdateContextFromUser(); + $oChecker = new ActionChecker($oFilter, UR_ACTION_BULK_MODIFY); + + $sClass = utils::ReadParam('class', '', false, 'class'); + $bPreview = utils::ReadParam('preview_mode', ''); + $sSelectedObj = utils::ReadParam('selectObj', '', false, 'raw_data'); + if ( empty($sClass) || empty($sSelectedObj)) // TO DO: check that the class name is valid ! + { + throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'selectObj')); + } + $aSelectedObj = explode(',', $sSelectedObj); + $sCancelUrl = "./UI.php?operation=search&filter=".urlencode($sFilter)."&".$oAppContext->GetForLink(); + $aContext = array( + 'filter' => $sFilter, + 'selectObj' => $sSelectedObj, + ); + cmdbAbstractObject::DoBulkModify($oP, $sClass, $aSelectedObj, 'preview_or_modify_all', $bPreview, $sCancelUrl, $aContext); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'new': // Form to create a new object + $oP->DisableBreadCrumb(); + $sClass = utils::ReadParam('class', '', false, 'class'); + $sStateCode = utils::ReadParam('state', ''); + $bCheckSubClass = utils::ReadParam('checkSubclass', true); + if ( empty($sClass) ) + { + throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); + } + +/* + $aArgs = utils::ReadParam('default', array(), false, 'raw_data'); + $aContext = $oAppContext->GetAsHash(); + foreach( $oAppContext->GetNames() as $key) + { + $aArgs[$key] = $oAppContext->GetCurrentValue($key); + } +*/ + // If the specified class has subclasses, ask the user an instance of which class to create + $aSubClasses = MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself + $aPossibleClasses = array(); + $sRealClass = ''; + if ($bCheckSubClass) + { + foreach($aSubClasses as $sCandidateClass) + { + if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) + { + $aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass); + } + } + // Only one of the subclasses can be instantiated... + if (count($aPossibleClasses) == 1) + { + $aKeys = array_keys($aPossibleClasses); + $sRealClass = $aKeys[0]; + } + } + else + { + $sRealClass = $sClass; + } + + if (!empty($sRealClass)) + { + // Display the creation form + $sClassLabel = MetaModel::GetName($sRealClass); + // Note: some code has been duplicated to the case 'apply_new' when a data integrity issue has been found + $oP->set_title(Dict::Format('UI:CreationPageTitle_Class', $sClassLabel)); + $oP->add("

".MetaModel::GetClassIcon($sRealClass)." ".Dict::Format('UI:CreationTitle_Class', $sClassLabel)."

\n"); + $oP->add("
\n"); + + // Set all the default values in an object and clone this "default" object + $oObjToClone = MetaModel::NewObject($sRealClass); + + // 1st - set context values + $oAppContext->InitObjectFromContext($oObjToClone); + + // 2nd - set values from the page argument 'default' + $oObjToClone->UpdateObjectFromArg('default'); + + cmdbAbstractObject::DisplayCreationForm($oP, $sRealClass, $oObjToClone, array()); + $oP->add("
\n"); + } + else + { + // Select the derived class to create + $sClassLabel = MetaModel::GetName($sClass); + $oP->add("

".MetaModel::GetClassIcon($sClass)." ".Dict::Format('UI:CreationTitle_Class', $sClassLabel)."

\n"); + $oP->add("
\n"); + $oP->add('
'); + $oP->add('

'.Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel)); + $aDefaults = utils::ReadParam('default', array(), false, 'raw_data'); + $oP->add($oAppContext->GetForForm()); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + foreach($aDefaults as $key => $value) + { + if (is_array($value)) + { + foreach($value as $key2 => $value2) + { + if (is_array($value2)) + { + foreach($value2 as $key3 => $value3) + { + $sValue = htmlentities($value3, ENT_QUOTES, 'UTF-8'); + $oP->add("\n"); + } + } + else + { + $sValue = htmlentities($value2, ENT_QUOTES, 'UTF-8'); + $oP->add("\n"); + } + } + } + else + { + $sValue = htmlentities($value, ENT_QUOTES, 'UTF-8'); + $oP->add("\n"); + } + } + $oP->add(''); + $oP->add(" 

"); + $oP->add('
'); + $oP->add("
\n"); + } + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'apply_modify': // Applying the modifications to an existing object + $oP->DisableBreadCrumb(); + $sClass = utils::ReadPostedParam('class', ''); + $sClassLabel = MetaModel::GetName($sClass); + $id = utils::ReadPostedParam('id', ''); + $sTransactionId = utils::ReadPostedParam('transaction_id', ''); + if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! + { + throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); + } + $bDisplayDetails = true; + $oObj = MetaModel::GetObject($sClass, $id, false); + if ($oObj == null) + { + $bDisplayDetails = false; + $oP->set_title(Dict::S('UI:ErrorPageTitle')); + $oP->P(Dict::S('UI:ObjectDoesNotExist')); + } + elseif (!utils::IsTransactionValid($sTransactionId, false)) + { + $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding + $oP->p("".Dict::S('UI:Error:ObjectAlreadyUpdated')."\n"); + } + else + { + $oObj->UpdateObjectFromPostedForm(); + $sMessage = ''; + $sSeverity = 'ok'; + + if (!$oObj->IsModified()) + { + $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding + $sMessage = Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); + $sSeverity = 'info'; + } + else + { + list($bRes, $aIssues) = $oObj->CheckToWrite(); + if ($bRes) + { + try + { + CMDBSource::Query('START TRANSACTION'); + $oObj->DBUpdate(); + CMDBSource::Query('COMMIT'); + $sMessage = Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); + $sSeverity = 'ok'; + } + catch(DeleteException $e) + { + CMDBSource::Query('ROLLBACK'); + // Say two things: 1) Don't be afraid nothing was modified + $sMessage = Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); + $sSeverity = 'info'; + cmdbAbstractObject::SetSessionMessage(get_class($oObj), $oObj->GetKey(), 'UI:Class_Object_NotUpdated', $sMessage, $sSeverity, 0, true /* must not exist */); + // 2) Ok, there was some trouble indeed + $sMessage = $e->getMessage(); + $sSeverity = 'error'; + $bDisplayDetails = true; + } + utils::RemoveTransaction($sTransactionId); + + } + else + { + $bDisplayDetails = false; + // Found issues, explain and give the user a second chance + // + $oObj->DisplayModifyForm($oP, array('wizard_container' => true)); // wizard_container: display the wizard border and the title + $sIssueDesc = Dict::Format('UI:ObjectCouldNotBeWritten', implode(', ', $aIssues)); + $oP->add_ready_script("alert('".addslashes($sIssueDesc)."');"); + } + } + } + if ($bDisplayDetails) + { + $oObj = MetaModel::GetObject(get_class($oObj), $oObj->GetKey()); //Workaround: reload the object so that the linkedset are displayed properly + $sNextAction = utils::ReadPostedParam('next_action', ''); + if (!empty($sNextAction)) + { + ApplyNextAction($oP, $oObj, $sNextAction); + } + else + { + // Nothing more to do + ReloadAndDisplay($oP, $oObj, 'update', $sMessage, $sSeverity); + } + + $bLockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled'); + if ($bLockEnabled) + { + // Release the concurrent lock, if any + $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, false, 'raw_data'); + if ($sOwnershipToken !== null) + { + // We're done, let's release the lock + iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken); + } + } + } + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'select_for_deletion': // Select multiple objects for deletion + $oP->DisableBreadCrumb(); + $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); + if (empty($sFilter)) + { + throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'filter')); + } + $oP->set_title(Dict::S('UI:BulkDeletePageTitle')); + $oP->add("

".Dict::S('UI:BulkDeleteTitle')."

\n"); + $oFilter = DBSearch::unserialize($sFilter); // TO DO : check that the filter is valid + $oFilter->UpdateContextFromUser(); + $oChecker = new ActionChecker($oFilter, UR_ACTION_BULK_DELETE); + DisplayMultipleSelectionForm($oP, $oFilter, 'bulk_delete', $oChecker); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'bulk_delete_confirmed': // Confirm bulk deletion of objects + $oP->DisableBreadCrumb(); + $sTransactionId = utils::ReadPostedParam('transaction_id', ''); + if (!utils::IsTransactionValid($sTransactionId)) + { + throw new ApplicationException(Dict::S('UI:Error:ObjectsAlreadyDeleted')); + } + // Fall through + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'delete': + case 'bulk_delete': // Actual bulk deletion (if confirmed) + $oP->DisableBreadCrumb(); + $sClass = utils::ReadParam('class', '', false, 'class'); + $sClassLabel = MetaModel::GetName($sClass); + $aObjects = array(); + if ($operation == 'delete') + { + // Single object + $id = utils::ReadParam('id', ''); + $oObj = MetaModel::GetObject($sClass, $id); + $aObjects[] = $oObj; + if (!UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, DBObjectSet::FromObject($oObj))) + { + throw new SecurityException(Dict::Format('UI:Error:DeleteNotAllowedOn_Class', $sClassLabel)); + } + } + else + { + // Several objects + $sFilter = utils::ReadPostedParam('filter', ''); + $oFullSetFilter = DBObjectSearch::unserialize($sFilter); + // Add user filter + $oFullSetFilter->UpdateContextFromUser(); + $aSelectObject = utils::ReadMultipleSelection($oFullSetFilter); + if ( empty($sClass) || empty($aSelectObject)) // TO DO: check that the class name is valid ! + { + throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'selectObject[]')); + } + foreach($aSelectObject as $iId) + { + $aObjects[] = MetaModel::GetObject($sClass, $iId); + } + if (count($aObjects) == 1) + { + if (!UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, DBObjectSet::FromArray($sClass, $aObjects))) + { + throw new SecurityException(Dict::Format('UI:Error:BulkDeleteNotAllowedOn_Class', $sClassLabel)); + } + } + else + { + if (!UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, DBObjectSet::FromArray($sClass, $aObjects))) + { + throw new SecurityException(Dict::Format('UI:Error:BulkDeleteNotAllowedOn_Class', $sClassLabel)); + } + $oP->set_title(Dict::S('UI:BulkDeletePageTitle')); + } + } + // Go for the common part... (delete single, delete bulk, delete confirmed) + cmdbAbstractObject::DeleteObjects($oP, $sClass, $aObjects, ($operation != 'bulk_delete_confirmed'), 'bulk_delete_confirmed'); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'apply_new': // Creation of a new object + $oP->DisableBreadCrumb(); + $sClass = utils::ReadPostedParam('class', '', 'class'); + $sClassLabel = MetaModel::GetName($sClass); + $sTransactionId = utils::ReadPostedParam('transaction_id', ''); + if ( empty($sClass) ) // TO DO: check that the class name is valid ! + { + throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); + } + if (!utils::IsTransactionValid($sTransactionId, false)) + { + $oP->p("".Dict::S('UI:Error:ObjectAlreadyCreated')."\n"); + } + else + { + $oObj = MetaModel::NewObject($sClass); + $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); + if (!empty($sStateAttCode)) + { + $sTargetState = utils::ReadPostedParam('obj_state', ''); + if ($sTargetState != '') + { + $oObj->Set($sStateAttCode, $sTargetState); + } + } + $oObj->UpdateObjectFromPostedForm(); + } + if (isset($oObj) && is_object($oObj)) + { + $sClass = get_class($oObj); + $sClassLabel = MetaModel::GetName($sClass); + + list($bRes, $aIssues) = $oObj->CheckToWrite(); + if ($bRes) + { + $oObj->DBInsertNoReload(); // No need to reload + utils::RemoveTransaction($sTransactionId); + $oP->set_title(Dict::S('UI:PageTitle:ObjectCreated')); + + // Compute the name, by reloading the object, even if it disappeared from the silo + $oObj = MetaModel::GetObject($sClass, $oObj->GetKey(), true /* Must be found */, true /* Allow All Data*/); + $sName = $oObj->GetName(); + $sMessage = Dict::Format('UI:Title:Object_Of_Class_Created', $sName, $sClassLabel); + + $sNextAction = utils::ReadPostedParam('next_action', ''); + if (!empty($sNextAction)) + { + $oP->add("

$sMessage

"); + ApplyNextAction($oP, $oObj, $sNextAction); + } + else + { + // Nothing more to do + ReloadAndDisplay($oP, $oObj, 'create', $sMessage, 'ok'); + } + } + else + { + // Found issues, explain and give the user a second chance + // + $oP->set_title(Dict::Format('UI:CreationPageTitle_Class', $sClassLabel)); + $oP->add("

".MetaModel::GetClassIcon($sClass)." ".Dict::Format('UI:CreationTitle_Class', $sClassLabel)."

\n"); + $oP->add("
\n"); + cmdbAbstractObject::DisplayCreationForm($oP, $sClass, $oObj); + $oP->add("
\n"); + $sIssueDesc = Dict::Format('UI:ObjectCouldNotBeWritten', implode(', ', $aIssues)); + $oP->add_ready_script("alert('".addslashes($sIssueDesc)."');"); + } + } + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'select_bulk_stimulus': // Form displayed when applying a stimulus to many objects + $oP->DisableBreadCrumb(); + $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); + $sStimulus = utils::ReadParam('stimulus', ''); + $sState = utils::ReadParam('state', ''); + if (empty($sFilter) || empty($sStimulus) || empty($sState)) + { + throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'filter', 'stimulus', 'state')); + } + $oFilter = DBObjectSearch::unserialize($sFilter); + $oFilter->UpdateContextFromUser(); + $sClass = $oFilter->GetClass(); + $aStimuli = MetaModel::EnumStimuli($sClass); + $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); + $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); + $oP->set_title($sActionLabel); + $oP->add(''); + + $oChecker = new StimulusChecker($oFilter, $sState, $sStimulus); + $aExtraFormParams = array('stimulus' => $sStimulus, 'state' => $sState); + DisplayMultipleSelectionForm($oP, $oFilter, 'bulk_stimulus', $oChecker, $aExtraFormParams); + break; + + case 'bulk_stimulus': + $oP->DisableBreadCrumb(); + $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); + $sStimulus = utils::ReadParam('stimulus', ''); + $sState = utils::ReadParam('state', ''); + if (empty($sFilter) || empty($sStimulus) || empty($sState)) + { + throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'filter', 'stimulus', 'state')); + } + $oFilter = DBObjectSearch::unserialize($sFilter); + // Add user filter + $oFilter->UpdateContextFromUser(); + $sClass = $oFilter->GetClass(); + $aSelectObject = utils::ReadMultipleSelection($oFilter); + if (count($aSelectObject) == 0) + { + // Nothing to do, no object was selected ! + throw new ApplicationException(Dict::S('UI:BulkAction:NoObjectSelected')); + } + else + { + $aTransitions = MetaModel::EnumTransitions($sClass, $sState); + $aStimuli = MetaModel::EnumStimuli($sClass); + + $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); + $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); + $sTargetState = $aTransitions[$sStimulus]['target_state']; + $aStates = MetaModel::EnumStates($sClass); + $aTargetStateDef = $aStates[$sTargetState]; + + $oP->set_title(Dict::Format('UI:StimulusModify_N_ObjectsOf_Class', $sActionLabel, count($aSelectObject), $sClass)); + $oP->add(''); + + $aExpectedAttributes = MetaModel::GetTransitionAttributes($sClass, $sStimulus, $sState); + $aDetails = array(); + $iFieldIndex = 0; + $aFieldsMap = array(); + $aValues = array(); + $aObjects = array(); + foreach($aSelectObject as $iId) + { + $aObjects[] = MetaModel::GetObject($sClass, $iId); + } + $oSet = DBObjectSet::FromArray($sClass, $aObjects); + $oObj = $oSet->ComputeCommonObject($aValues); + $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); + $oObj->Set($sStateAttCode,$sTargetState); + $sReadyScript = ''; + foreach($aExpectedAttributes as $sAttCode => $iExpectCode) + { + // Prompt for an attribute if + // - the attribute must be changed or must be displayed to the user for confirmation + // - or the field is mandatory and currently empty + if ( ($iExpectCode & (OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) || + (($iExpectCode & OPT_ATT_MANDATORY) && ($oObj->Get($sAttCode) == '')) ) + { + $aAttributesDef = MetaModel::ListAttributeDefs($sClass); + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $aPrerequisites = MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode); // List of attributes that are needed for the current one + if (count($aPrerequisites) > 0) + { + // When 'enabling' a field, all its prerequisites must be enabled too + $sFieldList = "['".implode("','", $aPrerequisites)."']"; + $oP->add_ready_script("$('#enable_{$sAttCode}').bind('change', function(evt, sFormId) { return PropagateCheckBox( this.checked, $sFieldList, true); } );\n"); + } + $aDependents = MetaModel::GetDependentAttributes($sClass, $sAttCode); // List of attributes that are needed for the current one + if (count($aDependents) > 0) + { + // When 'disabling' a field, all its dependent fields must be disabled too + $sFieldList = "['".implode("','", $aDependents)."']"; + $oP->add_ready_script("$('#enable_{$sAttCode}').bind('change', function(evt, sFormId) { return PropagateCheckBox( this.checked, $sFieldList, false); } );\n"); + } + $aArgs = array('this' => $oObj); + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $oObj->Get($sAttCode), $oObj->GetEditValue($sAttCode), $sAttCode, '', $iExpectCode, $aArgs); + $sComments = ''; + if (!isset($aValues[$sAttCode])) + { + $aValues[$sAttCode] = array(); + } + if (count($aValues[$sAttCode]) == 1) + { + $sComments .= '
1
'; + } + else + { + // Non-homogenous value + $iMaxCount = 5; + $sTip = "

".Dict::Format('UI:BulkModify_Count_DistinctValues', count($aValues[$sAttCode]))."

    "; + $index = 0; + foreach($aValues[$sAttCode] as $sCurrValue => $aVal) + { + $sDisplayValue = empty($aVal['display']) ? ''.Dict::S('Enum:Undefined').'' : str_replace(array("\n", "\r"), " ", $aVal['display']); + $sTip .= "
  • ".Dict::Format('UI:BulkModify:Value_Exists_N_Times', $sDisplayValue, $aVal['count'])."
  • "; + $index++; + if ($iMaxCount == $index) + { + $sTip .= "
  • ".Dict::Format('UI:BulkModify:N_MoreValues', count($aValues[$sAttCode]) - $iMaxCount)."
  • "; + break; + } + } + $sTip .= "

"; + $sTip = addslashes($sTip); + $sReadyScript .= "$('#multi_values_$sAttCode').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );\n"; + $sComments .= '
'.count($aValues[$sAttCode]).'
'; + } + $aDetails[] = array('label' => ''.$oAttDef->GetLabel().'', 'value' => "$sHTMLValue", 'comments' => $sComments); + $aFieldsMap[$sAttCode] = $sAttCode; + $iFieldIndex++; + } + } + $sButtonsPosition = MetaModel::GetConfig()->Get('buttons_position'); + if ($sButtonsPosition == 'bottom') + { + // bottom: Displays the ticket details BEFORE the actions + $oP->add('
'); + $oObj->DisplayBareProperties($oP); + $oP->add('
'); + } + $oP->add("
\n"); + $oP->add("
\n"); + $oP->add("
\n"); + $oP->details($aDetails); + $oP->add("
\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add($oAppContext->GetForForm()); + $oP->add("\n"); + $sURL = "./UI.php?operation=search&filter=".urlencode($sFilter)."&".$oAppContext->GetForLink(); + $oP->add("    \n"); + $oP->add("\n"); + $oP->add("
\n"); + $oP->add("
\n"); + if ($sButtonsPosition != 'bottom') + { + // top or both: Displays the ticket details AFTER the actions + $oP->add('
'); + $oObj->DisplayBareProperties($oP); + $oP->add('
'); + } + $iFieldsCount = count($aFieldsMap); + $sJsonFieldsMap = json_encode($aFieldsMap); + + $oP->add_script( +<<add_ready_script( +<<DisableBreadCrumb(); + $bPreviewMode = utils::ReadPostedParam('preview_mode', false); + $sFilter = utils::ReadPostedParam('filter', '', false, 'raw_data'); + $sStimulus = utils::ReadPostedParam('stimulus', ''); + $sState = utils::ReadPostedParam('state', ''); + $sSelectObject = utils::ReadPostedParam('selectObject', '', false, 'raw_data'); + $aSelectObject = explode(',', $sSelectObject); + + if (empty($sFilter) || empty($sStimulus) || empty($sState)) + { + throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'filter', 'stimulus', 'state')); + } + $sTransactionId = utils::ReadPostedParam('transaction_id', ''); + if (!utils::IsTransactionValid($sTransactionId)) + { + $oP->p(Dict::S('UI:Error:ObjectAlreadyUpdated')); + } + else + { + // For archiving the modification + $oFilter = DBObjectSearch::unserialize($sFilter); + // Add user filter + $oFilter->UpdateContextFromUser(); + $sClass = $oFilter->GetClass(); + $aObjects = array(); + foreach($aSelectObject as $iId) + { + $aObjects[] = MetaModel::GetObject($sClass, $iId); + } + + $aTransitions = MetaModel::EnumTransitions($sClass, $sState); + $aStimuli = MetaModel::EnumStimuli($sClass); + + $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); + $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); + + $oP->set_title(Dict::Format('UI:StimulusModify_N_ObjectsOf_Class', $sActionLabel, count($aObjects), $sClass)); + $oP->add(''); + + $oSet = DBObjectSet::FromArray($sClass, $aObjects); + + // For reporting + $aHeaders = array( + 'object' => array('label' => MetaModel::GetName($sClass), 'description' => Dict::S('UI:ModifiedObject')), + 'status' => array('label' => Dict::S('UI:BulkModifyStatus'), 'description' => Dict::S('UI:BulkModifyStatus+')), + 'errors' => array('label' => Dict::S('UI:BulkModifyErrors'), 'description' => Dict::S('UI:BulkModifyErrors+')), + ); + $aRows = array(); + while ($oObj = $oSet->Fetch()) + { + $sError = Dict::S('UI:BulkModifyStatusOk'); + try + { + $aTransitions = $oObj->EnumTransitions(); + $aStimuli = MetaModel::EnumStimuli($sClass); + if (!isset($aTransitions[$sStimulus])) + { + throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, $oObj->GetName(), $oObj->GetStateLabel())); + } + else + { + $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); + $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); + $sTargetState = $aTransitions[$sStimulus]['target_state']; + $aExpectedAttributes = $oObj->GetTransitionAttributes($sStimulus /* cureent state */); + $aDetails = array(); + $aErrors = array(); + foreach($aExpectedAttributes as $sAttCode => $iExpectCode) + { + $iFlags = $oObj->GetTransitionFlags($sAttCode, $sStimulus); + if (($iExpectCode & (OPT_ATT_MUSTCHANGE|OPT_ATT_MUSTPROMPT)) || ($oObj->Get($sAttCode) == '') ) + { + $paramValue = utils::ReadPostedParam("attr_$sAttCode", '', 'raw_data'); + if ( ($iFlags & OPT_ATT_SLAVE) && ($paramValue != $oObj->Get($sAttCode)) ) + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $aErrors[] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $oAttDef->GetLabel()); + unset($aExpectedAttributes[$sAttCode]); + } + } + } + + $oObj->UpdateObjectFromPostedForm('', array_keys($aExpectedAttributes), $aExpectedAttributes); + + if (count($aErrors) == 0) + { + if ($oObj->ApplyStimulus($sStimulus)) + { + list($bResult, $aErrors) = $oObj->CheckToWrite(); + $sStatus = $bResult ? Dict::S('UI:BulkModifyStatusModified') : Dict::S('UI:BulkModifyStatusSkipped'); + if ($bResult) + { + $oObj->DBUpdate(); + } + else + { + $sError = '

'.implode('

',$aErrors)."

\n"; + } + } + else + { + $sStatus = Dict::S('UI:BulkModifyStatusSkipped'); + $sError = '

'.Dict::S('UI:FailedToApplyStimuli')."

\n"; + } + } + else + { + $sStatus = Dict::S('UI:BulkModifyStatusSkipped'); + $sError = '

'.implode('

',$aErrors)."

\n"; + } + } + } + catch(Exception $e) + { + $sError = $e->getMessage(); + $sStatus = Dict::S('UI:BulkModifyStatusSkipped'); + } + $aRows[] = array( + 'object' => $oObj->GetHyperlink(), + 'status' => $sStatus, + 'errors' => $sError, + ); + } + $oP->Table($aHeaders, $aRows); + // Back to the list + $sURL = "./UI.php?operation=search&filter=".urlencode($sFilter)."&".$oAppContext->GetForLink(); + $oP->add(''); + } + break; + + case 'stimulus': // Form displayed when applying a stimulus (state change) + $oP->DisableBreadCrumb(); + $sClass = utils::ReadParam('class', '', false, 'class'); + $id = utils::ReadParam('id', ''); + $sStimulus = utils::ReadParam('stimulus', ''); + if ( empty($sClass) || empty($id) || empty($sStimulus) ) // TO DO: check that the class name is valid ! + { + throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus')); + } + $oObj = MetaModel::GetObject($sClass, $id, false); + if ($oObj != null) + { + $oObj->DisplayStimulusForm($oP, $sStimulus); + } + else + { + $oP->set_title(Dict::S('UI:ErrorPageTitle')); + $oP->P(Dict::S('UI:ObjectDoesNotExist')); + } + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'apply_stimulus': // Actual state change + $oP->DisableBreadCrumb(); + $sClass = utils::ReadPostedParam('class', ''); + $id = utils::ReadPostedParam('id', ''); + $sTransactionId = utils::ReadPostedParam('transaction_id', ''); + $sStimulus = utils::ReadPostedParam('stimulus', ''); + if ( empty($sClass) || empty($id) || empty($sStimulus) ) // TO DO: check that the class name is valid ! + { + throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus')); + } + $oObj = MetaModel::GetObject($sClass, $id, false); + if ($oObj != null) + { + $aTransitions = $oObj->EnumTransitions(); + $aStimuli = MetaModel::EnumStimuli($sClass); + $sMessage = ''; + $sSeverity = 'ok'; + $bDisplayDetails = true; + if (!isset($aTransitions[$sStimulus])) + { + throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, $oObj->GetName(), $oObj->GetStateLabel())); + } + if (!utils::IsTransactionValid($sTransactionId)) + { + $sMessage = Dict::S('UI:Error:ObjectAlreadyUpdated'); + $sSeverity = 'info'; + } + else + { + $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); + $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); + $sTargetState = $aTransitions[$sStimulus]['target_state']; + $aExpectedAttributes = $oObj->GetTransitionAttributes($sStimulus /*, current state*/); + $aDetails = array(); + $aErrors = array(); + foreach($aExpectedAttributes as $sAttCode => $iExpectCode) + { + $iFlags = $oObj->GetTransitionFlags($sAttCode, $sStimulus); + if (($iExpectCode & (OPT_ATT_MUSTCHANGE|OPT_ATT_MUSTPROMPT)) || ($oObj->Get($sAttCode) == '') ) + { + $paramValue = utils::ReadPostedParam("attr_$sAttCode", '', 'raw_data'); + if ( ($iFlags & OPT_ATT_SLAVE) && ($paramValue != $oObj->Get($sAttCode))) + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $aErrors[] = Dict::Format('UI:AttemptingToChangeASlaveAttribute_Name', $oAttDef->GetLabel()); + unset($aExpectedAttributes[$sAttCode]); + } + } + } + + $oObj->UpdateObjectFromPostedForm('', array_keys($aExpectedAttributes), $aExpectedAttributes); + + if (count($aErrors) == 0) + { + $sIssues = ''; + $bApplyStimulus = true; + list($bRes, $aIssues) = $oObj->CheckToWrite(); // Check before trying to write the object + if ($bRes) + { + try + { + $bApplyStimulus = $oObj->ApplyStimulus($sStimulus); // will write the object in the DB + } + catch(CoreException $e) + { + // Rollback to the previous state... by reloading the object from the database and applying the modifications again + $oObj = MetaModel::GetObject(get_class($oObj), $oObj->GetKey()); + $oObj->UpdateObjectFromPostedForm('', array_keys($aExpectedAttributes), $aExpectedAttributes); + $sIssues = $e->getMessage(); + } + } + else + { + $sIssues = implode(' ', $aIssues); + } + + if (!$bApplyStimulus) + { + $sMessage = Dict::S('UI:FailedToApplyStimuli'); + $sSeverity = 'error'; + } + else if ($sIssues != '') + { + + $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, false, 'raw_data'); + if ($sOwnershipToken !== null) + { + // Release the concurrent lock, if any, a new lock will be re-acquired by DisplayStimulusForm below + iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken); + } + + $bDisplayDetails = false; + // Found issues, explain and give the user a second chance + // + $oObj->DisplayStimulusForm($oP, $sStimulus); + $sIssueDesc = Dict::Format('UI:ObjectCouldNotBeWritten',$sIssues); + $oP->add_ready_script("alert('".addslashes($sIssueDesc)."');"); + } + else + { + $sMessage = Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); + $sSeverity = 'ok'; + utils::RemoveTransaction($sTransactionId); + $bLockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled'); + if ($bLockEnabled) + { + // Release the concurrent lock, if any + $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, false, 'raw_data'); + if ($sOwnershipToken !== null) + { + // We're done, let's release the lock + iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken); + } + } + } + } + else + { + $sMessage = implode('

', $aErrors); + $sSeverity = 'error'; + } + } + if ($bDisplayDetails) + { + ReloadAndDisplay($oP, $oObj, 'apply_stimulus', $sMessage, $sSeverity); + } + } + else + { + $oP->set_title(Dict::S('UI:ErrorPageTitle')); + $oP->P(Dict::S('UI:ObjectDoesNotExist')); + } + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'swf_navigator': // Graphical display of the relations "impact" / "depends on" + 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'); + $sDirection = utils::ReadParam('direction', 'down'); + $iGroupingThreshold = utils::ReadParam('g', 5); + + $bDirDown = ($sDirection === 'down'); + $oObj = MetaModel::GetObject($sClass, $id); + $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth', 20); + $aSourceObjects = array($oObj); + + $oP->set_title(MetaModel::GetRelationDescription($sRelation, $bDirDown).' '.$oObj->GetName()); + + $sPageId = "ui-relation-graph-".$sClass.'::'.$id; + $sLabel = $oObj->GetName().' '.MetaModel::GetRelationLabel($sRelation, $bDirDown); + $sDescription = MetaModel::GetRelationDescription($sRelation, $bDirDown).' '.$oObj->GetName(); + $oP->SetBreadCrumbEntry($sPageId, $sLabel, $sDescription); + + if ($sRelation == 'depends on') + { + $sRelation = 'impacts'; + $sDirection = 'up'; + } + if ($sDirection == 'up') + { + $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth); + } + else + { + $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth); + } + + + $aResults = $oRelGraph->GetObjectsByClass(); + $oDisplayGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down')); + + $oP->AddTabContainer('Navigator'); + $oP->SetCurrentTabContainer('Navigator'); + + $sFirstTab = MetaModel::GetConfig()->Get('impact_analysis_first_tab'); + $sContextKey = "itop-config-mgmt/relation_context/$sClass/$sRelation/$sDirection"; + + // Check if the current object supports Attachments, similar to AttachmentPlugin::IsTargetObject + $sClassForAttachment = null; + $iIdForAttachment = null; + if (class_exists('Attachment')) + { + $aAllowedClasses = MetaModel::GetModuleSetting('itop-attachments', 'allowed_classes', array('Ticket')); + foreach($aAllowedClasses as $sAllowedClass) + { + if ($oObj instanceof $sAllowedClass) + { + $iIdForAttachment = $id; + $sClassForAttachment = $sClass; + } + } + } + $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/tabularfieldsselector.js'); + $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.dragtable.js'); + $oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/dragtable.css'); + + // Display the tabs + if ($sFirstTab == 'list') + { + DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj); + $oP->SetCurrentTab(Dict::S('UI:RelationshipGraph')); + $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, array(), $sClassForAttachment, $iIdForAttachment, $sContextKey, array('this' => $oObj)); + DisplayNavigatorGroupTab($oP); + } + else + { + $oP->SetCurrentTab(Dict::S('UI:RelationshipGraph')); + $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, array(), $sClassForAttachment, $iIdForAttachment, $sContextKey, array('this' => $oObj)); + DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj); + DisplayNavigatorGroupTab($oP); + } + + $oP->SetCurrentTab(''); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'kill_lock': + $oP->DisableBreadCrumb(); + $sClass = utils::ReadParam('class', ''); + $id = utils::ReadParam('id', ''); + iTopOwnershipLock::KillLock($sClass, $id); + $oObj = MetaModel::GetObject($sClass, $id); + ReloadAndDisplay($oP, $oObj, 'concurrent_lock_killed', Dict::S('UI:ConcurrentLockKilled'), 'info'); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'cancel': // An action was cancelled + $oP->DisableBreadCrumb(); + $oP->set_title(Dict::S('UI:OperationCancelled')); + $oP->add('

'.Dict::S('UI:OperationCancelled').'

'); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + default: // Menu node rendering (templates) + ApplicationMenu::LoadAdditionalMenus(); + $oMenuNode = ApplicationMenu::GetMenuNode(ApplicationMenu::GetMenuIndexById(ApplicationMenu::GetActiveNodeId())); + if (is_object($oMenuNode)) + { + $oMenuNode->RenderContent($oP, $oAppContext->GetAsHash()); + $oP->set_title($oMenuNode->GetLabel()); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + + } + DisplayWelcomePopup($oP); + $oP->output(); +} +catch(CoreException $e) +{ + require_once(APPROOT.'/setup/setuppage.class.inc.php'); + $oP = new SetupPage(Dict::S('UI:PageTitle:FatalError')); + if ($e instanceof SecurityException) + { + $oP->add("

".Dict::S('UI:SystemIntrusion')."

\n"); + } + else + { + $oP->add("

".Dict::S('UI:FatalErrorMessage')."

\n"); + } + $oP->error(Dict::Format('UI:Error_Details', $e->getHtmlDesc())); + $oP->output(); + + if (MetaModel::IsLogEnabledIssue()) + { + if (MetaModel::IsValidClass('EventIssue')) + { + try + { + $oLog = new EventIssue(); + + $oLog->Set('message', $e->getMessage()); + $oLog->Set('userinfo', ''); + $oLog->Set('issue', $e->GetIssue()); + $oLog->Set('impact', 'Page could not be displayed'); + $oLog->Set('callstack', $e->getTrace()); + $oLog->Set('data', $e->getContextData()); + $oLog->DBInsertNoReload(); + } + catch(Exception $e) + { + IssueLog::Error("Failed to log issue into the DB"); + } + } + + IssueLog::Error($e->getMessage()); + } + + // For debugging only + //throw $e; +} +catch(Exception $e) +{ + require_once(APPROOT.'/setup/setuppage.class.inc.php'); + $oP = new SetupPage(Dict::S('UI:PageTitle:FatalError')); + $oP->add("

".Dict::S('UI:FatalErrorMessage')."

\n"); + $oP->error(Dict::Format('UI:Error_Details', $e->getMessage())); + $oP->output(); + + if (MetaModel::IsLogEnabledIssue()) + { + if (MetaModel::IsValidClass('EventIssue')) + { + try + { + $oLog = new EventIssue(); + + $oLog->Set('message', $e->getMessage()); + $oLog->Set('userinfo', ''); + $oLog->Set('issue', 'PHP Exception'); + $oLog->Set('impact', 'Page could not be displayed'); + $oLog->Set('callstack', $e->getTrace()); + $oLog->Set('data', array()); + $oLog->DBInsertNoReload(); + } + catch(Exception $e) + { + IssueLog::Error("Failed to log issue into the DB"); + } + } + + IssueLog::Error($e->getMessage()); + } } \ No newline at end of file