1<?php 2 3// Copyright (C) 2010-2017 Combodo SARL 4// 5// This file is part of iTop. 6// 7// iTop is free software; you can redistribute it and/or modify 8// it under the terms of the GNU Affero General Public License as published by 9// the Free Software Foundation, either version 3 of the License, or 10// (at your option) any later version. 11// 12// iTop is distributed in the hope that it will be useful, 13// but WITHOUT ANY WARRANTY; without even the implied warranty of 14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15// GNU Affero General Public License for more details. 16// 17// You should have received a copy of the GNU Affero General Public License 18// along with iTop. If not, see <http://www.gnu.org/licenses/> 19 20 21/** 22 * Main page of iTop 23 * 24 * @copyright Copyright (C) 2010-2017 Combodo SARL 25 * @license http://opensource.org/licenses/AGPL-3.0 26 */ 27 28 29/** 30 * Displays a popup welcome message, once per session at maximum 31 * until the user unchecks the "Display welcome at startup" 32 * @param WebPage $oP The current web page for the display 33 * @return void 34 */ 35function DisplayWelcomePopup(WebPage $oP) 36{ 37 if (!isset($_SESSION['welcome'])) 38 { 39 // Check, only once per session, if the popup should be displayed... 40 // If the user did not already ask for hiding it forever 41 $bPopup = appUserPreferences::GetPref('welcome_popup', true); 42 if ($bPopup) 43 { 44 $sTemplate = @file_get_contents('../application/templates/welcome_popup.html'); 45 if ($sTemplate !== false) 46 { 47 $oTemplate = new DisplayTemplate($sTemplate); 48 $oP->add("<div id=\"welcome_popup\">"); 49 $oTemplate->Render($oP, array()); 50 $oP->add("<p style=\"float:left\"><input type=\"checkbox\" checked id=\"display_welcome_popup\"/><label for=\"display_welcome_popup\"> ".Dict::S('UI:DisplayThisMessageAtStartup')."</label></p>\n"); 51 $oP->add("<p style=\"float:right\"><input type=\"button\" value=\"".Dict::S('UI:Button:Ok')."\" onClick=\"$('#welcome_popup').dialog('close');\"/>\n"); 52 $oP->add("</div>\n"); 53 $sTitle = addslashes(Dict::S('UI:WelcomeMenu:Title')); 54 $oP->add_ready_script( 55<<<EOF 56 $('#welcome_popup').dialog( { width:'80%', height: 'auto', title: '$sTitle', autoOpen: true, modal:true, 57 close: function() { 58 var bDisplay = $('#display_welcome_popup:checked').length; 59 SetUserPreference('welcome_popup', bDisplay, true); 60 } 61 }); 62 if ($('#welcome_popup').height() > ($(window).height()-70)) 63 { 64 $('#welcome_popup').height($(window).height()-70); 65 } 66EOF 67); 68 $_SESSION['welcome'] = 'ok'; 69 } 70 } 71 } 72} 73 74/** 75 * Apply the 'next-action' to the given object or redirect to the page that prompts for additional information if needed 76 * 77 * @param $oP WebPage The page for the output 78 * @param $oObj CMDBObject The object to process 79 * @param $sNextAction string The code of the stimulus for the 'action' (i.e. Transition) to apply 80 * 81 * @throws \ApplicationException 82 * @throws \CoreException 83 * @throws \CoreUnexpectedValue 84 */ 85function ApplyNextAction(Webpage $oP, CMDBObject $oObj, $sNextAction) 86{ 87 // Here handle the apply stimulus 88 $aTransitions = $oObj->EnumTransitions(); 89 if (!isset($aTransitions[$sNextAction])) 90 { 91 // Invalid stimulus 92 throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sNextAction, $oObj->GetName(), $oObj->GetStateLabel())); 93 } 94 // 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) 95 $aExpectedAttributes = $oObj->GetTransitionAttributes($sNextAction); 96 97 if (count($aExpectedAttributes) == 0) 98 { 99 // If all the mandatory fields are already present, just apply the transition silently... 100 if ($oObj->ApplyStimulus($sNextAction)) 101 { 102 $oObj->DBUpdate(); 103 } 104 ReloadAndDisplay($oP, $oObj); 105 } 106 else 107 { 108 // redirect to the 'stimulus' action 109 $oAppContext = new ApplicationContext(); 110//echo "<p>Missing Attributes <pre>".print_r($aExpectedAttributes, true)."</pre></p>\n"; 111 112 $oP->add_header('Location: '.utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=stimulus&class='.get_class($oObj).'&stimulus='.$sNextAction.'&id='.$oObj->getKey().'&'.$oAppContext->GetForLink()); 113 } 114} 115 116function ReloadAndDisplay($oPage, $oObj, $sMessageId = '', $sMessage = '', $sSeverity = null) 117{ 118 $oAppContext = new ApplicationContext(); 119 if ($sMessageId != '') 120 { 121 cmdbAbstractObject::SetSessionMessage(get_class($oObj), $oObj->GetKey(), $sMessageId, $sMessage, $sSeverity, 0, true /* must not exist */); 122 } 123 $oPage->add_header('Location: '.utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=details&class='.get_class($oObj).'&id='.$oObj->getKey().'&'.$oAppContext->GetForLink()); 124} 125 126/** 127 * Displays the details of an object 128 * @param $oP WebPage Page for the output 129 * @param $sClass string The name of the class of the object 130 * @param $oObj DBObject The object to display 131 * @param $id mixed Identifier of the object (name or ID) 132 * @throws \CoreException 133 * @throws \DictExceptionMissingString 134 * @throws \SecurityException 135*/ 136function DisplayDetails($oP, $sClass, $oObj, $id) 137{ 138 $sClassLabel = MetaModel::GetName($sClass); 139 140// 2018-04-11 : removal of the search block 141// $oSearch = new DBObjectSearch($sClass); 142// $oBlock = new DisplayBlock($oSearch, 'search', false); 143// $oBlock->Display($oP, 0, array( 144// 'table_id' => 'search-widget-results-outer', 145// 'open' => false, 146// 'update_history' => false, 147// )); 148 149 // The object could be listed, check if it is actually allowed to view it 150 $oSet = CMDBObjectSet::FromObject($oObj); 151 if (UserRights::IsActionAllowed($sClass, UR_ACTION_READ, $oSet) == UR_ALLOWED_NO) 152 { 153 throw new SecurityException('User not allowed to view this object', array('class' => $sClass, 'id' => $id)); 154 } 155 $oP->set_title(Dict::Format('UI:DetailsPageTitle', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding 156 $oObj->DisplayDetails($oP); 157} 158 159/** 160 * Display the session messages relative to the object identified by its "message key" (class::id) 161 * @param string $sMessageKey 162 * @param WebPage $oPage 163 */ 164function DisplayMessages($sMessageKey, WebPage $oPage) 165{ 166 if (array_key_exists('obj_messages', $_SESSION) && array_key_exists($sMessageKey, $_SESSION['obj_messages'])) 167 { 168 $aMessages = array(); 169 $aRanks = array(); 170 foreach ($_SESSION['obj_messages'][$sMessageKey] as $sMessageId => $aMessageData) 171 { 172 $sMsgClass = 'message_'.$aMessageData['severity']; 173 $aMessages[] = "<div class=\"header_message $sMsgClass\">".$aMessageData['message']."</div>"; 174 $aRanks[] = $aMessageData['rank']; 175 } 176 unset($_SESSION['obj_messages'][$sMessageKey]); 177 array_multisort($aRanks, $aMessages); 178 foreach ($aMessages as $sMessage) 179 { 180 $oPage->add($sMessage); 181 } 182 } 183} 184 185/** 186 * Helper to update the breadrumb for the current object 187 * @param DBObject $oObj 188 * @param WebPage $oPage 189 * @throws \CoreException 190 * @throws \DictExceptionMissingString 191*/ 192function SetObjectBreadCrumbEntry(DBObject $oObj, WebPage $oPage) 193{ 194 $sClass = get_class($oObj); // get the leaf class 195 $sIcon = MetaModel::GetClassIcon($sClass, false); 196 if ($sIcon == '') 197 { 198 $sIcon = utils::GetAbsoluteUrlAppRoot().'images/breadcrumb_object.png'; 199 } 200 $oPage->SetBreadCrumbEntry("ui-details-$sClass-".$oObj->GetKey(), $oObj->Get('friendlyname'), MetaModel::GetName($sClass).': '.$oObj->Get('friendlyname'), '', $sIcon); 201} 202 203/** 204 * Displays the result of a search request 205 * @param $oP WebPage Web page for the output 206 * @param $oFilter DBSearch The search of objects to display 207 * @param $bSearchForm boolean Whether or not to display the search form at the top the page 208 * @param $sBaseClass string The base class for the search (can be different from the actual class of the results) 209 * @param $sFormat string The format to use for the output: csv or html 210 * @param $bDoSearch bool True to display the search results below the search form 211 * @param $bSearchFormOpen bool True to display the search form fully expanded (only if $bSearchForm of course) 212 * @throws \CoreException 213 * @throws \DictExceptionMissingString 214 */ 215function DisplaySearchSet($oP, $oFilter, $bSearchForm = true, $sBaseClass = '', $sFormat = '', $bDoSearch = true, $bSearchFormOpen = true) 216{ 217 if ($bSearchForm) 218 { 219 $aParams = array('open' => $bSearchFormOpen, 'table_id' => '1'); 220 if (!empty($sBaseClass)) 221 { 222 $aParams['baseClass'] = $sBaseClass; 223 } 224 $oBlock = new DisplayBlock($oFilter, 'search', false /* Asynchronous */, $aParams); 225 $oBlock->Display($oP, 0); 226 } 227 if ($bDoSearch) 228 { 229 if (strtolower($sFormat) == 'csv') 230 { 231 $oBlock = new DisplayBlock($oFilter, 'csv', false); 232 $oBlock->Display($oP, 1); 233 // Adjust the size of the Textarea containing the CSV to fit almost all the remaining space 234 $oP->add_ready_script(" $('#1>textarea').height($('#1').parent().height() - $('#0').outerHeight() - 30).width( $('#1').parent().width() - 20);"); // adjust the size of the block 235 } 236 else 237 { 238 $oBlock = new DisplayBlock($oFilter, 'list', false); 239 $oBlock->Display($oP, 1); 240 241 // Breadcrumb 242 //$iCount = $oBlock->GetDisplayedCount(); 243 $sPageId = "ui-search-".$oFilter->GetClass(); 244 $sLabel = MetaModel::GetName($oFilter->GetClass()); 245 $oP->SetBreadCrumbEntry($sPageId, $sLabel, '', '', '../images/breadcrumb-search.png'); 246 } 247 } 248} 249 250/** 251 * Displays a form (checkboxes) to select the objects for which to apply a given action 252 * Only the objects for which the action is valid can be checked. By default all valid objects are checked 253 * 254 * @param \WebPage $oP WebPage The page for output 255 * @param \DBSearch $oFilter DBSearch The filter that defines the list of objects 256 * @param string $sNextOperation string The next operation (code) to be executed when the form is submitted 257 * @param ActionChecker $oChecker ActionChecker The helper class/instance used to check for which object the action is valid 258 * @param array $aExtraFormParams 259 * 260 * @throws \ApplicationException 261 */ 262function DisplayMultipleSelectionForm($oP, $oFilter, $sNextOperation, $oChecker, $aExtraFormParams = array()) 263{ 264 $oAppContext = new ApplicationContext(); 265 $iBulkActionAllowed = $oChecker->IsAllowed(); 266 $aExtraParams = array('selection_type' => 'multiple', 'selection_mode' => true, 'display_limit' => false, 'menu' => false); 267 if ($iBulkActionAllowed == UR_ALLOWED_DEPENDS) 268 { 269 $aExtraParams['selection_enabled'] = $oChecker->GetAllowedIDs(); 270 } 271 else if(UR_ALLOWED_NO) 272 { 273 throw new ApplicationException(Dict::Format('UI:ActionNotAllowed')); 274 } 275 276 $oBlock = new DisplayBlock($oFilter, 'list', false); 277 $oP->add("<form method=\"post\" action=\"./UI.php\">\n"); 278 $oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\n"); 279 $oP->add("<input type=\"hidden\" name=\"class\" value=\"".$oFilter->GetClass()."\">\n"); 280 $oP->add("<input type=\"hidden\" name=\"filter\" value=\"".htmlentities($oFilter->Serialize(), ENT_QUOTES, 'UTF-8')."\">\n"); 281 $oP->add("<input type=\"hidden\" name=\"transaction_id\" value=\"".utils::GetNewTransactionId()."\">\n"); 282 foreach($aExtraFormParams as $sName => $sValue) 283 { 284 $oP->add("<input type=\"hidden\" name=\"$sName\" value=\"$sValue\">\n"); 285 } 286 $oP->add($oAppContext->GetForForm()); 287 $oBlock->Display($oP, 1, $aExtraParams); 288 $oP->add("<input type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"window.history.back()\"> <input type=\"submit\" value=\"".Dict::S('UI:Button:Next')."\">\n"); 289 $oP->add("</form>\n"); 290 $oP->add_ready_script("$('#1 table.listResults').trigger('check_all');"); 291} 292 293function DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj) 294{ 295 $oP->SetCurrentTab(Dict::S('UI:RelationshipList')); 296 $oP->add("<div id=\"impacted_objects\" style=\"width:100%;background-color:#fff;padding:10px;\">"); 297 $sOldRelation = $sRelation; 298 if (($sRelation == 'impacts') && ($sDirection == 'up')) 299 { 300 $sOldRelation = 'depends on'; 301 } 302 $oP->add("<h1>".MetaModel::GetRelationDescription($sOldRelation).' '.$oObj->GetName()."</h1>\n"); 303 $oP->add("<div id=\"impacted_objects_lists\">"); 304 $oP->add('<img src="../images/indicator.gif">'); 305 /* 306 * Content is rendered asynchronously via pages/ajax.render.php?operation=relation_lists 307 */ 308 /* 309 $iBlock = 1; // Zero is not a valid blockid 310 foreach($aResults as $sListClass => $aObjects) 311 { 312 $oSet = CMDBObjectSet::FromArray($sListClass, $aObjects); 313 $oP->add("<div class=\"page_header\">\n"); 314 $oP->add("<h2>".MetaModel::GetClassIcon($sListClass)." <span class=\"hilite\">".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aObjects), Metamodel::GetName($sListClass))."</h2>\n"); 315 $oP->add("</div>\n"); 316 $oBlock = DisplayBlock::FromObjectSet($oSet, 'list'); 317 $oBlock->Display($oP, $iBlock++, array('table_id' => get_class($oObj).'_'.$sRelation.'_'.$sDirection.'_'.$sListClass)); 318 $oP->P(' '); // Some space ? 319 } 320 */ 321 $oP->add("</div>"); 322 $oP->add("</div>"); 323} 324 325function DisplayNavigatorGroupTab($oP) 326{ 327 $oP->SetCurrentTab(Dict::S('UI:RelationGroups')); 328 $oP->add("<div id=\"impacted_groups\" style=\"width:100%;background-color:#fff;padding:10px;\">"); 329 $oP->add('<img src="../images/indicator.gif">'); 330 /* 331 * Content is rendered asynchronously via pages/ajax.render.php?operation=relation_groups 332 */ 333 $oP->add("</div>"); 334} 335 336/*********************************************************************************** 337 * 338 * Main user interface page starts here 339 * 340 ***********************************************************************************/ 341require_once('../approot.inc.php'); 342require_once(APPROOT.'/application/application.inc.php'); 343require_once(APPROOT.'/application/itopwebpage.class.inc.php'); 344require_once(APPROOT.'/application/wizardhelper.class.inc.php'); 345 346require_once(APPROOT.'/application/startup.inc.php'); 347 348try 349{ 350 $operation = utils::ReadParam('operation', ''); 351 $bPrintable = (utils::ReadParam('printable', 0) == '1'); 352 353 $oKPI = new ExecutionKPI(); 354 $oKPI->ComputeAndReport('Data model loaded'); 355 356 $oKPI = new ExecutionKPI(); 357 358 require_once(APPROOT.'/application/loginwebpage.class.inc.php'); 359 $sLoginMessage = LoginWebPage::DoLogin(); // Check user rights and prompt if needed 360 $oAppContext = new ApplicationContext(); 361 362 $oKPI->ComputeAndReport('User login'); 363 364 $oP = new iTopWebPage(Dict::S('UI:WelcomeToITop'), $bPrintable); 365 $oP->SetMessage($sLoginMessage); 366 367 // All the following actions use advanced forms that require more javascript to be loaded 368 switch($operation) 369 { 370 case 'new': // Form to create a new object 371 case 'modify': // Form to modify an object 372 case 'apply_new': // Creation of a new object 373 case 'apply_modify': // Applying the modifications to an existing object 374 case 'form_for_modify_all': // Form to modify multiple objects (bulk modify) 375 case 'bulk_stimulus': // For to apply a stimulus to multiple objects 376 case 'stimulus': // Form displayed when applying a stimulus (state change) 377 case 'apply_stimulus': // Form displayed when applying a stimulus (state change) 378 $oP->add_linked_script("../js/json.js"); 379 $oP->add_linked_script("../js/forms-json-utils.js"); 380 $oP->add_linked_script("../js/wizardhelper.js"); 381 $oP->add_linked_script("../js/wizard.utils.js"); 382 $oP->add_linked_script("../js/linkswidget.js"); 383 $oP->add_linked_script("../js/linksdirectwidget.js"); 384 $oP->add_linked_script("../js/extkeywidget.js"); 385 $oP->add_linked_script("../js/jquery.blockUI.js"); 386 break; 387 } 388 389 switch($operation) 390 { 391 /////////////////////////////////////////////////////////////////////////////////////////// 392 393 case 'details': // Details of an object 394 $sClass = utils::ReadParam('class', ''); 395 $id = utils::ReadParam('id', ''); 396 if ( empty($sClass) || empty($id)) 397 { 398 throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); 399 } 400 401 if (is_numeric($id)) 402 { 403 $oObj = MetaModel::GetObject($sClass, $id, false /* MustBeFound */); 404 } 405 else 406 { 407 $oObj = MetaModel::GetObjectByName($sClass, $id, false /* MustBeFound */); 408 } 409 if (is_null($oObj)) 410 { 411 // Check anyhow if there is a message for this object (like you've just created it) 412 $sMessageKey = $sClass.'::'.$id; 413 DisplayMessages($sMessageKey, $oP); 414 $oP->set_title(Dict::S('UI:ErrorPageTitle')); 415 416 // Attempt to load the object in archive mode 417 utils::PushArchiveMode(true); 418 if (is_numeric($id)) 419 { 420 $oObj = MetaModel::GetObject($sClass, $id, false /* MustBeFound */); 421 } 422 else 423 { 424 $oObj = MetaModel::GetObjectByName($sClass, $id, false /* MustBeFound */); 425 } 426 utils::PopArchiveMode(); 427 if (is_null($oObj)) 428 { 429 $oP->P(Dict::S('UI:ObjectDoesNotExist')); 430 } 431 else 432 { 433 SetObjectBreadCrumbEntry($oObj, $oP); 434 $oP->P(Dict::S('UI:ObjectArchived')); 435 } 436 } 437 else 438 { 439 try 440 { 441 $oObj->Reload(); 442 } 443 catch(Exception $e) 444 { 445 // Probably not allowed to see this instance of a derived class 446 447 // Check anyhow if there is a message for this object (like you've just created it) 448 $sMessageKey = $sClass.'::'.$id; 449 DisplayMessages($sMessageKey, $oP); 450 451 $oObj = null; 452 $oP->set_title(Dict::S('UI:ErrorPageTitle')); 453 $oP->P(Dict::S('UI:ObjectDoesNotExist')); 454 } 455 if (!is_null($oObj)) 456 { 457 SetObjectBreadCrumbEntry($oObj, $oP); 458 DisplayDetails($oP, $sClass, $oObj, $id); 459 } 460 } 461 break; 462 463 case 'release_lock_and_details': 464 $oP->DisableBreadCrumb(); 465 $sClass = utils::ReadParam('class', ''); 466 $id = utils::ReadParam('id', ''); 467 $oObj = MetaModel::GetObject($sClass, $id); 468 $sToken = utils::ReadParam('token', ''); 469 if ($sToken != '') 470 { 471 iTopOwnershipLock::ReleaseLock($sClass, $id, $sToken); 472 } 473 cmdbAbstractObject::ReloadAndDisplay($oP, $oObj, array('operation' => 'details')); 474 break; 475 476 /////////////////////////////////////////////////////////////////////////////////////////// 477 478 case 'search_oql': // OQL query 479 $sOQLClass = utils::ReadParam('oql_class', '', false, 'class'); 480 $sBaseClass = utils::ReadParam('base_class', $sOQLClass, false, 'class'); 481 $sOQLClause = utils::ReadParam('oql_clause', '', false, 'raw_data'); 482 $sFormat = utils::ReadParam('format', ''); 483 $bSearchForm = utils::ReadParam('search_form', true); 484 $sTitle = utils::ReadParam('title', 'UI:SearchResultsPageTitle'); 485 if (empty($sOQLClass)) 486 { 487 throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'oql_class')); 488 } 489 $oP->set_title(Dict::S($sTitle)); 490 $oP->add('<h1>'.Dict::S($sTitle).'</h1>'); 491 $sOQL = "SELECT $sOQLClass $sOQLClause"; 492 try 493 { 494 $oFilter = DBObjectSearch::FromOQL($sOQL); 495 DisplaySearchSet($oP, $oFilter, $bSearchForm, $sBaseClass, $sFormat); 496 } 497 catch(CoreException $e) 498 { 499 $oFilter = new DBObjectSearch($sOQLClass); 500 $oSet = new DBObjectSet($oFilter); 501 if ($bSearchForm) 502 { 503 $oBlock = new DisplayBlock($oFilter, 'search', false); 504 $oBlock->Display($oP, 0, array('table_id' => 'search-widget-result-outer')); 505 } 506 $oP->add('<div id="search-widget-result-outer"><p><b>'.Dict::Format('UI:Error:IncorrectOQLQuery_Message', $e->getHtmlDesc()).'</b></p></div>'); 507 } 508 catch(Exception $e) 509 { 510 $oP->P('<b>'.Dict::Format('UI:Error:AnErrorOccuredWhileRunningTheQuery_Message', $e->getMessage()).'</b>'); 511 } 512 break; 513 514 /////////////////////////////////////////////////////////////////////////////////////////// 515 516 case 'search_form': // Search form 517 $sClass = utils::ReadParam('class', '', false, 'class'); 518 $sFormat = utils::ReadParam('format', 'html'); 519 $bSearchForm = utils::ReadParam('search_form', true); 520 $bDoSearch = utils::ReadParam('do_search', true); 521 if (empty($sClass)) 522 { 523 throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); 524 } 525 $oP->set_title(Dict::S('UI:SearchResultsPageTitle')); 526 $oFilter = new DBObjectSearch($sClass); 527 DisplaySearchSet($oP, $oFilter, $bSearchForm, '' /* sBaseClass */, $sFormat, $bDoSearch, true /* Search Form Expanded */); 528 break; 529 530 /////////////////////////////////////////////////////////////////////////////////////////// 531 532 case 'search': // Serialized DBSearch 533 $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); 534 $sFormat = utils::ReadParam('format', ''); 535 $bSearchForm = utils::ReadParam('search_form', true); 536 if (empty($sFilter)) 537 { 538 throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'filter')); 539 } 540 $oP->set_title(Dict::S('UI:SearchResultsPageTitle')); 541 $oFilter = DBSearch::unserialize($sFilter); // TO DO : check that the filter is valid 542 $oFilter->UpdateContextFromUser(); 543 DisplaySearchSet($oP, $oFilter, $bSearchForm, '' /* sBaseClass */, $sFormat); 544 break; 545 546 /////////////////////////////////////////////////////////////////////////////////////////// 547 548 case 'full_text': // Global "google-like" search 549 $oP->DisableBreadCrumb(); 550 $sFullText = trim(utils::ReadParam('text', '', false, 'raw_data')); 551 $iTune = utils::ReadParam('tune', 0); 552 if (empty($sFullText)) 553 { 554 $oP->p(Dict::S('UI:Search:NoSearch')); 555 } 556 else 557 { 558 $iErrors = 0; 559 560 // Check if a class name/label is supplied to limit the search 561 $sClassName = ''; 562 if (preg_match('/^([^\"]+):(.+)$/', $sFullText, $aMatches)) 563 { 564 $sClassName = $aMatches[1]; 565 if (MetaModel::IsValidClass($sClassName)) 566 { 567 $sFullText = trim($aMatches[2]); 568 } 569 elseif ($sClassName = MetaModel::GetClassFromLabel($sClassName, false /* => not case sensitive */)) 570 { 571 $sFullText = trim($aMatches[2]); 572 } 573 } 574 575 if (preg_match('/^"(.*)"$/', $sFullText, $aMatches)) 576 { 577 // The text is surrounded by double-quotes, remove the quotes and treat it as one single expression 578 $aFullTextNeedles = array($aMatches[1]); 579 } 580 else 581 { 582 // Split the text on the blanks and treat this as a search for <word1> AND <word2> AND <word3> 583 $aFullTextNeedles = explode(' ', $sFullText); 584 } 585 586 // Check the needle length 587 $iMinLenth = MetaModel::GetConfig()->Get('full_text_needle_min'); 588 foreach ($aFullTextNeedles as $sNeedle) 589 { 590 if (strlen($sNeedle) < $iMinLenth) 591 { 592 $oP->p(Dict::Format('UI:Search:NeedleTooShort', $sNeedle, $iMinLenth)); 593 $key = array_search($sNeedle, $aFullTextNeedles); 594 if($key!== false) 595 { 596 unset($aFullTextNeedles[$key]); 597 } 598 } 599 } 600 if(empty($aFullTextNeedles)) 601 { 602 $oP->p(Dict::S('UI:Search:NoSearch')); 603 break; 604 } 605 $sFullText = implode(' ', $aFullTextNeedles); 606 607 // Sanity check of the accelerators 608 /** @var array $aAccelerators */ 609 $aAccelerators = MetaModel::GetConfig()->Get('full_text_accelerators'); 610 foreach ($aAccelerators as $sClass => $aAccelerator) 611 { 612 try 613 { 614 $bSkip = array_key_exists('skip', $aAccelerator) ? $aAccelerator['skip'] : false; 615 if (!$bSkip) 616 { 617 $oSearch = DBObjectSearch::FromOQL($aAccelerator['query']); 618 if ($sClass != $oSearch->GetClass()) 619 { 620 $oP->p("Full text accelerator for class '$sClass': searched class mismatch (".$oSearch->GetClass().")"); 621 $iErrors++; 622 } 623 } 624 } 625 catch (OqlException $e) 626 { 627 $oP->p("Full text accelerator for class '$sClass': ".$e->getHtmlDesc()); 628 $iErrors++; 629 } 630 } 631 632 if ($iErrors == 0) 633 { 634 $oP->set_title(Dict::S('UI:SearchResultsPageTitle')); 635 $sPageId = "ui-global-search"; 636 $sLabel = Dict::S('UI:SearchResultsTitle'); 637 $sDescription = Dict::S('UI:SearchResultsTitle+'); 638 $oP->SetBreadCrumbEntry($sPageId, $sLabel, $sDescription, '', utils::GetAbsoluteUrlAppRoot().'images/search.png'); 639 $oP->add("<div style=\"padding: 10px;\">\n"); 640 $oP->add("<div class=\"header_message\" id=\"full_text_progress\" style=\"position: fixed; background-color: #cccccc; opacity: 0.7; padding: 1.5em;\">\n"); 641 $oP->add('<img id="full_text_indicator" src="../images/indicator.gif"> <span style="padding: 1.5em;">'.Dict::Format('UI:Search:Ongoing', htmlentities($sFullText, ENT_QUOTES, 'UTF-8')).'</span>'); 642 $oP->add("</div>\n"); 643 $oP->add("<div id=\"full_text_results\">\n"); 644 $oP->add("<div id=\"full_text_progress_placeholder\" style=\"padding: 1.5em;\"> </div>\n"); 645 $oP->add("<h2>".Dict::Format('UI:FullTextSearchTitle_Text', htmlentities($sFullText, ENT_QUOTES, 'UTF-8'))."</h2>"); 646 $oP->add("</div>\n"); 647 $oP->add("</div>\n"); 648 $sJSClass = addslashes($sClassName); 649 $sJSNeedles = json_encode($aFullTextNeedles); 650 $oP->add_ready_script( 651<<<EOF 652 var oParams = {operation: 'full_text_search', position: 0, 'class': '$sJSClass', needles: $sJSNeedles, tune: $iTune}; 653 $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', oParams, function(data) { 654 $('#full_text_results').append(data); 655 }); 656EOF 657 ); 658 if ($iTune > 0) 659 { 660 $oP->add_script("var oTimeStatistics = {};"); 661 } 662 } 663 } 664 break; 665 666 /////////////////////////////////////////////////////////////////////////////////////////// 667 668 case 'modify': // Form to modify an object 669 $oP->DisableBreadCrumb(); 670 $sClass = utils::ReadParam('class', '', false, 'class'); 671 $id = utils::ReadParam('id', ''); 672 if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! 673 { 674 throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); 675 } 676 // Check if the user can modify this object 677 $oObj = MetaModel::GetObject($sClass, $id, false /* MustBeFound */); 678 if (is_null($oObj)) 679 { 680 $oP->set_title(Dict::S('UI:ErrorPageTitle')); 681 $oP->P(Dict::S('UI:ObjectDoesNotExist')); 682 } 683 else 684 { 685 // The object could be read - check if it is allowed to modify it 686 $oSet = CMDBObjectSet::FromObject($oObj); 687 if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_NO) 688 { 689 throw new SecurityException('User not allowed to modify this object', array('class' => $sClass, 'id' => $id)); 690 } 691 // Note: code duplicated to the case 'apply_modify' when a data integrity issue has been found 692 $oObj->DisplayModifyForm($oP, array('wizard_container' => 1)); // wizard_container: Display the blue borders and the title above the form 693 } 694 break; 695 696 /////////////////////////////////////////////////////////////////////////////////////////// 697 698 case 'select_for_modify_all': // Select the list of objects to be modified (bulk modify) 699 $oP->DisableBreadCrumb(); 700 $oP->set_title(Dict::S('UI:ModifyAllPageTitle')); 701 $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); 702 if (empty($sFilter)) 703 { 704 throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'filter')); 705 } 706 $oFilter = DBObjectSearch::unserialize($sFilter); //TODO : check that the filter is valid 707 // Add user filter 708 $oFilter->UpdateContextFromUser(); 709 $sClass = $oFilter->GetClass(); 710 $oChecker = new ActionChecker($oFilter, UR_ACTION_BULK_MODIFY); 711 $oP->add("<h1>".Dict::S('UI:ModifyAllPageTitle')."</h1>\n"); 712 713 DisplayMultipleSelectionForm($oP, $oFilter, 'form_for_modify_all', $oChecker); 714 break; 715 /////////////////////////////////////////////////////////////////////////////////////////// 716 717 case 'form_for_modify_all': // Form to modify multiple objects (bulk modify) 718 $oP->DisableBreadCrumb(); 719 $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); 720 $sClass = utils::ReadParam('class', '', false, 'class'); 721 $oFullSetFilter = DBObjectSearch::unserialize($sFilter); 722 // Add user filter 723 $oFullSetFilter->UpdateContextFromUser(); 724 $aSelectedObj = utils::ReadMultipleSelection($oFullSetFilter); 725 $sCancelUrl = "./UI.php?operation=search&filter=".urlencode($sFilter)."&".$oAppContext->GetForLink(); 726 $aContext = array('filter' => htmlentities($sFilter, ENT_QUOTES, 'UTF-8')); 727 cmdbAbstractObject::DisplayBulkModifyForm($oP, $sClass, $aSelectedObj, 'preview_or_modify_all', $sCancelUrl, array(), $aContext); 728 break; 729 730 /////////////////////////////////////////////////////////////////////////////////////////// 731 732 case 'preview_or_modify_all': // Preview or apply bulk modify 733 $oP->DisableBreadCrumb(); 734 $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); 735 $oFilter = DBObjectSearch::unserialize($sFilter); // TO DO : check that the filter is valid 736 // Add user filter 737 $oFilter->UpdateContextFromUser(); 738 $oChecker = new ActionChecker($oFilter, UR_ACTION_BULK_MODIFY); 739 740 $sClass = utils::ReadParam('class', '', false, 'class'); 741 $bPreview = utils::ReadParam('preview_mode', ''); 742 $sSelectedObj = utils::ReadParam('selectObj', '', false, 'raw_data'); 743 if ( empty($sClass) || empty($sSelectedObj)) // TO DO: check that the class name is valid ! 744 { 745 throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'selectObj')); 746 } 747 $aSelectedObj = explode(',', $sSelectedObj); 748 $sCancelUrl = "./UI.php?operation=search&filter=".urlencode($sFilter)."&".$oAppContext->GetForLink(); 749 $aContext = array( 750 'filter' => htmlentities($sFilter, ENT_QUOTES, 'UTF-8'), 751 'selectObj' => $sSelectedObj, 752 ); 753 cmdbAbstractObject::DoBulkModify($oP, $sClass, $aSelectedObj, 'preview_or_modify_all', $bPreview, $sCancelUrl, $aContext); 754 break; 755 756 /////////////////////////////////////////////////////////////////////////////////////////// 757 758 case 'new': // Form to create a new object 759 $oP->DisableBreadCrumb(); 760 $sClass = utils::ReadParam('class', '', false, 'class'); 761 $sStateCode = utils::ReadParam('state', ''); 762 $bCheckSubClass = utils::ReadParam('checkSubclass', true); 763 if ( empty($sClass) ) 764 { 765 throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); 766 } 767 768/* 769 $aArgs = utils::ReadParam('default', array(), false, 'raw_data'); 770 $aContext = $oAppContext->GetAsHash(); 771 foreach( $oAppContext->GetNames() as $key) 772 { 773 $aArgs[$key] = $oAppContext->GetCurrentValue($key); 774 } 775*/ 776 // If the specified class has subclasses, ask the user an instance of which class to create 777 $aSubClasses = MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself 778 $aPossibleClasses = array(); 779 $sRealClass = ''; 780 if ($bCheckSubClass) 781 { 782 foreach($aSubClasses as $sCandidateClass) 783 { 784 if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) 785 { 786 $aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass); 787 } 788 } 789 // Only one of the subclasses can be instantiated... 790 if (count($aPossibleClasses) == 1) 791 { 792 $aKeys = array_keys($aPossibleClasses); 793 $sRealClass = $aKeys[0]; 794 } 795 } 796 else 797 { 798 $sRealClass = $sClass; 799 } 800 801 if (!empty($sRealClass)) 802 { 803 // Display the creation form 804 $sClassLabel = MetaModel::GetName($sRealClass); 805 // Note: some code has been duplicated to the case 'apply_new' when a data integrity issue has been found 806 $oP->set_title(Dict::Format('UI:CreationPageTitle_Class', $sClassLabel)); 807 $oP->add("<h1>".MetaModel::GetClassIcon($sRealClass)." ".Dict::Format('UI:CreationTitle_Class', $sClassLabel)."</h1>\n"); 808 $oP->add("<div class=\"wizContainer\">\n"); 809 810 // Set all the default values in an object and clone this "default" object 811 $oObjToClone = MetaModel::NewObject($sRealClass); 812 813 // 1st - set context values 814 $oAppContext->InitObjectFromContext($oObjToClone); 815 // 2nd - set values from the page argument 'default' 816 $oObjToClone->UpdateObjectFromArg('default'); 817 $aPrefillFormParam = array( 'user' => $_SESSION["auth_user"], 818 'context' => $oAppContext->GetAsHash(), 819 'default' => utils::ReadParam('default', array(), '', 'raw_data'), 820 'origin' => 'console' 821 ); 822 $oObjToClone->PrefillForm('creation_from_0',$aPrefillFormParam); 823 824 cmdbAbstractObject::DisplayCreationForm($oP, $sRealClass, $oObjToClone, array()); 825 $oP->add("</div>\n"); 826 } 827 else 828 { 829 // Select the derived class to create 830 $sClassLabel = MetaModel::GetName($sClass); 831 $oP->add("<h1>".MetaModel::GetClassIcon($sClass)." ".Dict::Format('UI:CreationTitle_Class', $sClassLabel)."</h1>\n"); 832 $oP->add("<div class=\"wizContainer\">\n"); 833 $oP->add('<form>'); 834 $oP->add('<p>'.Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel)); 835 $aDefaults = utils::ReadParam('default', array(), false, 'raw_data'); 836 $oP->add($oAppContext->GetForForm()); 837 $oP->add("<input type=\"hidden\" name=\"checkSubclass\" value=\"0\">\n"); 838 $oP->add("<input type=\"hidden\" name=\"state\" value=\"$sStateCode\">\n"); 839 $oP->add("<input type=\"hidden\" name=\"operation\" value=\"new\">\n"); 840 foreach($aDefaults as $key => $value) 841 { 842 if (is_array($value)) 843 { 844 foreach($value as $key2 => $value2) 845 { 846 if (is_array($value2)) 847 { 848 foreach($value2 as $key3 => $value3) 849 { 850 $sValue = htmlentities($value3, ENT_QUOTES, 'UTF-8'); 851 $oP->add("<input type=\"hidden\" name=\"default[$key][$key2][$key3]\" value=\"$sValue\">\n"); 852 } 853 } 854 else 855 { 856 $sValue = htmlentities($value2, ENT_QUOTES, 'UTF-8'); 857 $oP->add("<input type=\"hidden\" name=\"default[$key][$key2]\" value=\"$sValue\">\n"); 858 } 859 } 860 } 861 else 862 { 863 $sValue = htmlentities($value, ENT_QUOTES, 'UTF-8'); 864 $oP->add("<input type=\"hidden\" name=\"default[$key]\" value=\"$sValue\">\n"); 865 } 866 } 867 $oP->add('<select name="class">'); 868 asort($aPossibleClasses); 869 foreach($aPossibleClasses as $sClassName => $sClassLabel) 870 { 871 $sSelected = ($sClassName == $sClass) ? 'selected' : ''; 872 $oP->add("<option $sSelected value=\"$sClassName\">$sClassLabel</option>"); 873 } 874 $oP->add('</select>'); 875 $oP->add(" <input type=\"submit\" value=\"".Dict::S('UI:Button:Apply')."\"></p>"); 876 $oP->add('</form>'); 877 $oP->add("</div>\n"); 878 } 879 break; 880 881 /////////////////////////////////////////////////////////////////////////////////////////// 882 883 case 'apply_modify': // Applying the modifications to an existing object 884 $oP->DisableBreadCrumb(); 885 $sClass = utils::ReadPostedParam('class', ''); 886 $sClassLabel = MetaModel::GetName($sClass); 887 $id = utils::ReadPostedParam('id', ''); 888 $sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id'); 889 if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! 890 { 891 throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); 892 } 893 $bDisplayDetails = true; 894 $oObj = MetaModel::GetObject($sClass, $id, false); 895 if ($oObj == null) 896 { 897 $bDisplayDetails = false; 898 $oP->set_title(Dict::S('UI:ErrorPageTitle')); 899 $oP->P(Dict::S('UI:ObjectDoesNotExist')); 900 } 901 elseif (!utils::IsTransactionValid($sTransactionId, false)) 902 { 903 $sUser = UserRights::GetUser(); 904 IssueLog::Error("UI.php '$operation' : invalid transaction_id ! data: user='$sUser', class='$sClass'"); 905 $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding 906 $oP->p("<strong>".Dict::S('UI:Error:ObjectAlreadyUpdated')."</strong>\n"); 907 } 908 else 909 { 910 $aErrors = $oObj->UpdateObjectFromPostedForm(); 911 $sMessage = ''; 912 $sSeverity = 'ok'; 913 914 if (!$oObj->IsModified() && empty($aErrors)) 915 { 916 $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding 917 $sMessage = Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); 918 $sSeverity = 'info'; 919 } 920 else 921 { 922 try 923 { 924 CMDBSource::Query('START TRANSACTION'); 925 if (!empty($aErrors)) 926 { 927 throw new CoreCannotSaveObjectException(array('id' => $oObj->GetKey(), 'class' => $sClass, 'issues' => $aErrors)); 928 } 929 $oObj->DBUpdate(); 930 CMDBSource::Query('COMMIT'); 931 $sMessage = Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); 932 $sSeverity = 'ok'; 933 } 934 catch (CoreCannotSaveObjectException $e) 935 { 936 // Found issues, explain and give the user a second chance 937 // 938 CMDBSource::Query('ROLLBACK'); 939 $bDisplayDetails = false; 940 $aIssues = $e->getIssues(); 941 $oP->AddHeaderMessage($e->getHtmlMessage(), 'message_error'); 942 $oObj->DisplayModifyForm($oP, 943 array('wizard_container' => true)); // wizard_container: display the wizard border and the title 944 } 945 catch (DeleteException $e) 946 { 947 CMDBSource::Query('ROLLBACK'); 948 // Say two things: 949 // - 1) Don't be afraid nothing was modified 950 $sMessage = Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); 951 $sSeverity = 'info'; 952 cmdbAbstractObject::SetSessionMessage(get_class($oObj), $oObj->GetKey(), 'UI:Class_Object_NotUpdated', $sMessage, 953 $sSeverity, 0, true /* must not exist */); 954 // - 2) Ok, there was some trouble indeed 955 $sMessage = $e->getMessage(); 956 $sSeverity = 'error'; 957 $bDisplayDetails = true; 958 } 959 utils::RemoveTransaction($sTransactionId); 960 } 961 } 962 if ($bDisplayDetails) 963 { 964 $oObj = MetaModel::GetObject(get_class($oObj), $oObj->GetKey()); //Workaround: reload the object so that the linkedset are displayed properly 965 $sNextAction = utils::ReadPostedParam('next_action', ''); 966 if (!empty($sNextAction)) 967 { 968 ApplyNextAction($oP, $oObj, $sNextAction); 969 } 970 else 971 { 972 // Nothing more to do 973 ReloadAndDisplay($oP, $oObj, 'update', $sMessage, $sSeverity); 974 } 975 976 $bLockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled'); 977 if ($bLockEnabled) 978 { 979 // Release the concurrent lock, if any 980 $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data'); 981 if ($sOwnershipToken !== null) 982 { 983 // We're done, let's release the lock 984 iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken); 985 } 986 } 987 } 988 break; 989 990 /////////////////////////////////////////////////////////////////////////////////////////// 991 992 case 'select_for_deletion': // Select multiple objects for deletion 993 $oP->DisableBreadCrumb(); 994 $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); 995 if (empty($sFilter)) 996 { 997 throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'filter')); 998 } 999 $oP->set_title(Dict::S('UI:BulkDeletePageTitle')); 1000 $oP->add("<h1>".Dict::S('UI:BulkDeleteTitle')."</h1>\n"); 1001 $oFilter = DBSearch::unserialize($sFilter); // TO DO : check that the filter is valid 1002 $oFilter->UpdateContextFromUser(); 1003 $oChecker = new ActionChecker($oFilter, UR_ACTION_BULK_DELETE); 1004 DisplayMultipleSelectionForm($oP, $oFilter, 'bulk_delete', $oChecker); 1005 break; 1006 1007 /////////////////////////////////////////////////////////////////////////////////////////// 1008 1009 case 'bulk_delete_confirmed': // Confirm bulk deletion of objects 1010 $oP->DisableBreadCrumb(); 1011 $sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id'); 1012 if (!utils::IsTransactionValid($sTransactionId)) 1013 { 1014 $sUser = UserRights::GetUser(); 1015 $sClass = utils::ReadParam('class', '', false, 'class'); 1016 IssueLog::Error("UI.php '$operation' : invalid transaction_id ! data: user='$sUser', class='$sClass'"); 1017 throw new ApplicationException(Dict::S('UI:Error:ObjectsAlreadyDeleted')); 1018 } 1019 // Fall through 1020 1021 /////////////////////////////////////////////////////////////////////////////////////////// 1022 1023 case 'delete': 1024 case 'bulk_delete': // Actual bulk deletion (if confirmed) 1025 $oP->DisableBreadCrumb(); 1026 $sClass = utils::ReadParam('class', '', false, 'class'); 1027 $sClassLabel = MetaModel::GetName($sClass); 1028 $aObjects = array(); 1029 if ($operation == 'delete') 1030 { 1031 // Single object 1032 $id = utils::ReadParam('id', ''); 1033 $oObj = MetaModel::GetObject($sClass, $id); 1034 $aObjects[] = $oObj; 1035 if (!UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, DBObjectSet::FromObject($oObj))) 1036 { 1037 throw new SecurityException(Dict::Format('UI:Error:DeleteNotAllowedOn_Class', $sClassLabel)); 1038 } 1039 } 1040 else 1041 { 1042 // Several objects 1043 $sFilter = utils::ReadPostedParam('filter', '', 'raw_data'); 1044 $oFullSetFilter = DBObjectSearch::unserialize($sFilter); 1045 // Add user filter 1046 $oFullSetFilter->UpdateContextFromUser(); 1047 $aSelectObject = utils::ReadMultipleSelection($oFullSetFilter); 1048 if ( empty($sClass) || empty($aSelectObject)) // TO DO: check that the class name is valid ! 1049 { 1050 throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'selectObject[]')); 1051 } 1052 foreach($aSelectObject as $iId) 1053 { 1054 $aObjects[] = MetaModel::GetObject($sClass, $iId); 1055 } 1056 if (count($aObjects) == 1) 1057 { 1058 if (!UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, DBObjectSet::FromArray($sClass, $aObjects))) 1059 { 1060 throw new SecurityException(Dict::Format('UI:Error:BulkDeleteNotAllowedOn_Class', $sClassLabel)); 1061 } 1062 } 1063 else 1064 { 1065 if (!UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, DBObjectSet::FromArray($sClass, $aObjects))) 1066 { 1067 throw new SecurityException(Dict::Format('UI:Error:BulkDeleteNotAllowedOn_Class', $sClassLabel)); 1068 } 1069 $oP->set_title(Dict::S('UI:BulkDeletePageTitle')); 1070 } 1071 } 1072 // Go for the common part... (delete single, delete bulk, delete confirmed) 1073 cmdbAbstractObject::DeleteObjects($oP, $sClass, $aObjects, ($operation != 'bulk_delete_confirmed'), 'bulk_delete_confirmed'); 1074 break; 1075 1076 /////////////////////////////////////////////////////////////////////////////////////////// 1077 1078 case 'apply_new': // Creation of a new object 1079 $oP->DisableBreadCrumb(); 1080 $sClass = utils::ReadPostedParam('class', '', 'class'); 1081 $sClassLabel = MetaModel::GetName($sClass); 1082 $sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id'); 1083 $aErrors = array(); 1084 if ( empty($sClass) ) // TO DO: check that the class name is valid ! 1085 { 1086 throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); 1087 } 1088 if (!utils::IsTransactionValid($sTransactionId, false)) 1089 { 1090 $sUser = UserRights::GetUser(); 1091 IssueLog::Error("UI.php '$operation' : invalid transaction_id ! data: user='$sUser', class='$sClass'"); 1092 $oP->p("<strong>".Dict::S('UI:Error:ObjectAlreadyCreated')."</strong>\n"); 1093 } 1094 else 1095 { 1096 /** @var \cmdbAbstractObject $oObj */ 1097 $oObj = MetaModel::NewObject($sClass); 1098 $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); 1099 if (!empty($sStateAttCode)) 1100 { 1101 $sTargetState = utils::ReadPostedParam('obj_state', ''); 1102 if ($sTargetState != '') 1103 { 1104 $oObj->Set($sStateAttCode, $sTargetState); 1105 } 1106 } 1107 $aErrors = $oObj->UpdateObjectFromPostedForm(); 1108 } 1109 if (isset($oObj) && is_object($oObj)) 1110 { 1111 $sClass = get_class($oObj); 1112 $sClassLabel = MetaModel::GetName($sClass); 1113 1114 try 1115 { 1116 if (!empty($aErrors)) 1117 { 1118 throw new CoreCannotSaveObjectException(array('id' => $oObj->GetKey(), 'class' => $sClass, 'issues' => $aErrors)); 1119 } 1120 1121 $oObj->DBInsertNoReload();// No need to reload 1122 1123 utils::RemoveTransaction($sTransactionId); 1124 $oP->set_title(Dict::S('UI:PageTitle:ObjectCreated')); 1125 1126 // Compute the name, by reloading the object, even if it disappeared from the silo 1127 $oObj = MetaModel::GetObject($sClass, $oObj->GetKey(), true /* Must be found */, true /* Allow All Data*/); 1128 $sName = $oObj->GetName(); 1129 $sMessage = Dict::Format('UI:Title:Object_Of_Class_Created', $sName, $sClassLabel); 1130 1131 $sNextAction = utils::ReadPostedParam('next_action', ''); 1132 if (!empty($sNextAction)) 1133 { 1134 $oP->add("<h1>$sMessage</h1>"); 1135 ApplyNextAction($oP, $oObj, $sNextAction); 1136 } 1137 else 1138 { 1139 // Nothing more to do 1140 ReloadAndDisplay($oP, $oObj, 'create', $sMessage, 'ok'); 1141 } 1142 } 1143 catch (CoreCannotSaveObjectException $e) 1144 { 1145 // Found issues, explain and give the user a second chance 1146 // 1147 $aIssues = $e->getIssues(); 1148 1149 $oP->set_title(Dict::Format('UI:CreationPageTitle_Class', $sClassLabel)); 1150 $oP->add("<h1>".MetaModel::GetClassIcon($sClass)." ".Dict::Format('UI:CreationTitle_Class', $sClassLabel)."</h1>\n"); 1151 $oP->add("<div class=\"wizContainer\">\n"); 1152 $oP->AddHeaderMessage($e->getHtmlMessage(), 'message_error'); 1153 cmdbAbstractObject::DisplayCreationForm($oP, $sClass, $oObj); 1154 $oP->add("</div>\n"); 1155 } 1156 } 1157 break; 1158 1159 /////////////////////////////////////////////////////////////////////////////////////////// 1160 1161 case 'select_bulk_stimulus': // Form displayed when applying a stimulus to many objects 1162 $oP->DisableBreadCrumb(); 1163 $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); 1164 $sStimulus = utils::ReadParam('stimulus', ''); 1165 $sState = utils::ReadParam('state', ''); 1166 if (empty($sFilter) || empty($sStimulus) || empty($sState)) 1167 { 1168 throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'filter', 'stimulus', 'state')); 1169 } 1170 $oFilter = DBObjectSearch::unserialize($sFilter); 1171 $oFilter->UpdateContextFromUser(); 1172 $sClass = $oFilter->GetClass(); 1173 $aStimuli = MetaModel::EnumStimuli($sClass); 1174 $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); 1175 $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); 1176 $oP->set_title($sActionLabel); 1177 $oP->add('<div class="page_header">'); 1178 $oP->add('<h1>'.MetaModel::GetClassIcon($sClass).' '.$sActionLabel.'</h1>'); 1179 $oP->add('</div>'); 1180 1181 $oChecker = new StimulusChecker($oFilter, $sState, $sStimulus); 1182 $aExtraFormParams = array('stimulus' => $sStimulus, 'state' => $sState); 1183 DisplayMultipleSelectionForm($oP, $oFilter, 'bulk_stimulus', $oChecker, $aExtraFormParams); 1184 break; 1185 1186 case 'bulk_stimulus': 1187 $oP->DisableBreadCrumb(); 1188 $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); 1189 $sStimulus = utils::ReadParam('stimulus', ''); 1190 $sState = utils::ReadParam('state', ''); 1191 if (empty($sFilter) || empty($sStimulus) || empty($sState)) 1192 { 1193 throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'filter', 'stimulus', 'state')); 1194 } 1195 $oFilter = DBObjectSearch::unserialize($sFilter); 1196 // Add user filter 1197 $oFilter->UpdateContextFromUser(); 1198 $sClass = $oFilter->GetClass(); 1199 $aSelectObject = utils::ReadMultipleSelection($oFilter); 1200 if (count($aSelectObject) == 0) 1201 { 1202 // Nothing to do, no object was selected ! 1203 throw new ApplicationException(Dict::S('UI:BulkAction:NoObjectSelected')); 1204 } 1205 else 1206 { 1207 $aTransitions = MetaModel::EnumTransitions($sClass, $sState); 1208 $aStimuli = MetaModel::EnumStimuli($sClass); 1209 1210 $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); 1211 $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); 1212 $sTargetState = $aTransitions[$sStimulus]['target_state']; 1213 $aStates = MetaModel::EnumStates($sClass); 1214 $aTargetStateDef = $aStates[$sTargetState]; 1215 1216 $oP->set_title(Dict::Format('UI:StimulusModify_N_ObjectsOf_Class', $sActionLabel, count($aSelectObject), $sClass)); 1217 $oP->add('<div class="page_header">'); 1218 $oP->add('<h1>'.MetaModel::GetClassIcon($sClass).' '.Dict::Format('UI:StimulusModify_N_ObjectsOf_Class', $sActionLabel, count($aSelectObject), $sClass).'</h1>'); 1219 $oP->add('</div>'); 1220 1221 $aExpectedAttributes = MetaModel::GetTransitionAttributes($sClass, $sStimulus, $sState); 1222 $aDetails = array(); 1223 $sFormId = 'apply_stimulus'; 1224 $sFormPrefix = $sFormId.'_'; 1225 $iFieldIndex = 0; 1226 $aFieldsMap = array(); 1227 $aValues = array(); 1228 $aObjects = array(); 1229 foreach($aSelectObject as $iId) 1230 { 1231 $aObjects[] = MetaModel::GetObject($sClass, $iId); 1232 } 1233 $oSet = DBObjectSet::FromArray($sClass, $aObjects); 1234 $oObj = $oSet->ComputeCommonObject($aValues); 1235 $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); 1236 $oObj->Set($sStateAttCode,$sTargetState); 1237 $sReadyScript = ''; 1238 foreach($aExpectedAttributes as $sAttCode => $iExpectCode) 1239 { 1240 $sFieldInputId = $sFormPrefix.$sAttCode; 1241 // Prompt for an attribute if 1242 // - the attribute must be changed or must be displayed to the user for confirmation 1243 // - or the field is mandatory and currently empty 1244 if ( ($iExpectCode & (OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) || 1245 (($iExpectCode & OPT_ATT_MANDATORY) && ($oObj->Get($sAttCode) == '')) ) 1246 { 1247 $aAttributesDef = MetaModel::ListAttributeDefs($sClass); 1248 $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); 1249 $aPrerequisites = MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode); // List of attributes that are needed for the current one 1250 if (count($aPrerequisites) > 0) 1251 { 1252 // When 'enabling' a field, all its prerequisites must be enabled too 1253 $sFieldList = "['{$sFormPrefix}".implode("','{$sFormPrefix}", $aPrerequisites)."']"; 1254 $oP->add_ready_script("$('#enable_{$sFieldInputId}').bind('change', function(evt, sFormId) { return PropagateCheckBox( this.checked, $sFieldList, true); } );\n"); 1255 } 1256 $aDependents = MetaModel::GetDependentAttributes($sClass, $sAttCode); // List of attributes that are needed for the current one 1257 if (count($aDependents) > 0) 1258 { 1259 // When 'disabling' a field, all its dependent fields must be disabled too 1260 $sFieldList = "['{$sFormPrefix}".implode("','{$sFormPrefix}", $aDependents)."']"; 1261 $oP->add_ready_script("$('#enable_{$sFieldInputId}').bind('change', function(evt, sFormId) { return PropagateCheckBox( this.checked, $sFieldList, false); } );\n"); 1262 } 1263 $aArgs = array('this' => $oObj); 1264 $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $oObj->Get($sAttCode), $oObj->GetEditValue($sAttCode), $sFieldInputId, '', $iExpectCode, $aArgs); 1265 $sComments = '<input type="checkbox" checked id="enable_'.$sFieldInputId.'" onClick="ToggleField(this.checked, \''.$sFieldInputId.'\')"/>'; 1266 if (!isset($aValues[$sAttCode])) 1267 { 1268 $aValues[$sAttCode] = array(); 1269 } 1270 if (count($aValues[$sAttCode]) == 1) 1271 { 1272 $sComments .= '<div class="mono_value">1</div>'; 1273 } 1274 else 1275 { 1276 // Non-homogenous value 1277 $iMaxCount = 5; 1278 $sTip = "<p><b>".Dict::Format('UI:BulkModify_Count_DistinctValues', count($aValues[$sAttCode]))."</b><ul>"; 1279 $index = 0; 1280 foreach($aValues[$sAttCode] as $sCurrValue => $aVal) 1281 { 1282 $sDisplayValue = empty($aVal['display']) ? '<i>'.Dict::S('Enum:Undefined').'</i>' : str_replace(array("\n", "\r"), " ", $aVal['display']); 1283 $sTip .= "<li>".Dict::Format('UI:BulkModify:Value_Exists_N_Times', $sDisplayValue, $aVal['count'])."</li>"; 1284 $index++; 1285 if ($iMaxCount == $index) 1286 { 1287 $sTip .= "<li>".Dict::Format('UI:BulkModify:N_MoreValues', count($aValues[$sAttCode]) - $iMaxCount)."</li>"; 1288 break; 1289 } 1290 } 1291 $sTip .= "</ul></p>"; 1292 $sTip = addslashes($sTip); 1293 $sReadyScript .= "$('#multi_values_$sFieldInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );\n"; 1294 $sComments .= '<div class="multi_values" id="multi_values_'.$sFieldInputId.'">'.count($aValues[$sAttCode]).'</div>'; 1295 } 1296 $aDetails[] = array('label' => '<span>'.$oAttDef->GetLabel().'</span>', 'value' => "<span id=\"field_$sFieldInputId\">$sHTMLValue</span>", 'comments' => $sComments); 1297 $aFieldsMap[$sAttCode] = $sFieldInputId; 1298 $iFieldIndex++; 1299 } 1300 } 1301 $sButtonsPosition = MetaModel::GetConfig()->Get('buttons_position'); 1302 if ($sButtonsPosition == 'bottom') 1303 { 1304 // bottom: Displays the ticket details BEFORE the actions 1305 $oP->add('<div class="ui-widget-content">'); 1306 $oObj->DisplayBareProperties($oP); 1307 $oP->add('</div>'); 1308 } 1309 $oP->add("<div class=\"wizContainer\">\n"); 1310 $oP->add("<form id=\"{$sFormId}\" method=\"post\" onSubmit=\"return OnSubmit('{$sFormId}');\">\n"); 1311 $oP->add("<table><tr><td>\n"); 1312 $oP->details($aDetails); 1313 $oP->add("</td></tr></table>\n"); 1314 $oP->add("<input type=\"hidden\" name=\"class\" value=\"$sClass\">\n"); 1315 $oP->add("<input type=\"hidden\" name=\"operation\" value=\"bulk_apply_stimulus\">\n"); 1316 $oP->add("<input type=\"hidden\" name=\"preview_mode\" value=\"1\">\n"); 1317 $oP->add("<input type=\"hidden\" name=\"filter\" value=\"".utils::HtmlEntities($sFilter)."\">\n"); 1318 $oP->add("<input type=\"hidden\" name=\"stimulus\" value=\"$sStimulus\">\n"); 1319 $oP->add("<input type=\"hidden\" name=\"state\" value=\"$sState\">\n"); 1320 $oP->add("<input type=\"hidden\" name=\"transaction_id\" value=\"".utils::GetNewTransactionId()."\">\n"); 1321 $oP->add($oAppContext->GetForForm()); 1322 $oP->add("<input type=\"hidden\" name=\"selectObject\" value=\"".implode(',',$aSelectObject)."\">\n"); 1323 $sURL = "./UI.php?operation=search&filter=".urlencode($sFilter)."&".$oAppContext->GetForLink(); 1324 $oP->add("<input type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"window.location.href='$sURL'\"> \n"); 1325 $oP->add("<button type=\"submit\" class=\"action\"><span>$sActionLabel</span></button>\n"); 1326 $oP->add("</form>\n"); 1327 $oP->add("</div>\n"); 1328 if ($sButtonsPosition != 'bottom') 1329 { 1330 // top or both: Displays the ticket details AFTER the actions 1331 $oP->add('<div class="ui-widget-content">'); 1332 $oObj->DisplayBareProperties($oP); 1333 $oP->add('</div>'); 1334 } 1335 $iFieldsCount = count($aFieldsMap); 1336 $sJsonFieldsMap = json_encode($aFieldsMap); 1337 1338 $oP->add_script( 1339<<<EOF 1340 // Initializes the object once at the beginning of the page... 1341 var oWizardHelper = new WizardHelper('$sClass', '', '$sTargetState', '$sState', '$sStimulus'); 1342 oWizardHelper.SetFieldsMap($sJsonFieldsMap); 1343 oWizardHelper.SetFieldsCount($iFieldsCount); 1344EOF 1345); 1346 $oP->add_ready_script( 1347<<<EOF 1348 // Starts the validation when the page is ready 1349 CheckFields('{$sFormId}', false); 1350 $sReadyScript 1351EOF 1352); 1353 } 1354 break; 1355 1356 case 'bulk_apply_stimulus': 1357 $oP->DisableBreadCrumb(); 1358 $bPreviewMode = utils::ReadPostedParam('preview_mode', false); 1359 $sFilter = utils::ReadPostedParam('filter', '', 'raw_data'); 1360 $sStimulus = utils::ReadPostedParam('stimulus', ''); 1361 $sState = utils::ReadPostedParam('state', ''); 1362 $sSelectObject = utils::ReadPostedParam('selectObject', '', 'raw_data'); 1363 $aSelectObject = explode(',', $sSelectObject); 1364 1365 if (empty($sFilter) || empty($sStimulus) || empty($sState)) 1366 { 1367 throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'filter', 'stimulus', 'state')); 1368 } 1369 $sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id'); 1370 if (!utils::IsTransactionValid($sTransactionId)) 1371 { 1372 $sUser = UserRights::GetUser(); 1373 IssueLog::Error("UI.php '$operation' : invalid transaction_id ! data: user='$sUser'"); 1374 $oP->p(Dict::S('UI:Error:ObjectAlreadyUpdated')); 1375 } 1376 else 1377 { 1378 // For archiving the modification 1379 $oFilter = DBObjectSearch::unserialize($sFilter); 1380 // Add user filter 1381 $oFilter->UpdateContextFromUser(); 1382 $sClass = $oFilter->GetClass(); 1383 $aObjects = array(); 1384 foreach($aSelectObject as $iId) 1385 { 1386 $aObjects[] = MetaModel::GetObject($sClass, $iId); 1387 } 1388 1389 $aTransitions = MetaModel::EnumTransitions($sClass, $sState); 1390 $aStimuli = MetaModel::EnumStimuli($sClass); 1391 1392 $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); 1393 $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); 1394 1395 $oP->set_title(Dict::Format('UI:StimulusModify_N_ObjectsOf_Class', $sActionLabel, count($aObjects), $sClass)); 1396 $oP->add('<div class="page_header">'); 1397 $oP->add('<h1>'.MetaModel::GetClassIcon($sClass).' '.Dict::Format('UI:StimulusModify_N_ObjectsOf_Class', $sActionLabel, count($aObjects), $sClass).'</h1>'); 1398 $oP->add('</div>'); 1399 1400 $oSet = DBObjectSet::FromArray($sClass, $aObjects); 1401 1402 // For reporting 1403 $aHeaders = array( 1404 'object' => array('label' => MetaModel::GetName($sClass), 'description' => Dict::S('UI:ModifiedObject')), 1405 'status' => array('label' => Dict::S('UI:BulkModifyStatus'), 'description' => Dict::S('UI:BulkModifyStatus+')), 1406 'errors' => array('label' => Dict::S('UI:BulkModifyErrors'), 'description' => Dict::S('UI:BulkModifyErrors+')), 1407 ); 1408 $aRows = array(); 1409 while ($oObj = $oSet->Fetch()) 1410 { 1411 $sError = Dict::S('UI:BulkModifyStatusOk'); 1412 try 1413 { 1414 $aTransitions = $oObj->EnumTransitions(); 1415 $aStimuli = MetaModel::EnumStimuli($sClass); 1416 if (!isset($aTransitions[$sStimulus])) 1417 { 1418 throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, $oObj->GetName(), $oObj->GetStateLabel())); 1419 } 1420 else 1421 { 1422 $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); 1423 $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); 1424 $sTargetState = $aTransitions[$sStimulus]['target_state']; 1425 $aExpectedAttributes = $oObj->GetTransitionAttributes($sStimulus /* cureent state */); 1426 $aDetails = array(); 1427 $aErrors = array(); 1428 foreach($aExpectedAttributes as $sAttCode => $iExpectCode) 1429 { 1430 $iFlags = $oObj->GetTransitionFlags($sAttCode, $sStimulus); 1431 if (($iExpectCode & (OPT_ATT_MUSTCHANGE|OPT_ATT_MUSTPROMPT)) || ($oObj->Get($sAttCode) == '') ) 1432 { 1433 $paramValue = utils::ReadPostedParam("attr_$sAttCode", '', 'raw_data'); 1434 if ( ($iFlags & OPT_ATT_SLAVE) && ($paramValue != $oObj->Get($sAttCode)) ) 1435 { 1436 $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); 1437 $aErrors[] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $oAttDef->GetLabel()); 1438 unset($aExpectedAttributes[$sAttCode]); 1439 } 1440 } 1441 } 1442 1443 $oObj->UpdateObjectFromPostedForm('', array_keys($aExpectedAttributes), $aExpectedAttributes); 1444 1445 if (count($aErrors) == 0) 1446 { 1447 if ($oObj->ApplyStimulus($sStimulus)) 1448 { 1449 list($bResult, $aErrors) = $oObj->CheckToWrite(); 1450 $sStatus = $bResult ? Dict::S('UI:BulkModifyStatusModified') : Dict::S('UI:BulkModifyStatusSkipped'); 1451 if ($bResult) 1452 { 1453 $oObj->DBUpdate(); 1454 } 1455 else 1456 { 1457 $sError = '<p>'.implode('</p></p>',$aErrors)."</p>\n"; 1458 } 1459 } 1460 else 1461 { 1462 $sStatus = Dict::S('UI:BulkModifyStatusSkipped'); 1463 $sError = '<p>'.Dict::S('UI:FailedToApplyStimuli')."<p>\n"; 1464 } 1465 } 1466 else 1467 { 1468 $sStatus = Dict::S('UI:BulkModifyStatusSkipped'); 1469 $sError = '<p>'.implode('</p></p>',$aErrors)."</p>\n"; 1470 } 1471 } 1472 } 1473 catch(Exception $e) 1474 { 1475 $sError = $e->getMessage(); 1476 $sStatus = Dict::S('UI:BulkModifyStatusSkipped'); 1477 } 1478 $aRows[] = array( 1479 'object' => $oObj->GetHyperlink(), 1480 'status' => $sStatus, 1481 'errors' => $sError, 1482 ); 1483 } 1484 $oP->Table($aHeaders, $aRows); 1485 // Back to the list 1486 $sURL = "./UI.php?operation=search&filter=".urlencode($sFilter)."&".$oAppContext->GetForLink(); 1487 $oP->add('<input type="button" onClick="window.location.href=\''.$sURL.'\'" value="'.Dict::S('UI:Button:Done').'">'); 1488 } 1489 break; 1490 1491 case 'stimulus': // Form displayed when applying a stimulus (state change) 1492 $oP->DisableBreadCrumb(); 1493 $sClass = utils::ReadParam('class', '', false, 'class'); 1494 $id = utils::ReadParam('id', ''); 1495 $sStimulus = utils::ReadParam('stimulus', ''); 1496 if ( empty($sClass) || empty($id) || empty($sStimulus) ) // TO DO: check that the class name is valid ! 1497 { 1498 throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus')); 1499 } 1500 $aStimuli = MetaModel::EnumStimuli($sClass); 1501 if ((get_class($aStimuli[$sStimulus]) !== 'StimulusUserAction') || (UserRights::IsStimulusAllowed($sClass, $sStimulus) === UR_ALLOWED_NO)) 1502 { 1503 $sUser = UserRights::GetUser(); 1504 IssueLog::Error("UI.php '$operation' : Stimulus '$sStimulus' not allowed ! data: user='$sUser', class='$sClass'"); 1505 throw new ApplicationException(Dict::S('UI:Error:ActionNotAllowed')); 1506 } 1507 1508 $oObj = MetaModel::GetObject($sClass, $id, false); 1509 if ($oObj != null) 1510 { 1511 $aPrefillFormParam = array( 'user' => $_SESSION["auth_user"], 1512 'context' => $oAppContext->GetAsHash(), 1513 'stimulus' => $sStimulus, 1514 'origin' => 'console' 1515 ); 1516 $oObj->DisplayStimulusForm($oP, $sStimulus, $aPrefillFormParam); 1517 } 1518 else 1519 { 1520 $oP->set_title(Dict::S('UI:ErrorPageTitle')); 1521 $oP->P(Dict::S('UI:ObjectDoesNotExist')); 1522 } 1523 break; 1524 1525 /////////////////////////////////////////////////////////////////////////////////////////// 1526 1527 case 'apply_stimulus': // Actual state change 1528 $oP->DisableBreadCrumb(); 1529 $sClass = utils::ReadPostedParam('class', ''); 1530 $id = utils::ReadPostedParam('id', ''); 1531 $sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id'); 1532 $sStimulus = utils::ReadPostedParam('stimulus', ''); 1533 if ( empty($sClass) || empty($id) || empty($sStimulus) ) // TO DO: check that the class name is valid ! 1534 { 1535 throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus')); 1536 } 1537 $oObj = MetaModel::GetObject($sClass, $id, false); 1538 if ($oObj != null) 1539 { 1540 $aTransitions = $oObj->EnumTransitions(); 1541 $aStimuli = MetaModel::EnumStimuli($sClass); 1542 $sMessage = ''; 1543 $sSeverity = 'ok'; 1544 $bDisplayDetails = true; 1545 if (!isset($aTransitions[$sStimulus])) 1546 { 1547 throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, $oObj->GetName(), $oObj->GetStateLabel())); 1548 } 1549 if (!utils::IsTransactionValid($sTransactionId)) 1550 { 1551 $sUser = UserRights::GetUser(); 1552 IssueLog::Error("UI.php '$operation' : invalid transaction_id ! data: user='$sUser', class='$sClass'"); 1553 $sMessage = Dict::S('UI:Error:ObjectAlreadyUpdated'); 1554 $sSeverity = 'info'; 1555 } 1556 elseif ((get_class($aStimuli[$sStimulus]) !== 'StimulusUserAction') || (UserRights::IsStimulusAllowed($sClass, $sStimulus) === UR_ALLOWED_NO)) 1557 { 1558 $sUser = UserRights::GetUser(); 1559 IssueLog::Error("UI.php '$operation' : Stimulus '$sStimulus' not allowed ! data: user='$sUser', class='$sClass'"); 1560 $sMessage = Dict::S('UI:Error:ActionNotAllowed'); 1561 $sSeverity = 'error'; 1562 } 1563 else 1564 { 1565 $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); 1566 $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); 1567 $sTargetState = $aTransitions[$sStimulus]['target_state']; 1568 $aExpectedAttributes = $oObj->GetTransitionAttributes($sStimulus /*, current state*/); 1569 $aDetails = array(); 1570 $aErrors = array(); 1571 foreach($aExpectedAttributes as $sAttCode => $iExpectCode) 1572 { 1573 $iFlags = $oObj->GetTransitionFlags($sAttCode, $sStimulus); 1574 if (($iExpectCode & (OPT_ATT_MUSTCHANGE|OPT_ATT_MUSTPROMPT)) || ($oObj->Get($sAttCode) == '') ) 1575 { 1576 $paramValue = utils::ReadPostedParam("attr_$sAttCode", '', 'raw_data'); 1577 if ( ($iFlags & OPT_ATT_SLAVE) && ($paramValue != $oObj->Get($sAttCode))) 1578 { 1579 $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); 1580 $aErrors[] = Dict::Format('UI:AttemptingToChangeASlaveAttribute_Name', $oAttDef->GetLabel()); 1581 unset($aExpectedAttributes[$sAttCode]); 1582 } 1583 } 1584 } 1585 1586 $oObj->UpdateObjectFromPostedForm('', array_keys($aExpectedAttributes), $aExpectedAttributes); 1587 1588 if (count($aErrors) == 0) 1589 { 1590 $sIssues = ''; 1591 $bApplyStimulus = true; 1592 list($bRes, $aIssues) = $oObj->CheckToWrite(); // Check before trying to write the object 1593 if ($bRes) 1594 { 1595 try 1596 { 1597 $bApplyStimulus = $oObj->ApplyStimulus($sStimulus); // will write the object in the DB 1598 } 1599 catch(CoreException $e) 1600 { 1601 // Rollback to the previous state... by reloading the object from the database and applying the modifications again 1602 $oObj = MetaModel::GetObject(get_class($oObj), $oObj->GetKey()); 1603 $oObj->UpdateObjectFromPostedForm('', array_keys($aExpectedAttributes), $aExpectedAttributes); 1604 $sIssues = $e->getMessage(); 1605 } 1606 } 1607 else 1608 { 1609 $sIssues = implode(' ', $aIssues); 1610 } 1611 1612 if (!$bApplyStimulus) 1613 { 1614 $sMessage = Dict::S('UI:FailedToApplyStimuli'); 1615 $sSeverity = 'error'; 1616 1617 $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data'); 1618 if ($sOwnershipToken !== null) 1619 { 1620 // Release the concurrent lock, if any 1621 iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken); 1622 } 1623 } 1624 else if ($sIssues != '') 1625 { 1626 $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data'); 1627 if ($sOwnershipToken !== null) 1628 { 1629 // Release the concurrent lock, if any, a new lock will be re-acquired by DisplayStimulusForm below 1630 iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken); 1631 } 1632 1633 $bDisplayDetails = false; 1634 // Found issues, explain and give the user a second chance 1635 // 1636 $oObj->DisplayStimulusForm($oP, $sStimulus); 1637 $sIssueDesc = Dict::Format('UI:ObjectCouldNotBeWritten',$sIssues); 1638 $oP->add_ready_script("alert('".addslashes($sIssueDesc)."');"); 1639 } 1640 else 1641 { 1642 $sMessage = Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); 1643 $sSeverity = 'ok'; 1644 utils::RemoveTransaction($sTransactionId); 1645 $bLockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled'); 1646 if ($bLockEnabled) 1647 { 1648 // Release the concurrent lock, if any 1649 $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data'); 1650 if ($sOwnershipToken !== null) 1651 { 1652 // We're done, let's release the lock 1653 iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken); 1654 } 1655 } 1656 } 1657 } 1658 else 1659 { 1660 $sMessage = implode('</p><p>', $aErrors); 1661 $sSeverity = 'error'; 1662 } 1663 } 1664 if ($bDisplayDetails) 1665 { 1666 ReloadAndDisplay($oP, $oObj, 'apply_stimulus', $sMessage, $sSeverity); 1667 } 1668 } 1669 else 1670 { 1671 $oP->set_title(Dict::S('UI:ErrorPageTitle')); 1672 $oP->P(Dict::S('UI:ObjectDoesNotExist')); 1673 } 1674 break; 1675 1676 /////////////////////////////////////////////////////////////////////////////////////////// 1677 1678 case 'swf_navigator': // Graphical display of the relations "impact" / "depends on" 1679 require_once(APPROOT.'core/simplegraph.class.inc.php'); 1680 require_once(APPROOT.'core/relationgraph.class.inc.php'); 1681 require_once(APPROOT.'core/displayablegraph.class.inc.php'); 1682 $sClass = utils::ReadParam('class', '', false, 'class'); 1683 $id = utils::ReadParam('id', 0); 1684 $sRelation = utils::ReadParam('relation', 'impact'); 1685 $sDirection = utils::ReadParam('direction', 'down'); 1686 $iGroupingThreshold = utils::ReadParam('g', 5); 1687 1688 $bDirDown = ($sDirection === 'down'); 1689 $oObj = MetaModel::GetObject($sClass, $id); 1690 $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth'); 1691 $aSourceObjects = array($oObj); 1692 1693 $oP->set_title(MetaModel::GetRelationDescription($sRelation, $bDirDown).' '.$oObj->GetName()); 1694 1695 $sPageId = "ui-relation-graph-".$sClass.'::'.$id; 1696 $sLabel = $oObj->GetName().' '.MetaModel::GetRelationLabel($sRelation, $bDirDown); 1697 $sDescription = MetaModel::GetRelationDescription($sRelation, $bDirDown).' '.$oObj->GetName(); 1698 $oP->SetBreadCrumbEntry($sPageId, $sLabel, $sDescription); 1699 1700 if ($sRelation == 'depends on') 1701 { 1702 $sRelation = 'impacts'; 1703 $sDirection = 'up'; 1704 } 1705 if ($sDirection == 'up') 1706 { 1707 $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth); 1708 } 1709 else 1710 { 1711 $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth); 1712 } 1713 1714 1715 $aResults = $oRelGraph->GetObjectsByClass(); 1716 $oDisplayGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down')); 1717 1718 $oP->AddTabContainer('Navigator'); 1719 $oP->SetCurrentTabContainer('Navigator'); 1720 1721 $sFirstTab = MetaModel::GetConfig()->Get('impact_analysis_first_tab'); 1722 $sContextKey = "itop-config-mgmt/relation_context/$sClass/$sRelation/$sDirection"; 1723 1724 // Check if the current object supports Attachments, similar to AttachmentPlugin::IsTargetObject 1725 $sClassForAttachment = null; 1726 $iIdForAttachment = null; 1727 if (class_exists('Attachment')) 1728 { 1729 $aAllowedClasses = MetaModel::GetModuleSetting('itop-attachments', 'allowed_classes', array('Ticket')); 1730 foreach($aAllowedClasses as $sAllowedClass) 1731 { 1732 if ($oObj instanceof $sAllowedClass) 1733 { 1734 $iIdForAttachment = $id; 1735 $sClassForAttachment = $sClass; 1736 } 1737 } 1738 } 1739 1740 // Display the tabs 1741 if ($sFirstTab == 'list') 1742 { 1743 DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj); 1744 $oP->SetCurrentTab(Dict::S('UI:RelationshipGraph')); 1745 $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, array(), $sClassForAttachment, $iIdForAttachment, $sContextKey, array('this' => $oObj)); 1746 DisplayNavigatorGroupTab($oP); 1747 } 1748 else 1749 { 1750 $oP->SetCurrentTab(Dict::S('UI:RelationshipGraph')); 1751 $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, array(), $sClassForAttachment, $iIdForAttachment, $sContextKey, array('this' => $oObj)); 1752 DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj); 1753 DisplayNavigatorGroupTab($oP); 1754 } 1755 1756 $oP->SetCurrentTab(''); 1757 break; 1758 1759 /////////////////////////////////////////////////////////////////////////////////////////// 1760 1761 case 'kill_lock': 1762 $oP->DisableBreadCrumb(); 1763 $sClass = utils::ReadParam('class', ''); 1764 $id = utils::ReadParam('id', ''); 1765 iTopOwnershipLock::KillLock($sClass, $id); 1766 $oObj = MetaModel::GetObject($sClass, $id); 1767 ReloadAndDisplay($oP, $oObj, 'concurrent_lock_killed', Dict::S('UI:ConcurrentLockKilled'), 'info'); 1768 break; 1769 1770 /////////////////////////////////////////////////////////////////////////////////////////// 1771 1772 case 'cancel': // An action was cancelled 1773 $oP->DisableBreadCrumb(); 1774 $oP->set_title(Dict::S('UI:OperationCancelled')); 1775 $oP->add('<h1>'.Dict::S('UI:OperationCancelled').'</h1>'); 1776 break; 1777 1778 /////////////////////////////////////////////////////////////////////////////////////////// 1779 1780 default: // Menu node rendering (templates) 1781 ApplicationMenu::LoadAdditionalMenus(); 1782 $oMenuNode = ApplicationMenu::GetMenuNode(ApplicationMenu::GetMenuIndexById(ApplicationMenu::GetActiveNodeId())); 1783 if (is_object($oMenuNode)) 1784 { 1785 $oMenuNode->RenderContent($oP, $oAppContext->GetAsHash()); 1786 $oP->set_title($oMenuNode->GetLabel()); 1787 } 1788 1789 /////////////////////////////////////////////////////////////////////////////////////////// 1790 1791 } 1792 DisplayWelcomePopup($oP); 1793 $oP->output(); 1794} 1795catch(CoreException $e) 1796{ 1797 require_once(APPROOT.'/setup/setuppage.class.inc.php'); 1798 $oP = new SetupPage(Dict::S('UI:PageTitle:FatalError')); 1799 if ($e instanceof SecurityException) 1800 { 1801 $oP->add("<h1>".Dict::S('UI:SystemIntrusion')."</h1>\n"); 1802 } 1803 else 1804 { 1805 $oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n"); 1806 } 1807 $oP->error(Dict::Format('UI:Error_Details', $e->getHtmlDesc())); 1808 $oP->output(); 1809 1810 if (MetaModel::IsLogEnabledIssue()) 1811 { 1812 if (MetaModel::IsValidClass('EventIssue')) 1813 { 1814 try 1815 { 1816 $oLog = new EventIssue(); 1817 1818 $oLog->Set('message', $e->getMessage()); 1819 $oLog->Set('userinfo', ''); 1820 $oLog->Set('issue', $e->GetIssue()); 1821 $oLog->Set('impact', 'Page could not be displayed'); 1822 $oLog->Set('callstack', $e->getTrace()); 1823 $oLog->Set('data', $e->getContextData()); 1824 $oLog->DBInsertNoReload(); 1825 } 1826 catch(Exception $e) 1827 { 1828 IssueLog::Error("Failed to log issue into the DB"); 1829 } 1830 } 1831 1832 IssueLog::Error($e->getMessage()); 1833 } 1834 1835 // For debugging only 1836 //throw $e; 1837} 1838catch(Exception $e) 1839{ 1840 require_once(APPROOT.'/setup/setuppage.class.inc.php'); 1841 $oP = new SetupPage(Dict::S('UI:PageTitle:FatalError')); 1842 $oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n"); 1843 $oP->error(Dict::Format('UI:Error_Details', $e->getMessage())); 1844 $oP->output(); 1845 1846 if (MetaModel::IsLogEnabledIssue()) 1847 { 1848 if (MetaModel::IsValidClass('EventIssue')) 1849 { 1850 try 1851 { 1852 $oLog = new EventIssue(); 1853 1854 $oLog->Set('message', $e->getMessage()); 1855 $oLog->Set('userinfo', ''); 1856 $oLog->Set('issue', 'PHP Exception'); 1857 $oLog->Set('impact', 'Page could not be displayed'); 1858 $oLog->Set('callstack', $e->getTrace()); 1859 $oLog->Set('data', array()); 1860 $oLog->DBInsertNoReload(); 1861 } 1862 catch(Exception $e) 1863 { 1864 IssueLog::Error("Failed to log issue into the DB"); 1865 } 1866 } 1867 1868 IssueLog::Error($e->getMessage()); 1869 } 1870}