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\">&nbsp;".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()\">&nbsp;&nbsp;<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)."&nbsp;<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('&nbsp;'); // 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">&nbsp;<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;\">&nbsp;</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)."&nbsp;".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)."&nbsp;".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("&nbsp; <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)."&nbsp;".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).'&nbsp;'.$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).'&nbsp;'.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'\">&nbsp;&nbsp;&nbsp;&nbsp;\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).'&nbsp;'.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}