1<?php
2// Copyright (C) 2010-2017 Combodo SARL
3//
4//   This file is part of iTop.
5//
6//   iTop is free software; you can redistribute it and/or modify
7//   it under the terms of the GNU Affero General Public License as published by
8//   the Free Software Foundation, either version 3 of the License, or
9//   (at your option) any later version.
10//
11//   iTop is distributed in the hope that it will be useful,
12//   but WITHOUT ANY WARRANTY; without even the implied warranty of
13//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14//   GNU Affero General Public License for more details.
15//
16//   You should have received a copy of the GNU Affero General Public License
17//   along with iTop. If not, see <http://www.gnu.org/licenses/>
18
19
20/**
21 * Handles various ajax requests
22 *
23 * @copyright   Copyright (C) 2010-2017 Combodo SARL
24 * @license     http://opensource.org/licenses/AGPL-3.0
25 */
26
27require_once('../approot.inc.php');
28require_once(APPROOT.'application/application.inc.php');
29require_once(APPROOT.'application/webpage.class.inc.php');
30require_once(APPROOT.'application/ajaxwebpage.class.inc.php');
31require_once(APPROOT.'application/pdfpage.class.inc.php');
32require_once(APPROOT.'application/wizardhelper.class.inc.php');
33require_once(APPROOT.'application/ui.linkswidget.class.inc.php');
34require_once(APPROOT.'application/ui.searchformforeignkeys.class.inc.php');
35require_once(APPROOT.'application/ui.extkeywidget.class.inc.php');
36require_once(APPROOT.'application/datatable.class.inc.php');
37require_once(APPROOT.'application/excelexporter.class.inc.php');
38
39
40function LogErrorMessage($sMsgPrefix, $aContextInfo) {
41	$sCurrentUserLogin = UserRights::GetUser();
42	$sContextInfo = urldecode(http_build_query($aContextInfo, '', ', '));
43	$sErrorMessage = "$sMsgPrefix - User='$sCurrentUserLogin', $sContextInfo";
44	IssueLog::Error($sErrorMessage);
45}
46
47
48try
49{
50	require_once(APPROOT.'/application/startup.inc.php');
51	require_once(APPROOT.'/application/user.preferences.class.inc.php');
52
53	require_once(APPROOT.'/application/loginwebpage.class.inc.php');
54	LoginWebPage::DoLoginEx(null /* any portal */, false);
55
56	$oPage = new ajax_page("");
57	$oPage->no_cache();
58
59
60	$operation = utils::ReadParam('operation', '');
61	$sFilter = utils::ReadParam('filter', '', false, 'raw_data');
62	$sEncoding = utils::ReadParam('encoding', 'serialize');
63	$sClass = utils::ReadParam('class', 'MissingAjaxParam', false, 'class');
64	$sStyle = utils::ReadParam('style', 'list');
65
66	switch ($operation)
67	{
68		case 'datatable':
69		case 'pagination':
70			$oPage->SetContentType('text/html');
71			$extraParams = utils::ReadParam('extra_param', '', false, 'raw_data');
72			$aExtraParams = array();
73			if (is_array($extraParams))
74			{
75				$aExtraParams = $extraParams;
76			}
77			else
78			{
79				$sExtraParams = stripslashes($extraParams);
80				if (!empty($sExtraParams))
81				{
82					$val = json_decode(str_replace("'", '"', $sExtraParams), true /* associative array */);
83					if ($val !== null)
84					{
85						$aExtraParams = $val;
86					}
87				}
88			}
89			if ($sEncoding == 'oql')
90			{
91				$oFilter = DBSearch::FromOQL($sFilter);
92			}
93			else
94			{
95				$oFilter = DBSearch::unserialize($sFilter);
96			}
97			$iStart = utils::ReadParam('start', 0);
98			$iEnd = utils::ReadParam('end', 1);
99			$iSortCol = utils::ReadParam('sort_col', 'null');
100			$sSelectMode = utils::ReadParam('select_mode', '');
101			if (!empty($sSelectMode) && ($sSelectMode != 'none'))
102			{
103				// The first column is used for the selection (radio / checkbox) and is not sortable
104				$iSortCol--;
105			}
106			$bDisplayKey = utils::ReadParam('display_key', 'true') == 'true';
107			$aColumns = utils::ReadParam('columns', array(), false, 'raw_data');
108			$aClassAliases = utils::ReadParam('class_aliases', array());
109			$iListId = utils::ReadParam('list_id', 0);
110
111			// Filter the list to removed linked set since we are not able to display them here
112			$aOrderBy = array();
113			$iSortIndex = 0;
114
115			$aColumnsLoad = array();
116			foreach($aClassAliases as $sAlias => $sClassName)
117			{
118				$aColumnsLoad[$sAlias] = array();
119				foreach($aColumns[$sAlias] as $sAttCode => $aData)
120				{
121					if ($aData['checked'] == 'true')
122					{
123						$aColumns[$sAlias][$sAttCode]['checked'] = true;
124						if ($sAttCode == '_key_')
125						{
126							if ($iSortCol == $iSortIndex)
127							{
128								if (!MetaModel::HasChildrenClasses($oFilter->GetClass()))
129								{
130									$aNameSpec = MetaModel::GetNameSpec($oFilter->GetClass());
131									if ($aNameSpec[0] == '%1$s')
132									{
133										// The name is made of a single column, let's sort according to the sort algorithm for this column
134										$aOrderBy[$sAlias.'.'.$aNameSpec[1][0]] = (utils::ReadParam('sort_order', 'asc') == 'asc');
135									}
136									else
137									{
138										$aOrderBy[$sAlias.'.'.'friendlyname'] = (utils::ReadParam('sort_order', 'asc') == 'asc');
139									}
140								}
141								else
142								{
143									$aOrderBy[$sAlias.'.'.'friendlyname'] = (utils::ReadParam('sort_order', 'asc') == 'asc');
144								}
145							}
146						}
147						else
148						{
149							$oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode);
150							if ($oAttDef instanceof AttributeLinkedSet)
151							{
152								// Removed from the display list
153								unset($aColumns[$sAlias][$sAttCode]);
154							}
155							else
156							{
157								$aColumnsLoad[$sAlias][] = $sAttCode;
158							}
159							if ($iSortCol == $iSortIndex)
160							{
161								if ($oAttDef->IsExternalKey())
162								{
163									$sSortCol = $sAttCode.'_friendlyname';
164								}
165								else
166								{
167									$sSortCol = $sAttCode;
168								}
169								$aOrderBy[$sAlias.'.'.$sSortCol] = (utils::ReadParam('sort_order', 'asc') == 'asc');
170							}
171						}
172						$iSortIndex++;
173					}
174					else
175					{
176						$aColumns[$sAlias][$sAttCode]['checked'] = false;
177					}
178				}
179
180			}
181
182			// Load only the requested columns
183			$oSet = new DBObjectSet($oFilter, $aOrderBy, $aExtraParams, null, $iEnd - $iStart, $iStart);
184			$oSet->OptimizeColumnLoad($aColumnsLoad);
185
186			if (isset($aExtraParams['show_obsolete_data']))
187			{
188				$bShowObsoleteData = $aExtraParams['show_obsolete_data'];
189			}
190			else
191			{
192				$bShowObsoleteData = utils::ShowObsoleteData();
193			}
194			$oSet->SetShowObsoleteData($bShowObsoleteData);
195
196			$oDataTable = new DataTable($iListId, $oSet, $oSet->GetSelectedClasses());
197			if ($operation == 'datatable')
198			{
199				// Redraw the whole table
200				$sHtml = $oDataTable->UpdatePager($oPage, $iEnd - $iStart, $iStart); // Set the default page size
201				$sHtml .= $oDataTable->GetHTMLTable($oPage, $aColumns, $sSelectMode, $iEnd - $iStart, $bDisplayKey, $aExtraParams);
202			}
203			else
204			{
205				// redraw just the needed rows
206				$sHtml = $oDataTable->GetAsHTMLTableRows($oPage, $iEnd - $iStart, $aColumns, $sSelectMode, $bDisplayKey, $aExtraParams);
207			}
208			$oPage->add($sHtml);
209			break;
210
211		case 'datatable_save_settings':
212			$oPage->SetContentType('text/plain');
213			$iPageSize = utils::ReadParam('page_size', 10);
214			$sTableId = utils::ReadParam('table_id', null, false, 'raw_data');
215			$bSaveAsDefaults = (utils::ReadParam('defaults', 'true') == 'true');
216			$aClassAliases = utils::ReadParam('class_aliases', array(), false, 'raw_data');
217			$aColumns = utils::ReadParam('columns', array(), false, 'raw_data');
218
219			foreach($aColumns as $sAlias => $aList)
220			{
221				foreach($aList as $sAttCode => $aData)
222				{
223					$aColumns[$sAlias][$sAttCode]['checked'] = ($aData['checked'] == 'true');
224					$aColumns[$sAlias][$sAttCode]['disabled'] = ($aData['disabled'] == 'true');
225					$aColumns[$sAlias][$sAttCode]['sort'] = ($aData['sort']);
226				}
227			}
228
229			$oSettings = new DataTableSettings($aClassAliases, $sTableId);
230			$oSettings->iDefaultPageSize = $iPageSize;
231			$oSettings->aColumns = $aColumns;
232
233			if ($bSaveAsDefaults)
234			{
235				if ($sTableId != null)
236				{
237					$oCurrSettings = DataTableSettings::GetTableSettings($aClassAliases, $sTableId, true /* bOnlyTable */);
238					if ($oCurrSettings)
239					{
240						$oCurrSettings->ResetToDefault(false); // Reset this table to the defaults
241					}
242				}
243				$bRet = $oSettings->SaveAsDefault();
244			}
245			else
246			{
247				$bRet = $oSettings->Save();
248			}
249			$oPage->add($bRet ? 'Ok' : 'KO');
250			break;
251
252		case 'datatable_reset_settings':
253			$oPage->SetContentType('text/plain');
254			$sTableId = utils::ReadParam('table_id', null, false, 'raw_data');
255			$aClassAliases = utils::ReadParam('class_aliases', array(), false, 'raw_data');
256			$bResetAll = (utils::ReadParam('defaults', 'true') == 'true');
257
258			$oSettings = new DataTableSettings($aClassAliases, $sTableId);
259			$bRet = $oSettings->ResetToDefault($bResetAll);
260			$oPage->add($bRet ? 'Ok' : 'KO');
261			break;
262
263		// ui.searchformforeignkeys
264		case 'ShowModalSearchForeignKeys':
265			$oPage->SetContentType('text/html');
266			$iInputId = utils::ReadParam('iInputId', '');
267			$sTitle = utils::ReadParam('sTitle', '', false, 'raw_data');
268			$sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class');
269			$oWidget = new UISearchFormForeignKeys($sTargetClass, $iInputId);
270			$oWidget->ShowModalSearchForeignKeys($oPage, $sTitle);
271			break;
272
273		// ui.searchformforeignkeys
274		case 'GetFullListForeignKeysFromSelection':
275			$oPage->SetContentType('application/json');
276			$oWidget = new UISearchFormForeignKeys($sClass);
277			$oFullSetFilter = new DBObjectSearch($sClass);
278			$oWidget->GetFullListForeignKeysFromSelection($oPage, $oFullSetFilter);
279			break;
280
281		// ui.searchformforeignkeys
282		case 'ListResultsSearchForeignKeys':
283			$oPage->SetContentType('text/html');
284			$sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class');
285			$iInputId = utils::ReadParam('iInputId', '');
286			$sRemoteClass = utils::ReadParam('sRemoteClass', '', false, 'class');
287			$oWidget = new UISearchFormForeignKeys($sTargetClass, $iInputId);
288			$oWidget->ListResultsSearchForeignKeys($oPage, $sRemoteClass);
289			break;
290
291
292		// ui.linkswidget
293		case 'addObjects':
294			$oPage->SetContentType('text/html');
295			$sAttCode = utils::ReadParam('sAttCode', '');
296			$iInputId = utils::ReadParam('iInputId', '');
297			$sSuffix = utils::ReadParam('sSuffix', '');
298			$bDuplicates = (utils::ReadParam('bDuplicates', 'false') == 'false') ? false : true;
299			$sJson = utils::ReadParam('json', '', false, 'raw_data');
300			if (!empty($sJson))
301			{
302				$oWizardHelper = WizardHelper::FromJSON($sJson);
303				$oObj = $oWizardHelper->GetTargetObject();
304			}
305			else
306			{
307				// Search form: no current object
308				$oObj = null;
309			}
310			$oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix, $bDuplicates);
311			$oAppContext = new ApplicationContext();
312			$aPrefillFormParam = array( 'user' => $_SESSION["auth_user"],
313				'context' => $oAppContext->GetAsHash(),
314				'att_code' => $sAttCode,
315				'origin' => 'console',
316				'source_obj' => $oObj
317			);
318			$aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array());
319			$oWidget->GetObjectPickerDialog($oPage, $oObj, $sJson, $aAlreadyLinked, $aPrefillFormParam);
320			break;
321
322		// ui.linkswidget
323		case 'searchObjectsToAdd':
324			$oPage->SetContentType('text/html');
325			$sRemoteClass = utils::ReadParam('sRemoteClass', '', false, 'class');
326			$sAttCode = utils::ReadParam('sAttCode', '');
327			$iInputId = utils::ReadParam('iInputId', '');
328			$sSuffix = utils::ReadParam('sSuffix', '');
329			$bDuplicates = (utils::ReadParam('bDuplicates', 'false') == 'false') ? false : true;
330			$aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array());
331			$oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix, $bDuplicates);
332			$oWidget->SearchObjectsToAdd($oPage, $sRemoteClass, $aAlreadyLinked);
333			break;
334
335		//ui.linksdirectwidget
336		case 'createObject':
337			$oPage->SetContentType('text/html');
338			$sClass = utils::ReadParam('class', '', false, 'class');
339			$sRealClass = utils::ReadParam('real_class', '', false, 'class');
340			$sAttCode = utils::ReadParam('att_code', '');
341			$iInputId = utils::ReadParam('iInputId', '');
342			$oPage->SetContentType('text/html');
343			$sJson = utils::ReadParam('json', '', false, 'raw_data');
344			if (!empty($sJson))
345			{
346				$oWizardHelper = WizardHelper::FromJSON($sJson);
347				$oObj = $oWizardHelper->GetTargetObject();
348			}
349			$oObj =	$oWizardHelper->GetTargetObject();
350			$oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId);
351			$oWidget->GetObjectCreationDlg($oPage, $sRealClass, $oObj);
352		break;
353
354		// ui.linksdirectwidget
355		case 'getLinksetRow':
356			$oPage->SetContentType('text/html');
357			$sClass = utils::ReadParam('class', '', false, 'class');
358			$sRealClass = utils::ReadParam('real_class', '', false, 'class');
359			$sAttCode = utils::ReadParam('att_code', '');
360			$iInputId = utils::ReadParam('iInputId', '');
361			$iTempId = utils::ReadParam('tempId', '');
362			$aValues = utils::ReadParam('values', array(), false, 'raw_data');
363			$oPage->SetContentType('text/html');
364			$oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId);
365			$oPage->add($oWidget->GetRow($oPage, $sRealClass, $aValues, -$iTempId));
366			break;
367
368		// ui.linksdirectwidget
369		case 'selectObjectsToAdd':
370			$oPage->SetContentType('text/html');
371			$sClass = utils::ReadParam('class', '', false, 'class');
372			$aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array());
373			$sJson = utils::ReadParam('json', '', false, 'raw_data');
374			$oObj = null;
375			if ($sJson != '')
376			{
377				$oWizardHelper = WizardHelper::FromJSON($sJson);
378				$oObj = $oWizardHelper->GetTargetObject();
379			}
380			$sRealClass = utils::ReadParam('real_class', '', false, 'class');
381			$sAttCode = utils::ReadParam('att_code', '');
382			$iInputId = utils::ReadParam('iInputId', '');
383			$iCurrObjectId = utils::ReadParam('iObjId', 0);
384			$oPage->SetContentType('text/html');
385			$oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId);
386			$oWidget->GetObjectsSelectionDlg($oPage, $oObj, $aAlreadyLinked);
387			break;
388
389		// ui.linksdirectwidget
390		case 'searchObjectsToAdd2':
391			$oPage->SetContentType('text/html');
392			$sClass = utils::ReadParam('class', '', false, 'class');
393			$sRealClass = utils::ReadParam('real_class', '', false, 'class');
394			$sAttCode = utils::ReadParam('att_code', '');
395			$iInputId = utils::ReadParam('iInputId', '');
396			$aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array());
397			$sJson = utils::ReadParam('json', '', false, 'raw_data');
398			$oObj = null;
399			if ($sJson != '')
400			{
401				$oWizardHelper = WizardHelper::FromJSON($sJson);
402				$oObj = $oWizardHelper->GetTargetObject();
403			}
404			$oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId);
405			$oWidget->SearchObjectsToAdd($oPage, $sRealClass, $aAlreadyLinked, $oObj);
406			break;
407
408		// ui.linksdirectwidget
409		case 'doAddObjects2':
410			$oPage->SetContentType('text/html');
411			$oPage->SetContentType('text/html');
412			$sClass = utils::ReadParam('class', '', false, 'class');
413			$sRealClass = utils::ReadParam('real_class', '', false, 'class');
414			$sAttCode = utils::ReadParam('att_code', '');
415			$iInputId = utils::ReadParam('iInputId', '');
416			$iCurrObjectId = utils::ReadParam('iObjId', 0);
417			$sFilter = utils::ReadParam('filter', '', false, 'raw_data');
418			if ($sFilter != '')
419			{
420				$oFullSetFilter = DBObjectSearch::unserialize($sFilter);
421			}
422			else
423			{
424				$oLinksetDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
425				$valuesDef = $oLinksetDef->GetValuesDef();
426				if ($valuesDef === null)
427				{
428					$oFullSetFilter = new DBObjectSearch($oLinksetDef->GetLinkedClass());
429				}
430				else
431				{
432					if (!$valuesDef instanceof ValueSetObjects)
433					{
434						throw new Exception('Error: only ValueSetObjects are supported for "allowed_values" in AttributeLinkedSet ('.$this->sClass.'/'.$this->sAttCode.').');
435					}
436					$oFullSetFilter = DBObjectSearch::FromOQL($valuesDef->GetFilterExpression());
437				}
438			}
439			$oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId);
440			$oWidget->DoAddObjects($oPage, $oFullSetFilter);
441			break;
442
443		////////////////////////////////////////////////////////////
444
445		// ui.extkeywidget
446		case 'searchObjectsToSelect':
447			$oPage->SetContentType('text/html');
448			$sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class');
449			$iInputId = utils::ReadParam('iInputId', '');
450			$sRemoteClass = utils::ReadParam('sRemoteClass', '', false, 'class');
451			$sFilter = utils::ReadParam('sFilter', '', false, 'raw_data');
452			$sJson = utils::ReadParam('json', '', false, 'raw_data');
453			$sAttCode = utils::ReadParam('sAttCode', '');
454			$bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true');
455			if (!empty($sJson))
456			{
457				$oWizardHelper = WizardHelper::FromJSON($sJson);
458				$oObj = $oWizardHelper->GetTargetObject();
459			}
460			else
461			{
462				// Search form: no current object
463				$oObj = null;
464			}
465			$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, $bSearchMode);
466			$oWidget->SearchObjectsToSelect($oPage, $sFilter, $sRemoteClass, $oObj);
467			break;
468
469		// ui.extkeywidget: autocomplete
470		case 'ac_extkey':
471			$oPage->SetContentType('text/plain');
472			$sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class');
473			$iInputId = utils::ReadParam('iInputId', '');
474			$sFilter = utils::ReadParam('sFilter', '', false, 'raw_data');
475			$sJson = utils::ReadParam('json', '', false, 'raw_data');
476			$sContains = utils::ReadParam('q', '', false, 'raw_data');
477			$bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true');
478			$sOutputFormat = utils::ReadParam('sOutputFormat', UIExtKeyWidget::ENUM_OUTPUT_FORMAT_CSV, false, 'raw_data');
479            $sAutocompleteOperation = utils::ReadParam('sAutocompleteOperation', null, false, 'raw_data');
480			if ($sContains != '')
481			{
482				if (!empty($sJson))
483				{
484					$oWizardHelper = WizardHelper::FromJSON($sJson);
485					$oObj = $oWizardHelper->GetTargetObject();
486				}
487				else
488				{
489					// Search form: no current object
490					$oObj = null;
491				}
492				$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, '', $bSearchMode);
493				$oWidget->AutoComplete($oPage, $sFilter, $oObj, $sContains, $sOutputFormat, $sAutocompleteOperation);
494			}
495			break;
496
497		// ui.extkeywidget
498		case 'objectSearchForm':
499			$oPage->SetContentType('text/html');
500			$sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class');
501			$iInputId = utils::ReadParam('iInputId', '');
502			$sTitle = utils::ReadParam('sTitle', '', false, 'raw_data');
503			$sAttCode = utils::ReadParam('sAttCode', '');
504			$bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true');
505			$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, $bSearchMode);
506			$sJson = utils::ReadParam('json', '', false, 'raw_data');
507			if (!empty($sJson))
508			{
509				$oWizardHelper = WizardHelper::FromJSON($sJson);
510				$oObj = $oWizardHelper->GetTargetObject();
511			}
512			else
513			{
514				// Search form: no current object
515				$oObj = null;
516			}
517			$oWidget->GetSearchDialog($oPage, $sTitle, $oObj);
518			break;
519
520		// ui.extkeywidget
521		case 'objectCreationForm':
522			$oPage->SetContentType('text/html');
523			// Retrieving parameters
524			$sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class');
525	        $iInputId = utils::ReadParam('iInputId', '');
526	        $sAttCode = utils::ReadParam('sAttCode', '');
527	        $sJson = utils::ReadParam('json', '', false, 'raw_data');
528			// Building form, if target class is abstract we ask the user for the desired leaf class
529	        $oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, false);
530	        if(MetaModel::IsAbstract($sTargetClass))
531	        {
532	            $oWidget->GetClassSelectionForm($oPage);
533	        }
534	        else
535	        {
536		        $aPrefillFormParam = array();
537	            if (!empty($sJson))
538	            {
539		            $oWizardHelper = WizardHelper::FromJSON($sJson);
540		            $oObj = $oWizardHelper->GetTargetObject();
541		            $oAppContext = new ApplicationContext();
542		            $aPrefillFormParam = array( 'user' => $_SESSION["auth_user"],
543			                                    'context' => $oAppContext->GetAsHash(),
544			                                    'att_code' => $sAttCode,
545		                                        'source_obj' => $oObj,
546			                                    'origin' => 'console'
547		            );
548	            }
549	            else
550	            {
551	                // Search form: no current object
552	                $oObj = null;
553	            }
554	            $oWidget->GetObjectCreationForm($oPage, $oObj, $aPrefillFormParam);
555	        }
556			break;
557
558		// ui.extkeywidget
559		case 'doCreateObject':
560			$oPage->SetContentType('application/json');
561			$sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class');
562			$iInputId = utils::ReadParam('iInputId', '');
563			$sFormPrefix = utils::ReadParam('sFormPrefix', '');
564			$sAttCode = utils::ReadParam('sAttCode', '');
565			$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, false);
566			$aResult = $oWidget->DoCreateObject($oPage);
567			echo json_encode($aResult);
568			break;
569
570		// ui.extkeywidget
571		case 'getObjectName':
572			$oPage->SetContentType('application/json');
573			$sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class');
574			$iInputId = utils::ReadParam('iInputId', '');
575			$iObjectId = utils::ReadParam('iObjectId', '');
576			$bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true');
577			$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, '', $bSearchMode);
578			$sName = $oWidget->GetObjectName($iObjectId);
579			echo json_encode(array('name' => $sName));
580			break;
581
582		// ui.extkeywidget
583		case 'displayHierarchy':
584			$oPage->SetContentType('text/html');
585			$sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class');
586			$sInputId = utils::ReadParam('sInputId', '');
587			$sFilter = utils::ReadParam('sFilter', '', false, 'raw_data');
588			$sJson = utils::ReadParam('json', '', false, 'raw_data');
589			$currValue = utils::ReadParam('value', '');
590			$bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true');
591			if (!empty($sJson))
592			{
593				$oWizardHelper = WizardHelper::FromJSON($sJson);
594				$oObj = $oWizardHelper->GetTargetObject();
595			}
596			else
597			{
598				// Search form: no current object
599				$oObj = null;
600			}
601			$oWidget = new UIExtKeyWidget($sTargetClass, $sInputId, '', $bSearchMode);
602			$oWidget->DisplayHierarchy($oPage, $sFilter, $currValue, $oObj);
603			break;
604
605		////////////////////////////////////////////////////
606
607		// ui.linkswidget
608		case 'doAddObjects':
609			$oPage->SetContentType('text/html');
610			$sAttCode = utils::ReadParam('sAttCode', '');
611			$iInputId = utils::ReadParam('iInputId', '');
612			$sSuffix = utils::ReadParam('sSuffix', '');
613			$sRemoteClass = utils::ReadParam('sRemoteClass', $sClass, false, 'class');
614			$bDuplicates = (utils::ReadParam('bDuplicates', 'false') == 'false') ? false : true;
615			$sJson = utils::ReadParam('json', '', false, 'raw_data');
616			$iMaxAddedId = utils::ReadParam('max_added_id');
617			$oWizardHelper = WizardHelper::FromJSON($sJson);
618			$oObj = $oWizardHelper->GetTargetObject();
619			$oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix, $bDuplicates);
620			if ($sFilter != '')
621			{
622				$oFullSetFilter = DBObjectSearch::unserialize($sFilter);
623			}
624			else
625			{
626				$oFullSetFilter = new DBObjectSearch($sRemoteClass);
627			}
628			$oWidget->DoAddObjects($oPage, $iMaxAddedId, $oFullSetFilter, $oObj);
629			break;
630
631		////////////////////////////////////////////////////////////
632
633		case 'wizard_helper_preview':
634			$oPage->SetContentType('text/html');
635			$sJson = utils::ReadParam('json_obj', '', false, 'raw_data');
636			$oWizardHelper = WizardHelper::FromJSON($sJson);
637			$oObj = $oWizardHelper->GetTargetObject();
638			$oObj->DisplayBareProperties($oPage);
639			break;
640
641		case 'wizard_helper':
642			$oPage->SetContentType('text/html');
643			$sJson = utils::ReadParam('json_obj', '', false, 'raw_data');
644			$oWizardHelper = WizardHelper::FromJSON($sJson);
645			$oObj = $oWizardHelper->GetTargetObject();
646			$sClass = $oWizardHelper->GetTargetClass();
647			foreach($oWizardHelper->GetFieldsForDefaultValue() as $sAttCode)
648			{
649				$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
650				$defaultValue = $oAttDef->GetDefaultValue($oObj);
651				$oWizardHelper->SetDefaultValue($sAttCode, $defaultValue);
652				$oObj->Set($sAttCode, $defaultValue);
653			}
654			$sFormPrefix = $oWizardHelper->GetFormPrefix();
655			$aExpectedAttributes = ($oWizardHelper->GetStimulus() === null) ? array() : $oObj->GetTransitionAttributes($oWizardHelper->GetStimulus(), $oWizardHelper->GetInitialState());
656			foreach($oWizardHelper->GetFieldsForAllowedValues() as $sAttCode)
657			{
658				$sId = $oWizardHelper->GetIdForField($sAttCode);
659				if ($sId != '')
660				{
661					if (array_key_exists($sAttCode, $aExpectedAttributes))
662					{
663						$iFlags = $aExpectedAttributes[$sAttCode];
664					}
665					elseif ($oObj->IsNew())
666					{
667						$iFlags = $oObj->GetInitialStateAttributeFlags($sAttCode);
668					}
669					else
670					{
671						$iFlags = $oObj->GetAttributeFlags($sAttCode);
672					}
673					if ($iFlags & OPT_ATT_READONLY)
674					{
675						$sHTMLValue = "<span id=\"field_{$sId}\">".$oObj->GetAsHTML($sAttCode);
676						$oWizardHelper->SetAllowedValuesHtml($sAttCode, $sHTMLValue);
677					}
678					else
679					{
680						// It may happen that the field we'd like to update does not
681						// exist in the form. For example, if the field should be hidden/read-only
682						// in the current state of the object
683						$value = $oObj->Get($sAttCode);
684						$displayValue = $oObj->GetEditValue($sAttCode);
685						$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
686						if (!$oAttDef->IsWritable())
687						{
688							// Even non-writable fields (like AttributeExternal) can be refreshed
689							$sHTMLValue = $oObj->GetAsHTML($sAttCode);
690						}
691						else
692						{
693							$iFlags = MetaModel::GetAttributeFlags($sClass, $oObj->GetState(), $sAttCode);
694							$sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value, $displayValue, $sId, '', $iFlags, array('this' => $oObj, 'formPrefix' => $sFormPrefix), false);
695							// Make sure that we immediately validate the field when we reload it
696							$oPage->add_ready_script("$('#$sId').trigger('validate');");
697						}
698						$oWizardHelper->SetAllowedValuesHtml($sAttCode, $sHTMLValue);
699					}
700				}
701			}
702			$oPage->add_script("oWizardHelper{$sFormPrefix}.m_oData=".$oWizardHelper->ToJSON().";\noWizardHelper{$sFormPrefix}.UpdateFields();\n");
703			break;
704
705		case 'obj_creation_form':
706			$oPage->SetContentType('text/html');
707			$sJson = utils::ReadParam('json_obj', '', false, 'raw_data');
708			$oWizardHelper = WizardHelper::FromJSON($sJson);
709			$oObj = $oWizardHelper->GetTargetObject();
710			$sClass = $oWizardHelper->GetTargetClass();
711			$sTargetState = utils::ReadParam('target_state', '');
712			$iTransactionId = utils::ReadParam('transaction_id', '', false, 'transaction_id');
713			$oObj->Set(MetaModel::GetStateAttributeCode($sClass), $sTargetState);
714			cmdbAbstractObject::DisplayCreationForm($oPage, $sClass, $oObj, array(), array('action' => utils::GetAbsoluteUrlAppRoot().'pages/UI.php', 'transaction_id' => $iTransactionId));
715			break;
716
717		// DisplayBlock
718		case 'ajax':
719			$oPage->SetContentType('text/html');
720			if ($sFilter != "")
721			{
722				$sExtraParams = stripslashes(utils::ReadParam('extra_params', '', false, 'raw_data'));
723				$aExtraParams = array();
724				if (!empty($sExtraParams))
725				{
726					$aExtraParams = json_decode(str_replace("'", '"', $sExtraParams), true /* associative array */);
727				}
728				// Restore the app context from the ExtraParams
729				$oAppContext = new ApplicationContext(false); // false => don't read the context yet !
730				$aContext = array();
731				foreach($oAppContext->GetNames() as $sName)
732				{
733					$sParamName = 'c['.$sName.']';
734					if (isset($aExtraParams[$sParamName]))
735					{
736						$aContext[$sName] = $aExtraParams[$sParamName];
737					}
738				}
739				$_REQUEST['c'] = $aContext;
740				if ($sEncoding == 'oql')
741				{
742					$oFilter = DBSearch::FromOQL($sFilter);
743				}
744				else
745				{
746					$oFilter = DBSearch::unserialize($sFilter);
747				}
748				$oDisplayBlock = new DisplayBlock($oFilter, $sStyle, false);
749				$aExtraParams['display_limit'] = true;
750				$aExtraParams['truncated'] = true;
751				$oDisplayBlock->RenderContent($oPage, $aExtraParams);
752			}
753			else
754			{
755				$oPage->p("Invalid query (empty filter).");
756			}
757			break;
758
759		case 'displayCSVHistory':
760			$oPage->SetContentType('text/html');
761			$bShowAll = (utils::ReadParam('showall', 'false') == 'true');
762			BulkChange::DisplayImportHistory($oPage, true, $bShowAll);
763			break;
764
765		case 'details':
766			$oPage->SetContentType('text/html');
767			$key = utils::ReadParam('id', 0);
768			$oFilter = new DBObjectSearch($sClass);
769			$oFilter->AddCondition('id', $key, '=');
770			$oDisplayBlock = new DisplayBlock($oFilter, 'details', false);
771			$oDisplayBlock->RenderContent($oPage);
772			break;
773
774		case 'pie_chart':
775			$oPage->SetContentType('application/json');
776			$sGroupBy = utils::ReadParam('group_by', '');
777			if ($sFilter != '')
778			{
779				if ($sEncoding == 'oql')
780				{
781					$oFilter = DBSearch::FromOQL($sFilter);
782				}
783				else
784				{
785					$oFilter = DBSearch::unserialize($sFilter);
786				}
787				$oDisplayBlock = new DisplayBlock($oFilter, 'pie_chart_ajax', false);
788				$oDisplayBlock->RenderContent($oPage, array('group_by' => $sGroupBy));
789			}
790			else
791			{
792
793				$oPage->add("<chart>\n<chart_type>3d pie</chart_type><!-- empty filter '$sFilter' --></chart>\n.");
794			}
795			break;
796
797		case 'chart':
798			// Workaround for IE8 + IIS + HTTPS
799			// See TRAC #363, fix described here: http://forums.codecharge.com/posts.php?post_id=97771
800			$oPage->add_header("Expires: Fri, 17 Jul 1970 05:00:00 GMT");
801			$oPage->add_header("Cache-Control: cache, must-revalidate");
802			$oPage->add_header("Pragma: public");
803
804			$aParams = utils::ReadParam('params', array(), false, 'raw_data');
805			if ($sFilter != '')
806			{
807				$oFilter = DBSearch::unserialize($sFilter);
808				$oDisplayBlock = new DisplayBlock($oFilter, 'chart_ajax', false);
809				$oDisplayBlock->RenderContent($oPage, $aParams);
810			}
811			else
812			{
813
814				$oPage->add("<chart>\n<chart_type>3d pie</chart_type><!-- empty filter '$sFilter' --></chart>\n.");
815			}
816			break;
817
818		case 'modal_details':
819			$oPage->SetContentType('text/html');
820			$key = utils::ReadParam('id', 0);
821			$oFilter = new DBObjectSearch($sClass);
822			$oFilter->AddCondition('id', $key, '=');
823			$oPage->Add("<p style=\"width:100%; margin-top:-5px;padding:3px; background-color:#33f; color:#fff;\">Object Details</p>\n");
824			$oDisplayBlock = new DisplayBlock($oFilter, 'details', false);
825			$oDisplayBlock->RenderContent($oPage);
826			$oPage->Add("<input type=\"button\" class=\"jqmClose\" value=\" Close \" />\n");
827			break;
828
829		case 'link':
830			$oPage->SetContentType('text/html');
831			$sClass = utils::ReadParam('sclass', 'logInfra', false, 'class');
832			$sAttCode = utils::ReadParam('attCode', 'name');
833			//$sOrg = utils::ReadParam('org_id', '');
834			$sName = utils::ReadParam('q', '');
835			$iMaxCount = utils::ReadParam('max', 30);
836			$iCount = 0;
837			$oFilter = new DBObjectSearch($sClass);
838			$oFilter->AddCondition($sAttCode, $sName, 'Begins with');
839			//$oFilter->AddCondition('org_id', $sOrg, '=');
840			$oSet = new CMDBObjectSet($oFilter, array($sAttCode => true));
841			while (($iCount < $iMaxCount) && ($oObj = $oSet->fetch()))
842			{
843				$oPage->add($oObj->GetAsHTML($sAttCode)."|".$oObj->GetKey()."\n");
844				$iCount++;
845			}
846			break;
847
848		case 'combo_options':
849			$oPage->SetContentType('text/html');
850			$oFilter = DBSearch::FromOQL($sFilter);
851			$oSet = new CMDBObjectSet($oFilter);
852			while ($oObj = $oSet->fetch())
853			{
854				$oPage->add('<option title="Here is more information..." value="'.$oObj->GetKey().'">'.$oObj->GetName().'</option>');
855			}
856			break;
857
858		case 'display_document':
859			$id = utils::ReadParam('id', '');
860			$sField = utils::ReadParam('field', '');
861			if (!empty($sClass) && ($sClass != 'InlineImage') && !empty($id) && !empty($sField))
862			{
863				ormDocument::DownloadDocument($oPage, $sClass, $id, $sField, 'inline');
864			}
865			break;
866
867		case 'search_form':
868			$oPage->SetContentType('text/html');
869			$sClass = utils::ReadParam('className', '', false, 'class');
870			$sRootClass = utils::ReadParam('baseClass', '', false, 'class');
871			$currentId = utils::ReadParam('currentId', '');
872			$sTableId = utils::ReadParam('_table_id_', null, false, 'raw_data');
873			$sAction = utils::ReadParam('action', '');
874			$sSelectionMode = utils::ReadParam('selection_mode', null,false,'raw_data');
875			$sResultListOuterSelector = utils::ReadParam('result_list_outer_selector', null,false,'raw_data');
876			$scssCount = utils::ReadParam('css_count', null,false,'raw_data');
877			$sTableInnerId = utils::ReadParam('table_inner_id', null,false,'raw_data');
878
879			$oFilter = new DBObjectSearch($sClass);
880			$oSet = new CMDBObjectSet($oFilter);
881			$sHtml = cmdbAbstractObject::GetSearchForm($oPage, $oSet, array('currentId' => $currentId,
882																			'baseClass' => $sRootClass,
883																			'action' => $sAction,
884																			'table_id' => $sTableId,
885																			'selection_mode' => $sSelectionMode,
886																			'result_list_outer_selector' => $sResultListOuterSelector,
887																			'cssCount' => $scssCount,
888																			'table_inner_id' => $sTableInnerId));
889			$oPage->add($sHtml);
890			break;
891
892		case 'set_pref':
893			$sCode = utils::ReadPostedParam('code', '');
894			$sValue = utils::ReadPostedParam('value', '', 'raw_data');
895			appUserPreferences::SetPref($sCode, $sValue);
896			break;
897
898		case 'erase_all_pref':
899			// Can be useful in case a user got some corrupted prefs...
900			appUserPreferences::ClearPreferences();
901			break;
902
903		case 'on_form_cancel':
904			// Called when a creation/modification form is cancelled by the end-user
905			// Let's take this opportunity to inform the plug-ins so that they can perform some cleanup
906			$iTransactionId = utils::ReadParam('transaction_id', 0, false, 'transaction_id');
907			$sTempId = utils::GetUploadTempId($iTransactionId);
908			InlineImage::OnFormCancel($sTempId);
909			foreach(MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance)
910			{
911				$oExtensionInstance->OnFormCancel($sTempId);
912			}
913			$sObjClass = utils::ReadParam('obj_class', '', false, 'class');
914			$iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer');
915			$sToken = utils::ReadParam('token', 0, false, 'raw_data');
916			if (($sObjClass != '') && ($iObjKey != 0) && ($sToken != ''))
917			{
918				$bReleaseLock = iTopOwnershipLock::ReleaseLock($sObjClass, $iObjKey, $sToken);
919			}
920			break;
921
922		case 'dashboard':
923			$oPage->SetContentType('text/html');
924			$id = (int)utils::ReadParam('id', 0);
925			$sAttCode = utils::ReadParam('attcode', '');
926			/** @var \cmdbAbstractObject $oObj */
927			$oObj = MetaModel::GetObject($sClass, $id);
928			$oObj->DisplayDashboard($oPage, $sAttCode);
929			break;
930
931		case 'export_dashboard':
932			$sDashboardId = utils::ReadParam('id', '', false, 'raw_data');
933			$sDashboardFile = utils::ReadParam('file', '', false, 'raw_data');
934			$oDashboard = RuntimeDashboard::GetDashboard($sDashboardFile, $sDashboardId);
935			if (!is_null($oDashboard))
936			{
937				$oPage->TrashUnexpectedOutput();
938				$oPage->SetContentType('text/xml');
939				$oPage->SetContentDisposition('attachment', 'dashboard_'.$oDashboard->GetTitle().'.xml');
940				$oPage->add($oDashboard->ToXml());
941			}
942			break;
943
944		case 'import_dashboard':
945			$sTransactionId = utils::ReadParam('transaction_id', '', false, 'transaction_id');
946			if (!utils::IsTransactionValid($sTransactionId, true))
947			{
948				throw new SecurityException('ajax.render.php import_dashboard : invalid transaction_id');
949			}
950			$sDashboardId = utils::ReadParam('id', '', false, 'raw_data');
951			$sDashboardFile = utils::ReadParam('file', '', false, 'raw_data');
952			$oDashboard = RuntimeDashboard::GetDashboard($sDashboardFile, $sDashboardId);
953			$aResult = array('error' => '');
954			if (!is_null($oDashboard))
955			{
956				try
957				{
958					$oDoc = utils::ReadPostedDocument('dashboard_upload_file');
959					$oDashboard->FromXml($oDoc->GetData());
960					$oDashboard->Save();
961				} catch (DOMException $e)
962				{
963					$aResult = array('error' => Dict::S('UI:Error:InvalidDashboardFile'));
964				} catch (Exception $e)
965				{
966					$aResult = array('error' => $e->getMessage());
967				}
968			}
969			else
970			{
971				$aResult['error'] = 'Dashboard id="'.$sDashboardId.'" not found.';
972			}
973			$oPage->add(json_encode($aResult));
974			break;
975
976		case 'toggle_dashboard':
977			$oPage->SetContentType('text/html');
978			$sDashboardId = utils::ReadParam('dashboard_id', '', false, 'raw_data');
979
980			$bStandardSelected = appUserPreferences::GetPref('display_original_dashboard_'.$sDashboardId, false);
981			appUserPreferences::UnsetPref('display_original_dashboard_'.$sDashboardId);
982			appUserPreferences::SetPref('display_original_dashboard_'.$sDashboardId, !$bStandardSelected);
983
984			$aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data');
985			$sDashboardFile = utils::ReadParam('file', '', false, 'raw_data');
986			$sReloadURL = utils::ReadParam('reload_url', '', false, 'raw_data');
987			$oDashboard = RuntimeDashboard::GetDashboard($sDashboardFile, $sDashboardId);
988			$aResult = array('error' => '');
989			if (!is_null($oDashboard))
990			{
991				if (!empty($sReloadURL))
992				{
993					$oDashboard->SetReloadURL($sReloadURL);
994				}
995				$oDashboard->Render($oPage, false, $aExtraParams);
996			}
997			$oPage->add_ready_script("$('.dashboard_contents table.listResults').tableHover(); $('.dashboard_contents table.listResults').tablesorter( { widgets: ['myZebra', 'truncatedList']} );");
998			break;
999
1000		case 'reload_dashboard':
1001			$oPage->SetContentType('text/html');
1002			$sDashboardId = utils::ReadParam('dashboard_id', '', false, 'raw_data');
1003			$aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data');
1004			$sDashboardFile = utils::ReadParam('file', '', false, 'raw_data');
1005			$sReloadURL = utils::ReadParam('reload_url', '', false, 'raw_data');
1006			$oDashboard = RuntimeDashboard::GetDashboard($sDashboardFile, $sDashboardId);
1007			$aResult = array('error' => '');
1008			if (!is_null($oDashboard))
1009			{
1010				if (!empty($sReloadURL))
1011				{
1012					$oDashboard->SetReloadURL($sReloadURL);
1013				}
1014				$oDashboard->Render($oPage, false, $aExtraParams);
1015			}
1016			$oPage->add_ready_script("$('.dashboard_contents table.listResults').tableHover(); $('.dashboard_contents table.listResults').tablesorter( { widgets: ['myZebra', 'truncatedList']} );");
1017			break;
1018
1019		case 'save_dashboard':
1020			$sDashboardId = utils::ReadParam('dashboard_id', '', false, 'raw_data');
1021			$aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data');
1022			$sReloadURL = utils::ReadParam('reload_url', '', false, 'raw_data');
1023			$sJSExtraParams = json_encode($aExtraParams);
1024			$aParams = array();
1025			$aParams['layout_class'] = utils::ReadParam('layout_class', '');
1026			$aParams['title'] = utils::ReadParam('title', '', false, 'raw_data');
1027			$aParams['auto_reload'] = utils::ReadParam('auto_reload', false);
1028			$aParams['auto_reload_sec'] = utils::ReadParam('auto_reload_sec', 300);
1029			$aParams['cells'] = utils::ReadParam('cells', array(), false, 'raw_data');
1030			$oDashboard = new RuntimeDashboard($sDashboardId);
1031			$oDashboard->FromParams($aParams);
1032			$oDashboard->Save();
1033			$sDashboardFile = addslashes(utils::ReadParam('file', '', false, 'raw_data'));
1034			$sDivId = preg_replace('/[^a-zA-Z0-9_]/', '', $sDashboardId);
1035			// trigger a reload of the current page since the dashboard just changed
1036			$oPage->add_script(
1037<<<EOF
1038			$('.dashboard_contents#$sDivId').block();
1039			$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
1040			   { operation: 'reload_dashboard', dashboard_id: '$sDashboardId', file: '$sDashboardFile', extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
1041			   function(data){
1042				 $('.dashboard_contents#$sDivId').html(data);
1043				 $('.dashboard_contents#$sDivId').unblock();
1044				}
1045			 );
1046EOF
1047			);
1048			break;
1049
1050		case 'revert_dashboard':
1051			$sDashboardId = utils::ReadParam('dashboard_id', '', false, 'raw_data');
1052			$sReloadURL = utils::ReadParam('reload_url', '', false, 'raw_data');
1053			appUserPreferences::UnsetPref('display_original_dashboard_'.$sDashboardId);
1054			$oDashboard = new RuntimeDashboard($sDashboardId);
1055			$oDashboard->Revert();
1056			$sFile = addslashes($oDashboard->GetDefinitionFile());
1057			$sDivId = preg_replace('/[^a-zA-Z0-9_]/', '', $sDashboardId);
1058			// trigger a reload of the current page since the dashboard just changed
1059			$oPage->add_script(
1060<<<EOF
1061			$('.dashboard_contents#$sDivId').block();
1062			$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
1063			   { operation: 'reload_dashboard', dashboard_id: '$sDashboardId', file: '$sFile', reload_url: '$sReloadURL'},
1064			   function(data){
1065				 $('.dashboard_contents#$sDivId').html(data);
1066				 $('.dashboard_contents#$sDivId').unblock();
1067				}
1068			 );
1069EOF
1070			);
1071			break;
1072
1073		case 'render_dashboard':
1074			$sDashboardId = utils::ReadParam('dashboard_id', '', false, 'raw_data');
1075			$aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data');
1076			$aParams = array();
1077			$aParams['layout_class'] = utils::ReadParam('layout_class', '');
1078			$aParams['title'] = utils::ReadParam('title', '', false, 'raw_data');
1079			$aParams['cells'] = utils::ReadParam('cells', array(), false, 'raw_data');
1080			$aParams['auto_reload'] = utils::ReadParam('auto_reload', false);
1081			$aParams['auto_reload_sec'] = utils::ReadParam('auto_reload_sec', 300);
1082			$sReloadURL = utils::ReadParam('reload_url', '', false, 'raw_data');
1083			$oDashboard = new RuntimeDashboard($sDashboardId);
1084			$oDashboard->FromParams($aParams);
1085			$oDashboard->SetReloadURL($sReloadURL);
1086			$oDashboard->Render($oPage, true /* bEditMode */, $aExtraParams);
1087			break;
1088
1089		case 'dashboard_editor':
1090			$sId = utils::ReadParam('id', '', false, 'raw_data');
1091			$aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data');
1092			$sDashboardFile = utils::ReadParam('file', '', false, 'raw_data');
1093			$sReloadURL = utils::ReadParam('reload_url', '', false, 'raw_data');
1094			$oDashboard = RuntimeDashboard::GetDashboard($sDashboardFile, $sId);
1095			if (!is_null($oDashboard))
1096			{
1097				if (!empty($sReloadURL))
1098				{
1099					$oDashboard->SetReloadURL($sReloadURL);
1100				}
1101				$oDashboard->RenderEditor($oPage, $aExtraParams);
1102			}
1103			break;
1104
1105		case 'new_dashlet':
1106			require_once(APPROOT.'application/forms.class.inc.php');
1107			require_once(APPROOT.'application/dashlet.class.inc.php');
1108			$sDashletClass = utils::ReadParam('dashlet_class', '');
1109			$sDashletId = utils::ReadParam('dashlet_id', '', false, 'raw_data');
1110			if (is_subclass_of($sDashletClass, 'Dashlet'))
1111			{
1112				$oDashlet = new $sDashletClass(new ModelReflectionRuntime(), $sDashletId);
1113				$offset = $oPage->start_capture();
1114				$oDashlet->DoRender($oPage, true /* bEditMode */, false /* bEnclosingDiv */);
1115				$sHtml = addslashes($oPage->end_capture($offset));
1116				$sHtml = str_replace("\n", '', $sHtml);
1117				$sHtml = str_replace("\r", '', $sHtml);
1118				$oPage->add_script("$('#dashlet_$sDashletId').html('$sHtml');"); // in ajax web page add_script has the same effect as add_ready_script
1119				// but is executed BEFORE all 'ready_scripts'
1120				$oForm = $oDashlet->GetForm(); // Rebuild the form since the values/content changed
1121				$oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property'));
1122				$sHtml = addslashes($oForm->RenderAsPropertySheet($oPage, true /* bReturnHtml */, '.itop-dashboard'));
1123				$sHtml = str_replace("\n", '', $sHtml);
1124				$sHtml = str_replace("\r", '', $sHtml);
1125				$oPage->add_script("$('#dashlet_properties_$sDashletId').html('$sHtml')"); // in ajax web page add_script has the same effect as add_ready_script																	   // but is executed BEFORE all 'ready_scripts'
1126			}
1127			break;
1128
1129		case 'update_dashlet_property':
1130			require_once(APPROOT.'application/forms.class.inc.php');
1131			require_once(APPROOT.'application/dashlet.class.inc.php');
1132			$aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data');
1133			$aParams = utils::ReadParam('params', '', false, 'raw_data');
1134			$sDashletClass = $aParams['attr_dashlet_class'];
1135			$sDashletType = $aParams['attr_dashlet_type'];
1136			$sDashletId = $aParams['attr_dashlet_id'];
1137			$aUpdatedProperties = $aParams['updated']; // Code of the changed properties as an array: 'attr_xxx', 'attr_xxy', etc...
1138			$aPreviousValues = $aParams['previous_values']; // hash array: 'attr_xxx' => 'old_value'
1139			if (is_subclass_of($sDashletClass, 'Dashlet'))
1140			{
1141				/** @var \Dashlet $oDashlet */
1142				$oDashlet = new $sDashletClass(new ModelReflectionRuntime(), $sDashletId);
1143				$oDashlet->SetDashletType($sDashletType);
1144				$oForm = $oDashlet->GetForm();
1145				$aValues = $oForm->ReadParams(); // hash array: 'xxx' => 'new_value'
1146
1147				$aCurrentValues = $aValues;
1148				$aUpdatedDecoded = array();
1149				foreach($aUpdatedProperties as $sProp)
1150				{
1151					$sDecodedProp = str_replace('attr_', '', $sProp); // Remove the attr_ prefix
1152					$aCurrentValues[$sDecodedProp] = (isset($aPreviousValues[$sProp]) ? $aPreviousValues[$sProp] : ''); // Set the previous value
1153					$aUpdatedDecoded[] = $sDecodedProp;
1154				}
1155
1156				$oDashlet->FromParams($aCurrentValues);
1157				$sPrevClass = get_class($oDashlet);
1158				$oDashlet = $oDashlet->Update($aValues, $aUpdatedDecoded);
1159				$sNewClass = get_class($oDashlet);
1160				if ($sNewClass != $sPrevClass)
1161				{
1162					$oPage->add_ready_script("$('#dashlet_$sDashletId').dashlet('option', {dashlet_class: '$sNewClass'});");
1163				}
1164				if ($oDashlet->IsRedrawNeeded())
1165				{
1166					$offset = $oPage->start_capture();
1167					$oDashlet->DoRender($oPage, true /* bEditMode */, false /* bEnclosingDiv */, $aExtraParams);
1168					$sHtml = addslashes($oPage->end_capture($offset));
1169					$sHtml = str_replace("\n", '', $sHtml);
1170					$sHtml = str_replace("\r", '', $sHtml);
1171
1172					$oPage->add_script("$('#dashlet_$sDashletId').html('$sHtml');"); // in ajax web page add_script has the same effect as add_ready_script
1173					// but is executed BEFORE all 'ready_scripts'
1174				}
1175				if ($oDashlet->IsFormRedrawNeeded())
1176				{
1177					$oForm = $oDashlet->GetForm(); // Rebuild the form since the values/content changed
1178					$oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property', 'extra_params' => $aExtraParams));
1179					$sHtml = addslashes($oForm->RenderAsPropertySheet($oPage, true /* bReturnHtml */, '.itop-dashboard'));
1180					$sHtml = str_replace("\n", '', $sHtml);
1181					$sHtml = str_replace("\r", '', $sHtml);
1182					$oPage->add_script("$('#dashlet_properties_$sDashletId').html('$sHtml')"); // in ajax web page add_script has the same effect as add_ready_script																	   // but is executed BEFORE all 'ready_scripts'
1183					// but is executed BEFORE all 'ready_scripts'
1184				}
1185			}
1186			break;
1187
1188		case 'dashlet_creation_dlg':
1189			$sOQL = utils::ReadParam('oql', '', false, 'raw_data');
1190			RuntimeDashboard::GetDashletCreationDlgFromOQL($oPage, $sOQL);
1191			break;
1192
1193		case 'add_dashlet':
1194			$oForm = RuntimeDashboard::GetDashletCreationForm();
1195			$aValues = $oForm->ReadParams();
1196
1197			$sDashletClass = $aValues['dashlet_class'];
1198			$sMenuId = $aValues['menu_id'];
1199
1200			if (is_subclass_of($sDashletClass, 'Dashlet'))
1201			{
1202				$oDashlet = new $sDashletClass(new ModelReflectionRuntime(), 0);
1203				$oDashlet->FromParams($aValues);
1204
1205				ApplicationMenu::LoadAdditionalMenus();
1206				$index = ApplicationMenu::GetMenuIndexById($sMenuId);
1207				$oMenu = ApplicationMenu::GetMenuNode($index);
1208				$oMenu->AddDashlet($oDashlet);
1209				// navigate to the dashboard page
1210				if ($aValues['open_editor'])
1211				{
1212					$oPage->add_ready_script("window.location.href='".addslashes(utils::GetAbsoluteUrlAppRoot().'pages/UI.php?c[menu]='.urlencode($sMenuId))."&edit=1';"); // reloads the page, doing a GET even if we arrived via a POST
1213				}
1214			}
1215			break;
1216
1217		case 'shortcut_list_dlg':
1218			$sOQL = utils::ReadParam('oql', '', false, 'raw_data');
1219			$sTableSettings = utils::ReadParam('table_settings', '', false, 'raw_data');
1220			ShortcutOQL::GetCreationDlgFromOQL($oPage, $sOQL, $sTableSettings);
1221			break;
1222
1223		case 'shortcut_list_create':
1224			$oForm = ShortcutOQL::GetCreationForm();
1225			$aValues = $oForm->ReadParams();
1226
1227			$oAppContext = new ApplicationContext();
1228			$aContext = $oAppContext->GetAsHash();
1229			$sContext = serialize($aContext);
1230
1231			$oShortcut = MetaModel::NewObject("ShortcutOQL");
1232			$oShortcut->Set('user_id', UserRights::GetUserId());
1233			$oShortcut->Set("context", $sContext);
1234			$oShortcut->Set("name", $aValues['name']);
1235			$oShortcut->Set("oql", $aValues['oql']);
1236			$iAutoReload = (int)$aValues['auto_reload_sec'];
1237			if (($aValues['auto_reload']) && ($iAutoReload > 0))
1238			{
1239				$oShortcut->Set("auto_reload_sec", max(MetaModel::GetConfig()->Get('min_reload_interval'), $iAutoReload));
1240				$oShortcut->Set("auto_reload", 'custom');
1241			}
1242			utils::PushArchiveMode(false);
1243			$iId = $oShortcut->DBInsertNoReload();
1244			utils::PopArchiveMode();
1245
1246			$oShortcut->CloneTableSettings($aValues['table_settings']);
1247
1248			// Add the menu node in the right place
1249			//
1250			// Mmmm... already done because the newly created menu is read from the DB
1251			//         as soon as we invoke DisplayMenu
1252
1253			// Refresh the menu pane
1254			$aExtraParams = array();
1255			ApplicationMenu::DisplayMenu($oPage, $aExtraParams);
1256			break;
1257
1258		case 'shortcut_rename_dlg':
1259			$oSearch = new DBObjectSearch('Shortcut');
1260			$aShortcuts = utils::ReadMultipleSelection($oSearch);
1261			$iShortcut = $aShortcuts[0];
1262			$oShortcut = MetaModel::GetObject('Shortcut', $iShortcut);
1263			$oShortcut->StartRenameDialog($oPage);
1264			break;
1265
1266		case 'shortcut_rename_go':
1267			$iShortcut = utils::ReadParam('id', 0);
1268			$oShortcut = MetaModel::GetObject('Shortcut', $iShortcut);
1269
1270			$sName = utils::ReadParam('attr_name', '', false, 'raw_data');
1271			if (strlen($sName) > 0)
1272			{
1273				$oShortcut->Set('name', $sName);
1274				utils::PushArchiveMode(false);
1275				$oShortcut->DBUpdate();
1276				utils::PopArchiveMode();
1277				$oPage->add_ready_script('window.location.reload();');
1278			}
1279
1280			break;
1281
1282		case 'shortcut_delete_go':
1283			$oSearch = new DBObjectSearch('Shortcut');
1284			$oSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
1285			$aShortcuts = utils::ReadMultipleSelection($oSearch);
1286			foreach($aShortcuts as $iShortcut)
1287			{
1288				$oShortcut = MetaModel::GetObject('Shortcut', $iShortcut);
1289				utils::PushArchiveMode(false);
1290				$oShortcut->DBDelete();
1291				utils::PopArchiveMode();
1292				$oPage->add_ready_script('window.location.reload();');
1293			}
1294			break;
1295
1296		case 'about_box':
1297			$oPage->SetContentType('text/html');
1298
1299			$sDialogTitle = addslashes(Dict::S('UI:About:Title'));
1300			$oPage->add_ready_script(
1301				<<<EOF
1302$('#about_box').dialog({
1303	width: 700,
1304	modal: true,
1305	title: '$sDialogTitle',
1306	close: function() { $(this).remove(); }
1307});
1308$("#collapse_support_details").click(function() {
1309	$("#support_details").slideToggle('normal');
1310	$("#collapse_support_details").toggleClass('open');
1311});
1312$('#support_details').toggle();
1313EOF
1314			);
1315			$sVersionString = Dict::Format('UI:iTopVersion:Long', ITOP_APPLICATION, ITOP_VERSION, ITOP_REVISION, ITOP_BUILD_DATE);
1316			$sMySQLVersion = CMDBSource::GetDBVersion();
1317			$sPHPVersion = phpversion();
1318			$sOSVersion = PHP_OS;
1319			$sWebServerVersion = $_SERVER["SERVER_SOFTWARE"];
1320			$sModules = implode(', ', get_loaded_extensions());
1321
1322			// Get the datamodel directory
1323			$oFilter = DBObjectSearch::FromOQL('SELECT ModuleInstallation WHERE name="datamodel"');
1324			$oSet = new DBObjectSet($oFilter, array('installed' => false)); // Most recent first
1325			$oLastInstall = $oSet->Fetch();
1326			$sLastInstallDate = $oLastInstall->Get('installed');
1327			$sDataModelVersion = $oLastInstall->Get('version');
1328			$aDataModelInfo = json_decode($oLastInstall->Get('comment'), true);
1329			$sDataModelSourceDir = $aDataModelInfo['source_dir'];
1330
1331			require_once(APPROOT.'setup/runtimeenv.class.inc.php');
1332			$sCurrEnv = utils::GetCurrentEnvironment();
1333			$oRuntimeEnv = new RunTimeEnvironment($sCurrEnv);
1334			$aSearchDirs = array(APPROOT.$sDataModelSourceDir);
1335			if (file_exists(APPROOT.'extensions'))
1336			{
1337				$aSearchDirs[] = APPROOT.'extensions';
1338			}
1339			$sExtraDir = APPROOT.'data/'.$sCurrEnv.'-modules/';
1340			if (file_exists($sExtraDir))
1341			{
1342				$aSearchDirs[] = $sExtraDir;
1343			}
1344			$aAvailableModules = $oRuntimeEnv->AnalyzeInstallation(MetaModel::GetConfig(), $aSearchDirs);
1345
1346			require_once(APPROOT.'setup/setuputils.class.inc.php');
1347			$aLicenses = SetupUtils::GetLicenses($sCurrEnv);
1348
1349			$aItopSettings = array('cron_max_execution_time', 'timezone');
1350			$aPHPSettings = array('memory_limit', 'max_execution_time', 'upload_max_filesize', 'post_max_size');
1351			$aMySQLSettings = array('max_allowed_packet', 'key_buffer_size', 'query_cache_size');
1352			$aMySQLStatuses = array('Key_read_requests', 'Key_reads');
1353
1354			if (extension_loaded('suhosin'))
1355			{
1356				$aPHPSettings[] = 'suhosin.post.max_vars';
1357				$aPHPSettings[] = 'suhosin.get.max_value_length';
1358			}
1359
1360			$aMySQLVars = array();
1361			foreach(CMDBSource::QueryToArray('SHOW VARIABLES') as $aRow)
1362			{
1363				$aMySQLVars[$aRow['Variable_name']] = $aRow['Value'];
1364			}
1365
1366			$aMySQLStats = array();
1367			foreach(CMDBSource::QueryToArray('SHOW GLOBAL STATUS') as $aRow)
1368			{
1369				$aMySQLStats[$aRow['Variable_name']] = $aRow['Value'];
1370			}
1371
1372			// Display
1373			//
1374			$oPage->add("<div id=\"about_box\">");
1375			$oPage->add('<div style="margin-left: 120px;">');
1376			$oPage->add('<table>');
1377			$oPage->add('<tr>');
1378			$oPage->add('<td><a href="http://www.combodo.com" title="www.combodo.com" target="_blank" style="background: none;"><img src="../images/logo-combodo.png?t='.utils::GetCacheBusterTimestamp().'" style="float: right;"/></a></td>');
1379			$oPage->add('<td style="padding-left: 20px;">');
1380			$oPage->add($sVersionString.'<br/>');
1381			$oPage->add(Dict::S('UI:About:DataModel').': '.$sDataModelVersion.'<br/>');
1382			$oPage->add('MySQL: '.$sMySQLVersion.'<br/>');
1383			$oPage->add('PHP: '.$sPHPVersion.'<br/>');
1384			$oPage->add('</td>');
1385			$oPage->add('</tr>');
1386			$oPage->add('</table>');
1387			$oPage->add("</div>");
1388
1389			$oPage->add("<div>");
1390			$oPage->add('<fieldset>');
1391			$oPage->add('<legend>'.Dict::S('UI:About:Licenses').'</legend>');
1392			$oPage->add('<ul style="margin: 0; font-size: smaller; max-height: 15em; overflow: auto;">');
1393			$index = 0;
1394			foreach($aLicenses as $oLicense)
1395			{
1396				$oPage->add('<li><b>'.$oLicense->product.'</b>, &copy; '.$oLicense->author.' is licensed under the <b>'.$oLicense->license_type.' license</b>. (<a id="toggle_'.$index.'" class="CollapsibleLabel" style="cursor:pointer;">Details</a>)');
1397				$oPage->add('<div id="license_'.$index.'" class="license_text" style="display:none;overflow:auto;max-height:10em;font-size:small;border:1px #696969 solid;margin-bottom:1em; margin-top:0.5em;padding:0.5em;">'.$oLicense->text.'</div>');
1398				$oPage->add_ready_script('$("#toggle_'.$index.'").click( function() { $("#license_'.$index.'").slideToggle("normal"); } );');
1399				$index++;
1400			}
1401			$oPage->add('</ul>');
1402			$oPage->add('</fieldset>');
1403			$oPage->add("</div>");
1404
1405			$oPage->add('<fieldset>');
1406			$oPage->add('<legend>'.Dict::S('UI:About:InstallationOptions').'</legend>');
1407			$oPage->add("<div style=\"max-height: 150px; overflow: auto; font-size: smaller;\">");
1408			$oPage->add('<ul style="margin: 0;">');
1409
1410			require_once(APPROOT.'setup/extensionsmap.class.inc.php');
1411			$oExtensionsMap = new iTopExtensionsMap();
1412			$oExtensionsMap->LoadChoicesFromDatabase(MetaModel::GetConfig());
1413			$aChoices = $oExtensionsMap->GetChoices();
1414			foreach($aChoices as $oExtension)
1415			{
1416				switch ($oExtension->sSource)
1417				{
1418					case iTopExtension::SOURCE_REMOTE:
1419						$sSource = ' <span class="extension-source">'.Dict::S('UI:About:RemoteExtensionSource').'</span>';
1420						break;
1421
1422					case iTopExtension::SOURCE_MANUAL:
1423						$sSource = ' <span class="extension-source">'.Dict::S('UI:About:ManualExtensionSource').'</span>';
1424						break;
1425
1426					default:
1427						$sSource = '';
1428				}
1429				$oPage->add('<li title="'.Dict::Format('UI:About:Extension_Version', $oExtension->sInstalledVersion).'">'.$oExtension->sLabel.$sSource.'</li>');
1430			}
1431			$oPage->add('</ul>');
1432			$oPage->add("</div>");
1433			$oPage->add('</fieldset>');
1434
1435
1436			// MUST NOT be localized, as the information given here will be sent to the support
1437			$oPage->add("<a id=\"collapse_support_details\" class=\"CollapsibleLabel\" href=\"#\">".Dict::S('UI:About:Support')."</a></br>\n");
1438			$oPage->add("<div id=\"support_details\">");
1439			$oPage->add('<textarea readonly style="width: 660px; height: 150px; font-size: smaller;">');
1440			$oPage->add("===== begin =====\n");
1441			$oPage->add('iTopVersion: '.ITOP_VERSION."\n");
1442			$oPage->add('iTopBuild: '.ITOP_REVISION."\n");
1443			$oPage->add('iTopBuildDate: '.ITOP_BUILD_DATE."\n");
1444			$oPage->add('DataModelVersion: '.$sDataModelVersion."\n");
1445			$oPage->add('MySQLVersion: '.$sMySQLVersion."\n");
1446			$oPage->add('PHPVersion: '.$sPHPVersion."\n");
1447			$oPage->add('OSVersion: '.$sOSVersion."\n");
1448			$oPage->add('WebServerVersion: '.$sWebServerVersion."\n");
1449			$oPage->add('PHPModules: '.$sModules."\n");
1450			foreach($aItopSettings as $siTopVar)
1451			{
1452				$oPage->add('ItopSetting/'.$siTopVar.': '.MetaModel::GetConfig()->Get($siTopVar)."\n");
1453			}
1454			foreach($aPHPSettings as $sPHPVar)
1455			{
1456				$oPage->add('PHPSetting/'.$sPHPVar.': '.ini_get($sPHPVar)."\n");
1457			}
1458			foreach($aMySQLSettings as $sMySQLVar)
1459			{
1460				$oPage->add('MySQLSetting/'.$sMySQLVar.': '.$aMySQLVars[$sMySQLVar]."\n");
1461			}
1462			foreach($aMySQLStatuses as $sMySQLStatus)
1463			{
1464				$oPage->add('MySQLStatus/'.$sMySQLStatus.': '.$aMySQLStats[$sMySQLStatus]."\n");
1465			}
1466
1467			$oPage->add('InstallDate: '.$sLastInstallDate."\n");
1468			$oPage->add('InstallPath: '.APPROOT."\n");
1469			$oPage->add("---- Installation choices ----\n");
1470			foreach($aChoices as $oExtension)
1471			{
1472				switch ($oExtension->sSource)
1473				{
1474					case iTopExtension::SOURCE_REMOTE:
1475						$sSource = ' ('.Dict::S('UI:About:RemoteExtensionSource').')';
1476						break;
1477
1478					case iTopExtension::SOURCE_MANUAL:
1479						$sSource = ' ('.Dict::S('UI:About:ManualExtensionSource').')';
1480						break;
1481
1482					default:
1483						$sSource = '';
1484				}
1485				$oPage->add('InstalledExtension/'.$oExtension->sCode.'/'.$oExtension->sVersion.$sSource."\n");
1486			}
1487			$oPage->add("---- Actual modules installed ----\n");
1488			foreach($aAvailableModules as $sModuleId => $aModuleData)
1489			{
1490				if ($sModuleId == '_Root_') continue;
1491				if ($aModuleData['version_db'] == '') continue;
1492				$oPage->add('InstalledModule/'.$sModuleId.': '.$aModuleData['version_db']."\n");
1493			}
1494
1495			$oPage->add('===== end =====');
1496			$oPage->add('</textarea>');
1497			$oPage->add("</div>");
1498
1499			$oPage->add("</div>");
1500			break;
1501
1502		case 'history':
1503			$oPage->SetContentType('text/html');
1504			$id = (int)utils::ReadParam('id', 0);
1505			$iStart = (int)utils::ReadParam('start', 0);
1506			$iCount = (int)utils::ReadParam('count', MetaModel::GetConfig()->Get('max_history_length'));
1507			$oObj = MetaModel::GetObject($sClass, $id);
1508			$oObj->DisplayBareHistory($oPage, false, $iCount, $iStart);
1509			$oPage->add_ready_script("$('#history table.listResults').tableHover(); $('#history table.listResults').tablesorter( { widgets: ['myZebra', 'truncatedList']} );");
1510			break;
1511
1512		case 'history_from_filter':
1513			$oPage->SetContentType('text/html');
1514			$oHistoryFilter = DBSearch::unserialize($sFilter);
1515			$iStart = (int)utils::ReadParam('start', 0);
1516			$iCount = (int)utils::ReadParam('count', MetaModel::GetConfig()->Get('max_history_length'));
1517			$oBlock = new HistoryBlock($oHistoryFilter, 'table', false);
1518			$oBlock->SetLimit($iCount, $iStart);
1519			$oBlock->Display($oPage, 'history');
1520			$oPage->add_ready_script("$('#history table.listResults').tableHover(); $('#history table.listResults').tablesorter( { widgets: ['myZebra', 'truncatedList']} );");
1521			break;
1522
1523		case 'full_text_search':
1524			$aFullTextNeedles = utils::ReadParam('needles', array(), false, 'raw_data');
1525			$sFullText = trim(implode(' ', $aFullTextNeedles));
1526			$sClassName = utils::ReadParam('class', '');
1527			$iCount = utils::ReadParam('count', 0);
1528			$iCurrentPos = utils::ReadParam('position', 0);
1529			$iTune = utils::ReadParam('tune', 0);
1530			if (empty($sFullText))
1531			{
1532				$oPage->p(Dict::S('UI:Search:NoSearch'));
1533				break;
1534			}
1535
1536			// Search in full text mode in all the classes
1537			$aMatches = array();
1538
1539			// Build the ordered list of classes to search into
1540			//
1541			if (empty($sClassName))
1542			{
1543				$aSearchClasses = MetaModel::GetClasses('searchable');
1544			}
1545			else
1546			{
1547				// Search is limited to a given class and its subclasses
1548				$aSearchClasses = MetaModel::EnumChildClasses($sClassName, ENUM_CHILD_CLASSES_ALL);
1549			}
1550			// Skip abstract classes, since we search in all the child classes anyway
1551			foreach($aSearchClasses as $idx => $sClass)
1552			{
1553				if (MetaModel::IsAbstract($sClass))
1554				{
1555					unset($aSearchClasses[$idx]);
1556				}
1557			}
1558
1559			$sMaxChunkDuration = MetaModel::GetConfig()->Get('full_text_chunk_duration');
1560			$aAccelerators = MetaModel::GetConfig()->Get('full_text_accelerators');
1561
1562			foreach(array_reverse($aAccelerators) as $sClass => $aRestriction)
1563			{
1564				$bSkip = false;
1565				$iPos = array_search($sClass, $aSearchClasses);
1566				if ($iPos !== false)
1567				{
1568					unset($aSearchClasses[$iPos]);
1569				}
1570				else
1571				{
1572					$bSkip = true;
1573				}
1574				$bSkip |= array_key_exists('skip', $aRestriction) ? $aRestriction['skip'] : false;
1575				if (!in_array($sClass, $aSearchClasses))
1576				{
1577					if ($sClass == $sClassName)
1578					{
1579						// Class explicitely requested, do NOT skip it
1580						// beware: there may not be a 'query' defined for a skipped class !
1581						$bSkip = false;
1582					}
1583				}
1584				if (!$bSkip)
1585				{
1586					// NOT skipped, add the class to the list of classes to search into
1587					if (array_key_exists('query', $aRestriction))
1588					{
1589						array_unshift($aSearchClasses, $aRestriction['query']);
1590					}
1591					else
1592					{
1593						// No accelerator query
1594						array_unshift($aSearchClasses, $sClassName);
1595					}
1596				}
1597			}
1598
1599			$aSearchClasses = array_values($aSearchClasses); // renumbers the array starting from zero, removing the missing indexes
1600			$fStarted = microtime(true);
1601			$iFoundInThisRound = 0;
1602			for($iPos = $iCurrentPos; $iPos < count($aSearchClasses); $iPos++)
1603			{
1604				if ($iFoundInThisRound && (microtime(true) - $fStarted >= $sMaxChunkDuration))
1605				{
1606					break;
1607				}
1608
1609				$sClassSpec = $aSearchClasses[$iPos];
1610				if (substr($sClassSpec, 0, 7) == 'SELECT ')
1611				{
1612					$oFilter = DBObjectSearch::FromOQL($sClassSpec);
1613					$sClassName = $oFilter->GetClass();
1614					$sNeedleFormat = isset($aAccelerators[$sClassName]['needle']) ? $aAccelerators[$sClassName]['needle'] : '%$needle$%';
1615					$sNeedle = str_replace('$needle$', $sFullText, $sNeedleFormat);
1616					$aParams = array('needle' => $sNeedle);
1617				}
1618				else
1619				{
1620					$sClassName = $sClassSpec;
1621					$oFilter = new DBObjectSearch($sClassName);
1622					$aParams = array();
1623
1624					foreach($aFullTextNeedles as $sSearchText)
1625					{
1626						$oFilter->AddCondition_FullText($sSearchText);
1627					}
1628				}
1629				$oFilter->SetShowObsoleteData(utils::ShowObsoleteData());
1630				// Skip abstract classes
1631				if (MetaModel::IsAbstract($sClassName)) continue;
1632
1633				if ($iTune > 0)
1634				{
1635					$fStartedClass = microtime(true);
1636				}
1637				$oSet = new DBObjectSet($oFilter, array(), $aParams);
1638				if (array_key_exists($sClassName, $aAccelerators) && array_key_exists('attributes', $aAccelerators[$sClassName]))
1639				{
1640					$oSet->OptimizeColumnLoad(array($oFilter->GetClassAlias() => $aAccelerators[$sClassName]['attributes']));
1641				}
1642
1643				$sFullTextJS = addslashes($sFullText);
1644				$bEnableEnlarge = array_key_exists($sClassName, $aAccelerators) && array_key_exists('query', $aAccelerators[$sClassName]);
1645				if (array_key_exists($sClassName, $aAccelerators) && array_key_exists('enable_enlarge', $aAccelerators[$sClassName]))
1646				{
1647					$bEnableEnlarge &= $aAccelerators[$sClassName]['enable_enlarge'];
1648				}
1649				$sEnlargeTheSearch =
1650					<<<EOF
1651			$('.search-class-$sClassName button').prop('disabled', true);
1652
1653			$('.search-class-$sClassName h2').append('&nbsp;<img id="indicator" src="../images/indicator.gif">');
1654			var oParams = {operation: 'full_text_search_enlarge', class: '$sClassName', text: '$sFullTextJS'};
1655			$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', oParams, function(data) {
1656				$('.search-class-$sClassName').html(data);
1657			});
1658EOF;
1659
1660
1661				$sEnlargeButton = '';
1662				if ($bEnableEnlarge)
1663				{
1664					$sEnlargeButton = "&nbsp;<button onclick=\"".htmlentities($sEnlargeTheSearch, ENT_QUOTES, 'UTF-8')."\">".Dict::S('UI:Search:Enlarge')."</button>";
1665				}
1666				if ($oSet->Count() > 0)
1667				{
1668					$aLeafs = array();
1669					while ($oObj = $oSet->Fetch())
1670					{
1671						if (get_class($oObj) == $sClassName)
1672						{
1673							$aLeafs[] = $oObj->GetKey();
1674							$iFoundInThisRound++;
1675						}
1676					}
1677					$oLeafsFilter = new DBObjectSearch($sClassName);
1678					if (count($aLeafs) > 0)
1679					{
1680						$iCount += count($aLeafs);
1681						$oPage->add("<div class=\"search-class-result search-class-$sClassName\">\n");
1682						$oPage->add("<div class=\"page_header\">\n");
1683						if (array_key_exists($sClassName, $aAccelerators))
1684						{
1685							$oPage->add("<h2>".MetaModel::GetClassIcon($sClassName)."&nbsp;<span class=\"hilite\">".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aLeafs), Metamodel::GetName($sClassName)).$sEnlargeButton."</h2>\n");
1686						}
1687						else
1688						{
1689							$oPage->add("<h2>".MetaModel::GetClassIcon($sClassName)."&nbsp;<span class=\"hilite\">".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aLeafs), Metamodel::GetName($sClassName))."</h2>\n");
1690						}
1691						$oPage->add("</div>\n");
1692						$oLeafsFilter->AddCondition('id', $aLeafs, 'IN');
1693						$oBlock = new DisplayBlock($oLeafsFilter, 'list', false);
1694						$sBlockId = 'global_search_'.$sClassName;
1695						$oPage->add('<div id="'.$sBlockId.'">');
1696						$oBlock->RenderContent($oPage, array('table_id' => $sBlockId, 'currentId' => $sBlockId));
1697						$oPage->add("</div>\n");
1698						$oPage->add("</div>\n");
1699						$oPage->p('&nbsp;'); // Some space ?
1700					}
1701				}
1702				else
1703				{
1704					if (array_key_exists($sClassName, $aAccelerators))
1705					{
1706						$oPage->add("<div class=\"search-class-result search-class-$sClassName\">\n");
1707						$oPage->add("<div class=\"page_header\">\n");
1708						$oPage->add("<h2>".MetaModel::GetClassIcon($sClassName)."&nbsp;<span class=\"hilite\">".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', 0, Metamodel::GetName($sClassName)).$sEnlargeButton."</h2>\n");
1709						$oPage->add("</div>\n");
1710						$oPage->add("</div>\n");
1711						$oPage->p('&nbsp;'); // Some space ?
1712					}
1713				}
1714				if ($iTune > 0)
1715				{
1716					$fDurationClass = microtime(true) - $fStartedClass;
1717					$oPage->add_script("oTimeStatistics.$sClassName = $fDurationClass;");
1718				}
1719			}
1720			if ($iPos < count($aSearchClasses))
1721			{
1722				$sJSNeedle = json_encode($aFullTextNeedles);
1723				$oPage->add_ready_script(
1724					<<<EOF
1725				var oParams = {operation: 'full_text_search', position: $iPos, needles: $sJSNeedle, count: $iCount, tune: $iTune};
1726				$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', oParams, function(data) {
1727					$('#full_text_results').append(data);
1728				});
1729EOF
1730				);
1731			}
1732			else
1733			{
1734				// We're done
1735				$oPage->add_ready_script(
1736					<<<EOF
1737$('#full_text_indicator').hide();
1738$('#full_text_progress,#full_text_progress_placeholder').hide(500);
1739EOF
1740				);
1741
1742				if ($iTune > 0)
1743				{
1744					$oPage->add_ready_script(
1745						<<<EOF
1746				var sRes = '<h4>Search statistics (tune = 1)</h4><table>';
1747				sRes += '<thead><tr><th>Class</th><th>Time</th></tr></thead>';
1748				sRes += '<tbody>';
1749				var fTotal = 0;
1750				for (var sClass in oTimeStatistics)
1751				{
1752					fTotal = fTotal + oTimeStatistics[sClass];
1753					fRounded = Math.round(oTimeStatistics[sClass] * 1000) / 1000;
1754					sRes += '<tr><td>' + sClass + '</td><td>' + fRounded + '</td></tr>';
1755				}
1756
1757				fRoundedTotal = Math.round(fTotal * 1000) / 1000;
1758				sRes += '<tr><td><b>Total</b></td><td><b>' + fRoundedTotal + '</b></td></tr>';
1759				sRes += '</tbody>';
1760				sRes += '</table>';
1761				$('#full_text_results').append(sRes);
1762EOF
1763					);
1764				}
1765
1766				if ($iCount == 0)
1767				{
1768					$sFullTextSummary = addslashes(Dict::S('UI:Search:NoObjectFound'));
1769					$oPage->add_ready_script("$('#full_text_results').append('<div id=\"no_object_found\">$sFullTextSummary</div>');");
1770				}
1771			}
1772			break;
1773
1774		case 'full_text_search_enlarge':
1775			$sFullText = trim(utils::ReadParam('text', '', false, 'raw_data'));
1776			$sClass = trim(utils::ReadParam('class', ''));
1777			$iTune = utils::ReadParam('tune', 0);
1778
1779			if (preg_match('/^"(.*)"$/', $sFullText, $aMatches))
1780			{
1781				// The text is surrounded by double-quotes, remove the quotes and treat it as one single expression
1782				$aFullTextNeedles = array($aMatches[1]);
1783			}
1784			else
1785			{
1786				// Split the text on the blanks and treat this as a search for <word1> AND <word2> AND <word3>
1787				$aFullTextNeedles = explode(' ', $sFullText);
1788			}
1789
1790			$oFilter = new DBObjectSearch($sClass);
1791			foreach($aFullTextNeedles as $sSearchText)
1792			{
1793				$oFilter->AddCondition_FullText($sSearchText);
1794			}
1795			$oFilter->SetShowObsoleteData(utils::ShowObsoleteData());
1796			$oSet = new DBObjectSet($oFilter);
1797			$oPage->add("<div class=\"page_header\">\n");
1798			$oPage->add("<h2>".MetaModel::GetClassIcon($sClass)."&nbsp;<span class=\"hilite\">".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', $oSet->Count(), Metamodel::GetName($sClass))."</h2>\n");
1799			$oPage->add("</div>\n");
1800			if ($oSet->Count() > 0)
1801			{
1802				$aLeafs = array();
1803				while ($oObj = $oSet->Fetch())
1804				{
1805					if (get_class($oObj) == $sClass)
1806					{
1807						$aLeafs[] = $oObj->GetKey();
1808					}
1809				}
1810				$oLeafsFilter = new DBObjectSearch($sClass);
1811				if (count($aLeafs) > 0)
1812				{
1813					$oLeafsFilter->AddCondition('id', $aLeafs, 'IN');
1814					$oBlock = new DisplayBlock($oLeafsFilter, 'list', false);
1815					$sBlockId = 'global_search_'.$sClass;
1816					$oPage->add('<div id="'.$sBlockId.'">');
1817					$oBlock->RenderContent($oPage, array('table_id' => $sBlockId, 'currentId' => $sBlockId));
1818					$oPage->add('</div>');
1819					$oPage->P('&nbsp;'); // Some space ?
1820					// Hide "no object found"
1821					$oPage->add_ready_script('$("#no_object_found").hide();');
1822				}
1823			}
1824			$oPage->add_ready_script(
1825				<<<EOF
1826$('#full_text_indicator').hide();
1827$('#full_text_progress,#full_text_progress_placeholder').hide(500);
1828EOF
1829			);
1830			break;
1831
1832		case 'xlsx_export_dialog':
1833			$sFilter = utils::ReadParam('filter', '', false, 'raw_data');
1834			$oPage->SetContentType('text/html');
1835			$oPage->add(
1836				<<<EOF
1837<style>
1838 .ui-progressbar {
1839	position: relative;
1840}
1841.progress-label {
1842	position: absolute;
1843	left: 50%;
1844	top: 1px;
1845	font-size: 11pt;
1846}
1847.download-form button {
1848	display:block;
1849	margin-left: auto;
1850	margin-right: auto;
1851	margin-top: 2em;
1852}
1853.ui-progressbar-value {
1854	background: url(../setup/orange-progress.gif);
1855}
1856.progress-bar {
1857	height: 20px;
1858}
1859.statistics > div {
1860	padding-left: 16px;
1861	cursor: pointer;
1862	font-size: 10pt;
1863	background: url(../images/minus.gif) 0 2px no-repeat;
1864}
1865.statistics > div.closed {
1866	padding-left: 16px;
1867	background: url(../images/plus.gif) 0 2px no-repeat;
1868}
1869
1870.statistics .closed .stats-data {
1871	display: none;
1872}
1873.stats-data td {
1874	padding-right: 5px;
1875}
1876</style>
1877EOF
1878			);
1879			$oPage->add('<div id="XlsxExportDlg">');
1880			$oPage->add('<div class="export-options">');
1881			$oPage->add('<p><input type="checkbox" id="export-advanced-mode"/>&nbsp;<label for="export-advanced-mode">'.Dict::S('UI:CSVImport:AdvancedMode').'</label></p>');
1882			$oPage->add('<p style="font-size:10pt;margin-left:2em;margin-top:-0.5em;padding-bottom:1em;">'.Dict::S('UI:CSVImport:AdvancedMode+').'</p>');
1883			$oPage->add('<p><input type="checkbox" id="export-auto-download" checked="checked"/>&nbsp;<label for="export-auto-download">'.Dict::S('ExcelExport:AutoDownload').'</label></p>');
1884			$oPage->add('</div>');
1885			$oPage->add('<div class="progress"><p class="status-message">'.Dict::S('ExcelExport:PreparingExport').'</p><div class="progress-bar"><div class="progress-label"></div></div></div>');
1886			$oPage->add('<div class="statistics"><div class="stats-toggle closed">'.Dict::S('ExcelExport:Statistics').'<div class="stats-data"></div></div></div>');
1887			$oPage->add('</div>');
1888			$aLabels = array(
1889				'dialog_title' => Dict::S('ExcelExporter:ExportDialogTitle'),
1890				'cancel_button' => Dict::S('UI:Button:Cancel'),
1891				'export_button' => Dict::S('ExcelExporter:ExportButton'),
1892				'download_button' => Dict::Format('ExcelExporter:DownloadButton', 'export.xlsx'), //TODO: better name for the file (based on the class of the filter??)
1893			);
1894			$sJSLabels = json_encode($aLabels);
1895			$sFilter = addslashes($sFilter);
1896			$sJSPageUrl = addslashes(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php');
1897			$oPage->add_ready_script("$('#XlsxExportDlg').xlsxexporter({filter: '$sFilter', labels: $sJSLabels, ajax_page_url: '$sJSPageUrl'});");
1898			break;
1899
1900		case 'xlsx_start':
1901			$sFilter = utils::ReadParam('filter', '', false, 'raw_data');
1902			$bAdvanced = (utils::ReadParam('advanced', 'false') == 'true');
1903			$oSearch = DBObjectSearch::unserialize($sFilter);
1904
1905			$oExcelExporter = new ExcelExporter();
1906			$oExcelExporter->SetObjectList($oSearch);
1907			//$oExcelExporter->SetChunkSize(10); //Only for testing
1908			$oExcelExporter->SetAdvancedMode($bAdvanced);
1909			$sToken = $oExcelExporter->SaveState();
1910			$oPage->add(json_encode(array('status' => 'ok', 'token' => $sToken)));
1911			break;
1912
1913		case 'xlsx_run':
1914			$sMemoryLimit = MetaModel::GetConfig()->Get('xlsx_exporter_memory_limit');
1915			ini_set('memory_limit', $sMemoryLimit);
1916			ini_set('max_execution_time', max(300, ini_get('max_execution_time'))); // At least 5 minutes
1917
1918			$sToken = utils::ReadParam('token', '', false, 'raw_data');
1919			$oExcelExporter = new ExcelExporter($sToken);
1920			$aStatus = $oExcelExporter->Run();
1921			$aResults = array('status' => $aStatus['code'], 'percentage' => $aStatus['percentage'], 'message' => $aStatus['message']);
1922			if ($aStatus['code'] == 'done')
1923			{
1924				$aResults['statistics'] = $oExcelExporter->GetStatistics('html');
1925			}
1926			$oPage->add(json_encode($aResults));
1927			break;
1928
1929		case 'xlsx_download':
1930			$sToken = utils::ReadParam('token', '', false, 'raw_data');
1931			$oPage->SetContentType('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
1932			$oPage->SetContentDisposition('attachment', 'export.xlsx');
1933			$sFileContent = ExcelExporter::GetExcelFileFromToken($sToken);
1934			$oPage->add($sFileContent);
1935			ExcelExporter::CleanupFromToken($sToken);
1936			break;
1937
1938		case 'xlsx_abort':
1939			// Stop & cleanup an export...
1940			$sToken = utils::ReadParam('token', '', false, 'raw_data');
1941			ExcelExporter::CleanupFromToken($sToken);
1942			break;
1943
1944		case 'relation_pdf':
1945		case 'relation_attachment':
1946			require_once(APPROOT.'core/simplegraph.class.inc.php');
1947			require_once(APPROOT.'core/relationgraph.class.inc.php');
1948			require_once(APPROOT.'core/displayablegraph.class.inc.php');
1949			$sRelation = utils::ReadParam('relation', 'impacts');
1950			$sDirection = utils::ReadParam('direction', 'down');
1951
1952			$iGroupingThreshold = utils::ReadParam('g', 5, false, 'integer');
1953			$sPageFormat = utils::ReadParam('p', 'A4');
1954			$sPageOrientation = utils::ReadParam('o', 'L');
1955			$sTitle = utils::ReadParam('title', '', false, 'raw_data');
1956			$sPositions = utils::ReadParam('positions', null, false, 'raw_data');
1957			$aExcludedClasses = utils::ReadParam('excluded_classes', array(), false, 'raw_data');
1958			$bIncludeList = (bool)utils::ReadParam('include_list', false);
1959			$sComments = utils::ReadParam('comments', '', false, 'raw_data');
1960			$aContexts = utils::ReadParam('contexts', array(), false, 'raw_data');
1961			$sContextKey = utils::ReadParam('context_key', '', false, 'raw_data');
1962			$aPositions = null;
1963			if ($sPositions != null)
1964			{
1965				$aPositions = json_decode($sPositions, true);
1966			}
1967
1968			// Get the list of source objects
1969			$aSources = utils::ReadParam('sources', array(), false, 'raw_data');
1970			$aSourceObjects = array();
1971			foreach($aSources as $sClass => $aIDs)
1972			{
1973				$oSearch = new DBObjectSearch($sClass);
1974				$oSearch->AddCondition('id', $aIDs, 'IN');
1975				$oSet = new DBObjectSet($oSearch);
1976				while ($oObj = $oSet->Fetch())
1977				{
1978					$aSourceObjects[] = $oObj;
1979				}
1980			}
1981			$sSourceClass = '*';
1982			if (count($aSourceObjects) == 1)
1983			{
1984				$sSourceClass = get_class($aSourceObjects[0]);
1985			}
1986
1987			// Get the list of excluded objects
1988			$aExcluded = utils::ReadParam('excluded', array(), false, 'raw_data');
1989			$aExcludedObjects = array();
1990			foreach($aExcluded as $sClass => $aIDs)
1991			{
1992				$oSearch = new DBObjectSearch($sClass);
1993				$oSearch->AddCondition('id', $aIDs, 'IN');
1994				$oSet = new DBObjectSet($oSearch);
1995				while ($oObj = $oSet->Fetch())
1996				{
1997					$aExcludedObjects[] = $oObj;
1998				}
1999			}
2000
2001			$iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth');
2002			if ($sDirection == 'up')
2003			{
2004				$oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts);
2005			}
2006			else
2007			{
2008				$oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects, $aContexts);
2009			}
2010
2011			// Remove excluded classes from the graph
2012			if (count($aExcludedClasses) > 0)
2013			{
2014				$oIterator = new RelationTypeIterator($oRelGraph, 'Node');
2015				foreach($oIterator as $oNode)
2016				{
2017					$oObj = $oNode->GetProperty('object');
2018					if ($oObj && in_array(get_class($oObj), $aExcludedClasses))
2019					{
2020						$oRelGraph->FilterNode($oNode);
2021					}
2022				}
2023			}
2024
2025			$oPage = new PDFPage($sTitle, $sPageFormat, $sPageOrientation);
2026			$oPage->SetContentDisposition('attachment', $sTitle.'.pdf');
2027
2028			$oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down'));
2029			$oGraph->InitFromGraphviz();
2030			if ($aPositions != null)
2031			{
2032				$oGraph->UpdatePositions($aPositions);
2033			}
2034
2035			$aGroups = array();
2036			$oIterator = new RelationTypeIterator($oGraph, 'Node');
2037			foreach($oIterator as $oNode)
2038			{
2039				if ($oNode instanceof DisplayableGroupNode)
2040				{
2041					$aGroups[$oNode->GetProperty('group_index')] = $oNode->GetObjects();
2042				}
2043			}
2044			// First page is the graph
2045			$oGraph->RenderAsPDF($oPage, $sComments, $sContextKey);
2046
2047			if ($bIncludeList)
2048			{
2049				// Then the lists of objects (one table per finalclass)
2050				$aResults = array();
2051				$oIterator = new RelationTypeIterator($oRelGraph, 'Node');
2052				foreach($oIterator as $oNode)
2053				{
2054					$oObj = $oNode->GetProperty('object'); // Some nodes (Redundancy Nodes and Group) do not contain an object
2055					if ($oObj)
2056					{
2057						$sObjClass = get_class($oObj);
2058						if (!array_key_exists($sObjClass, $aResults))
2059						{
2060							$aResults[$sObjClass] = array();
2061						}
2062						$aResults[$sObjClass][] = $oObj;
2063					}
2064				}
2065
2066				$oPage->get_tcpdf()->AddPage();
2067				$oPage->get_tcpdf()->SetFont('dejavusans', '', 10, '', true); // Reset the font size to its default
2068				$oPage->add('<div class="page_header"><h1>'.Dict::S('UI:RelationshipList').'</h1></div>');
2069				$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
2070				foreach($aResults as $sListClass => $aObjects)
2071				{
2072					set_time_limit($iLoopTimeLimit * count($aObjects));
2073					$oSet = CMDBObjectSet::FromArray($sListClass, $aObjects);
2074					$oSet->SetShowObsoleteData(utils::ShowObsoleteData());
2075					$sHtml = "<div class=\"page_header\">\n";
2076					$sHtml .= "<table class=\"section\"><tr><td>".MetaModel::GetClassIcon($sListClass, true, 'width: 24px; height: 24px;')." ".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', $oSet->Count(),
2077							Metamodel::GetName($sListClass))."</td></tr></table>\n";
2078					$sHtml .= "</div>\n";
2079					$oPage->add($sHtml);
2080					cmdbAbstractObject::DisplaySet($oPage, $oSet, array('table_id' => $sSourceClass.'_'.$sRelation.'_'.$sDirection.'_'.$sListClass));
2081					$oPage->p(''); // Some space
2082				}
2083
2084				// Then the content of the groups (one table per group)
2085				if (count($aGroups) > 0)
2086				{
2087					$oPage->get_tcpdf()->AddPage();
2088					$oPage->add('<div class="page_header"><h1>'.Dict::S('UI:RelationGroups').'</h1></div>');
2089					foreach($aGroups as $idx => $aObjects)
2090					{
2091						set_time_limit($iLoopTimeLimit * count($aObjects));
2092						$sListClass = get_class(current($aObjects));
2093						$oSet = CMDBObjectSet::FromArray($sListClass, $aObjects);
2094						$sHtml = "<div class=\"page_header\">\n";
2095						$sHtml .= "<table class=\"section\"><tr><td>".MetaModel::GetClassIcon($sListClass, true, 'width: 24px; height: 24px;')." ".Dict::Format('UI:RelationGroupNumber_N', (1 + $idx))."</td></tr></table>\n";
2096						$sHtml .= "</div>\n";
2097						$oPage->add($sHtml);
2098						cmdbAbstractObject::DisplaySet($oPage, $oSet);
2099						$oPage->p(''); // Some space
2100					}
2101				}
2102			}
2103			if ($operation == 'relation_attachment')
2104			{
2105				$sObjClass = utils::ReadParam('obj_class', '', false, 'class');
2106				$iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer');
2107
2108				// Save the generated PDF as an attachment
2109				$sPDF = $oPage->get_pdf();
2110				$oPage = new ajax_page('');
2111				$oAttachment = new Attachment();
2112				$oAttachment->Set('item_class', $sObjClass);
2113				$oAttachment->Set('item_id', $iObjKey);
2114				$oDoc = new ormDocument($sPDF, 'application/pdf', $sTitle.'.pdf');
2115				$oAttachment->Set('contents', $oDoc);
2116				$iAttachmentId = $oAttachment->DBInsert();
2117				$aRet = array(
2118					'status' => 'ok',
2119					'att_id' => $iAttachmentId,
2120				);
2121				$oPage->add(json_encode($aRet));
2122			}
2123			break;
2124
2125		case 'relation_json':
2126			require_once(APPROOT.'core/simplegraph.class.inc.php');
2127			require_once(APPROOT.'core/relationgraph.class.inc.php');
2128			require_once(APPROOT.'core/displayablegraph.class.inc.php');
2129			$sRelation = utils::ReadParam('relation', 'impacts');
2130			$sDirection = utils::ReadParam('direction', 'down');
2131			$iGroupingThreshold = utils::ReadParam('g', 5);
2132			$sPositions = utils::ReadParam('positions', null, false, 'raw_data');
2133			$aExcludedClasses = utils::ReadParam('excluded_classes', array(), false, 'raw_data');
2134			$aContexts = utils::ReadParam('contexts', array(), false, 'raw_data');
2135			$sContextKey = utils::ReadParam('context_key', array(), false, 'raw_data');
2136			$aPositions = null;
2137			if ($sPositions != null)
2138			{
2139				$aPositions = json_decode($sPositions, true);
2140			}
2141
2142			// Get the list of source objects
2143			$aSources = utils::ReadParam('sources', array(), false, 'raw_data');
2144			$aSourceObjects = array();
2145			foreach($aSources as $sClass => $aIDs)
2146			{
2147				$oSearch = new DBObjectSearch($sClass);
2148				$oSearch->AddCondition('id', $aIDs, 'IN');
2149				$oSet = new DBObjectSet($oSearch);
2150				while ($oObj = $oSet->Fetch())
2151				{
2152					$aSourceObjects[] = $oObj;
2153				}
2154			}
2155
2156			// Get the list of excluded objects
2157			$aExcluded = utils::ReadParam('excluded', array(), false, 'raw_data');
2158			$aExcludedObjects = array();
2159			foreach($aExcluded as $sClass => $aIDs)
2160			{
2161				$oSearch = new DBObjectSearch($sClass);
2162				$oSearch->AddCondition('id', $aIDs, 'IN');
2163				$oSet = new DBObjectSet($oSearch);
2164				while ($oObj = $oSet->Fetch())
2165				{
2166					$aExcludedObjects[] = $oObj;
2167				}
2168			}
2169
2170			// Compute the graph
2171			$iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth');
2172			if ($sDirection == 'up')
2173			{
2174				$oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts);
2175			}
2176			else
2177			{
2178				$oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects, $aContexts);
2179			}
2180
2181			// Remove excluded classes from the graph
2182			if (count($aExcludedClasses) > 0)
2183			{
2184				$oIterator = new RelationTypeIterator($oRelGraph, 'Node');
2185				foreach($oIterator as $oNode)
2186				{
2187					$oObj = $oNode->GetProperty('object');
2188					if ($oObj && in_array(get_class($oObj), $aExcludedClasses))
2189					{
2190						$oRelGraph->FilterNode($oNode);
2191					}
2192				}
2193			}
2194
2195			$oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down'));
2196			$oGraph->InitFromGraphviz();
2197			if ($aPositions != null)
2198			{
2199				$oGraph->UpdatePositions($aPositions);
2200			}
2201			$oPage->add($oGraph->GetAsJSON($sContextKey));
2202			$oPage->SetContentType('application/json');
2203			break;
2204
2205		case 'relation_groups':
2206			$aGroups = utils::ReadParam('groups');
2207			$iBlock = 1; // Zero is not a valid blockid
2208			foreach($aGroups as $idx => $aDefinition)
2209			{
2210				$sListClass = $aDefinition['class'];
2211				$oSearch = new DBObjectSearch($sListClass);
2212				$oSearch->AddCondition('id', $aDefinition['keys'], 'IN');
2213				$oSearch->SetShowObsoleteData(utils::ShowObsoleteData());
2214				$oPage->add("<h1>".Dict::Format('UI:RelationGroupNumber_N', (1 + $idx))."</h1>\n");
2215				$oPage->add("<div id=\"relation_group_$idx\" class=\"page_header\">\n");
2216				$oPage->add("<h2>".MetaModel::GetClassIcon($sListClass)."&nbsp;<span class=\"hilite\">".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aDefinition['keys']), Metamodel::GetName($sListClass))."</h2>\n");
2217				$oPage->add("</div>\n");
2218				$oBlock = new DisplayBlock($oSearch, 'list');
2219				$oBlock->Display($oPage, 'group_'.$iBlock++);
2220				$oPage->p('&nbsp;'); // Some space ?
2221			}
2222			break;
2223
2224		case 'relation_lists':
2225			$aLists = utils::ReadParam('lists');
2226			$iBlock = 1; // Zero is not a valid blockid
2227			foreach($aLists as $sListClass => $aKeys)
2228			{
2229				$oSearch = new DBObjectSearch($sListClass);
2230				$oSearch->AddCondition('id', $aKeys, 'IN');
2231				$oSearch->SetShowObsoleteData(utils::ShowObsoleteData());
2232				$oPage->add("<div class=\"page_header\">\n");
2233				$oPage->add("<h2>".MetaModel::GetClassIcon($sListClass)."&nbsp;<span class=\"hilite\">".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aKeys), Metamodel::GetName($sListClass))."</h2>\n");
2234				$oPage->add("</div>\n");
2235				$oBlock = new DisplayBlock($oSearch, 'list');
2236				$oBlock->Display($oPage, 'list_'.$iBlock++, array('table_id' => 'ImpactAnalysis_'.$sListClass));
2237				$oPage->p('&nbsp;'); // Some space ?
2238			}
2239			break;
2240
2241		case 'ticket_impact':
2242			require_once(APPROOT.'core/simplegraph.class.inc.php');
2243			require_once(APPROOT.'core/relationgraph.class.inc.php');
2244			require_once(APPROOT.'core/displayablegraph.class.inc.php');
2245			$sRelation = utils::ReadParam('relation', 'impacts');
2246			$sDirection = utils::ReadParam('direction', 'down');
2247			$iGroupingThreshold = utils::ReadParam('g', 5);
2248			$sClass = utils::ReadParam('class', '', false, 'class');
2249			$sAttCode = utils::ReadParam('attcode', 'functionalcis_list');
2250			$sImpactAttCode = utils::ReadParam('impact_attcode', 'impact_code');
2251			$sImpactAttCodeValue = utils::ReadParam('impact_attcode_value', 'manual');
2252			$iId = (int)utils::ReadParam('id', 0, false, 'integer');
2253
2254			// Get the list of source objects
2255			$oTicket = MetaModel::GetObject($sClass, $iId);
2256			$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
2257			$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
2258			$oExtKeyToRemote = MetaModel::GetAttributeDef($oAttDef->GetLinkedClass(), $sExtKeyToRemote);
2259			$sRemoteClass = $oExtKeyToRemote->GetTargetClass();
2260			$oSet = $oTicket->Get($sAttCode);
2261			$aSourceObjects = array();
2262			$aExcludedObjects = array();
2263			while ($oLnk = $oSet->Fetch())
2264			{
2265				if ($oLnk->Get($sImpactAttCode) == 'manual')
2266				{
2267					$aSourceObjects[] = MetaModel::GetObject($sRemoteClass, $oLnk->Get($sExtKeyToRemote));
2268				}
2269				if ($oLnk->Get($sImpactAttCode) == 'not_impacted')
2270				{
2271					$aExcludedObjects[] = MetaModel::GetObject($sRemoteClass, $oLnk->Get($sExtKeyToRemote));
2272				}
2273			}
2274
2275			// Compute the graph
2276			$iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth');
2277			if ($sDirection == 'up')
2278			{
2279				$oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth);
2280			}
2281			else
2282			{
2283				$oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, $aExcludedObjects);
2284			}
2285
2286			$aResults = $oRelGraph->GetObjectsByClass();
2287			$oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down'));
2288
2289			$sContextKey = 'itop-tickets/relation_context/'.$sClass.'/'.$sRelation.'/'.$sDirection;
2290			$oAppContext = new ApplicationContext();
2291			$oGraph->Display($oPage, $aResults, $sRelation, $oAppContext, $aExcludedObjects, $sClass, $iId, $sContextKey, array('this' => $oTicket));
2292			break;
2293
2294		case 'export_build':
2295			register_shutdown_function(function () {
2296				$aErr = error_get_last();
2297				if (($aErr !== null) && ($aErr['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR)))
2298				{
2299					ob_end_clean();
2300					echo json_encode(array('code' => 'error', 'percentage' => 100, 'message' => Dict::Format('UI:Error_Details', $aErr['message'])));
2301				}
2302			});
2303			try
2304			{
2305				$token = utils::ReadParam('token', null);
2306				$sTokenForDisplay = utils::HtmlEntities($token);
2307				$aResult = array( // Fallback error, just in case
2308					'code' => 'error',
2309					'percentage' => 100,
2310					'message' => "Export not found for token: '$sTokenForDisplay'",
2311				);
2312				$data = '';
2313				if ($token === null)
2314				{
2315					$sFormat = utils::ReadParam('format', '');
2316					$sExpression = utils::ReadParam('expression', null, false, 'raw_data');
2317					$iQueryId = utils::ReadParam('query', null);
2318					if ($sExpression === null)
2319					{
2320						$oQuerySearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', array('query_id' => $iQueryId));
2321						$oQuerySearch->UpdateContextFromUser();
2322						$oQueries = new DBObjectSet($oQuerySearch);
2323						if ($oQueries->Count() > 0)
2324						{
2325							$oQuery = $oQueries->Fetch();
2326							$sExpression = $oQuery->Get('oql');
2327						}
2328						else
2329						{
2330							$aResult = array('code' => 'error', 'percentage' => 100, 'message' => "Invalid query phrasebook identifier: '$iQueryId'");
2331						}
2332					}
2333					if ($sExpression !== null)
2334					{
2335						$oSearch = DBObjectSearch::FromOQL($sExpression);
2336						$oSearch->UpdateContextFromUser();
2337						$oExporter = BulkExport::FindExporter($sFormat, $oSearch);
2338						$oExporter->SetObjectList($oSearch);
2339						$oExporter->SetFormat($sFormat);
2340						$oExporter->SetChunkSize(EXPORTER_DEFAULT_CHUNK_SIZE);
2341						$oExporter->ReadParameters();
2342					}
2343
2344					// First pass, generate the headers
2345					$data .= $oExporter->GetHeader();
2346				}
2347				else
2348				{
2349					$oExporter = BulkExport::FindExporterFromToken($token);
2350				}
2351
2352				if ($oExporter)
2353				{
2354					$data .= $oExporter->GetNextChunk($aResult);
2355					if ($aResult['code'] != 'done')
2356					{
2357						$oExporter->AppendToTmpFile($data);
2358						$aResult['token'] = $oExporter->SaveState();
2359					}
2360					else
2361					{
2362						// Last pass
2363						$data .= $oExporter->GetFooter();
2364						$oExporter->AppendToTmpFile($data);
2365						$aResult['token'] = $oExporter->SaveState();
2366						if (substr($oExporter->GetMimeType(), 0, 5) == 'text/')
2367						{
2368							// Result must be encoded in UTF-8 to be passed as part of a JSON structure
2369							$sCharset = $oExporter->GetCharacterSet();
2370							if (strtoupper($sCharset) != 'UTF-8')
2371							{
2372								$aResult['text_result'] = iconv($sCharset, 'UTF-8', file_get_contents($oExporter->GetTmpFilePath()));
2373							}
2374							else
2375							{
2376								$aResult['text_result'] = file_get_contents($oExporter->GetTmpFilePath());
2377							}
2378							$aResult['mime_type'] = $oExporter->GetMimeType();
2379						}
2380						$aResult['message'] = Dict::Format('Core:BulkExport:ClickHereToDownload_FileName', $oExporter->GetDownloadFileName());
2381					}
2382				}
2383				$oPage->add(json_encode($aResult));
2384			} catch (BulkExportException $e)
2385			{
2386				$aResult = array('code' => 'error', 'percentage' => 100, 'message' => utils::HtmlEntities($e->GetLocalizedMessage()));
2387				$oPage->add(json_encode($aResult));
2388			} catch (Exception $e)
2389			{
2390				$aResult = array('code' => 'error', 'percentage' => 100, 'message' => utils::HtmlEntities($e->getMessage()));
2391				$oPage->add(json_encode($aResult));
2392			}
2393			break;
2394
2395		case 'export_download':
2396			$token = utils::ReadParam('token', null);
2397			if ($token !== null)
2398			{
2399				$oExporter = BulkExport::FindExporterFromToken($token);
2400				if ($oExporter)
2401				{
2402					$sMimeType = $oExporter->GetMimeType();
2403					if (substr($sMimeType, 0, 5) == 'text/')
2404					{
2405						$sMimeType .= ';charset='.strtolower($oExporter->GetCharacterSet());
2406					}
2407					$oPage->SetContentType($sMimeType);
2408					$oPage->SetContentDisposition('attachment', $oExporter->GetDownloadFileName());
2409					$oPage->add(file_get_contents($oExporter->GetTmpFilePath()));
2410				}
2411			}
2412			break;
2413
2414		case 'export_cancel':
2415			$token = utils::ReadParam('token', null);
2416			if ($token !== null)
2417			{
2418				$oExporter = BulkExport::FindExporterFromToken($token);
2419				if ($oExporter)
2420				{
2421					$oExporter->Cleanup();
2422				}
2423			}
2424			$aResult = array('code' => 'error', 'percentage' => 100, 'message' => Dict::S('Core:BulkExport:ExportCancelledByUser'));
2425			$oPage->add(json_encode($aResult));
2426			break;
2427
2428		case 'extend_lock':
2429			$sObjClass = utils::ReadParam('obj_class', '', false, 'class');
2430			$iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer');
2431			$sToken = utils::ReadParam('token', 0, false, 'raw_data');
2432			$aResult = iTopOwnershipLock::ExtendLock($sObjClass, $iObjKey, $sToken);
2433			if (!$aResult['status'])
2434			{
2435				if ($aResult['operation'] == 'lost')
2436				{
2437					$sName = $aResult['owner']->GetName();
2438					if ($aResult['owner']->Get('contactid') != 0)
2439					{
2440						$sName .= ' ('.$aResult['owner']->Get('contactid_friendlyname').')';
2441					}
2442					$aResult['message'] = Dict::Format('UI:CurrentObjectIsLockedBy_User', $sName);
2443					$aResult['popup_message'] = Dict::Format('UI:CurrentObjectIsLockedBy_User_Explanation', $sName);
2444				}
2445				else
2446				{
2447					if ($aResult['operation'] == 'expired')
2448					{
2449						$aResult['message'] = Dict::S('UI:CurrentObjectLockExpired');
2450						$aResult['popup_message'] = Dict::S('UI:CurrentObjectLockExpired_Explanation');
2451					}
2452				}
2453			}
2454			$oPage->add(json_encode($aResult));
2455			break;
2456
2457		case 'watchdog':
2458			$oPage->add('ok'); // Better for debugging...
2459			break;
2460
2461		case 'cke_img_upload':
2462			// Image uploaded via CKEditor
2463			$aResult = array(
2464				'uploaded' => 0,
2465				'fileName' => '',
2466				'url' => '',
2467				'icon' => '',
2468				'msg' => '',
2469				'att_id' => 0,
2470				'preview' => 'false',
2471			);
2472
2473			$sObjClass = stripslashes(utils::ReadParam('obj_class', '', false, 'class'));
2474			$sTempId = utils::ReadParam('temp_id', '', false, 'transaction_id');
2475			if (empty($sObjClass))
2476			{
2477				$aResult['error'] = "Missing argument 'obj_class'";
2478			}
2479			elseif (empty($sTempId))
2480			{
2481				$aResult['error'] = "Missing argument 'temp_id'";
2482			}
2483			else
2484			{
2485				try
2486				{
2487					$oDoc = utils::ReadPostedDocument('upload');
2488					if (InlineImage::IsImage($oDoc->GetMimeType()))
2489					{
2490						$aDimensions = null;
2491						$oDoc = InlineImage::ResizeImageToFit($oDoc, $aDimensions);
2492						$oAttachment = MetaModel::NewObject('InlineImage');
2493						$oAttachment->Set('expire', time() + MetaModel::GetConfig()->Get('draft_attachments_lifetime'));
2494						$oAttachment->Set('temp_id', $sTempId);
2495						$oAttachment->Set('item_class', $sObjClass);
2496						$oAttachment->SetDefaultOrgId();
2497						$oAttachment->Set('contents', $oDoc);
2498						$oAttachment->Set('secret', sprintf('%06x', mt_rand(0, 0xFFFFFF))); // something not easy to guess
2499						$iAttId = $oAttachment->DBInsert();
2500
2501						$aResult['uploaded'] = 1;
2502						$aResult['msg'] = htmlentities($oDoc->GetFileName(), ENT_QUOTES, 'UTF-8');
2503						$aResult['fileName'] = $oDoc->GetFileName();
2504						$aResult['url'] = utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL.$iAttId.'&s='.$oAttachment->Get('secret');
2505						if (is_array($aDimensions))
2506						{
2507							$aResult['width'] = $aDimensions['width'];
2508							$aResult['height'] = $aDimensions['height'];
2509						}
2510					}
2511					else
2512					{
2513						$aResult['error'] = $oDoc->GetFileName().' is not a valid image format.';
2514					}
2515				} catch (FileUploadException $e)
2516				{
2517					$aResult['error'] = $e->GetMessage();
2518				}
2519			}
2520			$oPage->add(json_encode($aResult));
2521			break;
2522
2523		case 'cke_upload_and_browse':
2524			$sTempId = utils::ReadParam('temp_id', '', false, 'transaction_id');
2525			$sObjClass = utils::ReadParam('obj_class', '', false, 'class');
2526			try
2527			{
2528				$oDoc = utils::ReadPostedDocument('upload');
2529				$sDocMimeType = $oDoc->GetMimeType();
2530				if (!InlineImage::IsImage($sDocMimeType))
2531				{
2532					LogErrorMessage('CKE : error when uploading image in ajax.render.php, not an image',
2533						array(
2534							'operation' => 'cke_upload_and_browse',
2535							'class' => $sObjClass,
2536							'ImgMimeType' => $sDocMimeType,
2537						));
2538				} else {
2539					$aDimensions = null;
2540					$oDoc = InlineImage::ResizeImageToFit($oDoc, $aDimensions);
2541					$oAttachment = MetaModel::NewObject('InlineImage');
2542					$oAttachment->Set('expire', time() + MetaModel::GetConfig()->Get('draft_attachments_lifetime'));
2543					$oAttachment->Set('temp_id', $sTempId);
2544					$oAttachment->Set('item_class', $sObjClass);
2545					$oAttachment->SetDefaultOrgId();
2546					$oAttachment->Set('contents', $oDoc);
2547					$oAttachment->Set('secret', sprintf('%06x', mt_rand(0, 0xFFFFFF))); // something not easy to guess
2548					$iAttId = $oAttachment->DBInsert();
2549				}
2550
2551			} catch (FileUploadException $e)
2552			{
2553				LogErrorMessage('CKE : error when uploading image in ajax.render.php, exception occured',
2554					array(
2555						'operation' => 'cke_upload_and_browse',
2556						'class' => $sObjClass,
2557						'exceptionMsg' => $e,
2558					));
2559			}
2560		// Fall though !! => browse
2561
2562		case 'cke_browse':
2563			$oPage = new NiceWebPage(Dict::S('UI:BrowseInlineImages'));
2564			$oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/magnific-popup.css');
2565			$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.magnific-popup.min.js');
2566			$sImgUrl = utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL;
2567
2568			$sTempId = utils::ReadParam('temp_id', '', false, 'transaction_id');
2569			$sClass = utils::ReadParam('obj_class', '', false, 'class');
2570			$iObjectId = utils::ReadParam('obj_key', 0, false, 'integer');
2571			$sCKEditorFuncNum = utils::ReadParam('CKEditorFuncNum', '');
2572
2573			$sPostUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?CKEditorFuncNum='.$sCKEditorFuncNum;
2574
2575			$oPage->add_style(
2576				<<<EOF
2577body {
2578	overflow: auto;
2579}
2580EOF
2581			);
2582			$sMaxUpload = InlineImage::GetMaxUpload();
2583			$sUploadLegend = Dict::S('UI:UploadInlineImageLegend');
2584			$sUploadLabel = Dict::S('UI:SelectInlineImageToUpload');
2585			$sAvailableImagesLegend = Dict::S('UI:AvailableInlineImagesLegend');
2586			$sInsertBtnLabel = Dict::S('UI:Button:Insert');
2587			$sNoInlineImage = Dict::S('UI:NoInlineImage');
2588			$oPage->add(
2589				<<<EOF
2590<div>
2591	<fieldset>
2592		<legend>$sUploadLegend</legend>
2593		<form method="post" id="upload_form" action="$sPostUrl" enctype="multipart/form-data">
2594			<input type="hidden" name="operation" value="cke_upload_and_browse">
2595			<input type="hidden" name="temp_id" value="$sTempId">
2596			<input type="hidden" name="obj_class" value="$sClass">
2597			<input type="hidden" name="obj_key" value="$iObjectId">
2598			$sUploadLabel <input id="upload_button" type="file" name="upload"> <span id="upload_status"> $sMaxUpload</span>
2599		</form>
2600	</fieldset>
2601</div>
2602EOF
2603			);
2604
2605			$oPage->add_script(
2606				<<<EOF
2607        // Helper function to get parameters from the query string.
2608        function getUrlParam( paramName ) {
2609            var reParam = new RegExp( '(?:[\?&]|&)' + paramName + '=([^&]+)', 'i' );
2610            var match = window.location.search.match( reParam );
2611
2612            return ( match && match.length > 1 ) ? match[1] : null;
2613        }
2614        // Simulate user action of selecting a file to be returned to CKEditor.
2615        function returnFileUrl(iAttId, sAltText, sSecret) {
2616
2617            var funcNum = getUrlParam( 'CKEditorFuncNum' );
2618            var fileUrl = '$sImgUrl'+iAttId+'&s='+sSecret;
2619            window.opener.CKEDITOR.tools.callFunction( funcNum, fileUrl, function() {
2620                // Get the reference to a dialog window.
2621                var dialog = this.getDialog();
2622                // Check if this is the Image Properties dialog window.
2623                if ( dialog.getName() == 'image' ) {
2624                    // Get the reference to a text field that stores the "alt" attribute.
2625                    var element = dialog.getContentElement( 'info', 'txtAlt' );
2626                    // Assign the new value.
2627                    if ( element )
2628                        element.setValue(sAltText);
2629                }
2630                // Return "false" to stop further execution. In such case CKEditor will ignore the second argument ("fileUrl")
2631                // and the "onSelect" function assigned to the button that called the file manager (if defined).
2632                // return false;
2633            } );
2634            window.close();
2635        }
2636EOF
2637			);
2638			$oPage->add_ready_script(
2639				<<<EOF
2640$('#upload_button').on('change', function() {
2641	$('#upload_status').html('<img src="../images/indicator.gif">');
2642	$('#upload_form').submit();
2643	$(this).prop('disabled', true);
2644});
2645$('.img-picker').magnificPopup({type: 'image', closeOnContentClick: true });
2646EOF
2647			);
2648			$sOQL = "SELECT InlineImage WHERE ((temp_id = :temp_id) OR (item_class = :obj_class AND item_id = :obj_id))";
2649			$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array(), array('temp_id' => $sTempId, 'obj_class' => $sClass, 'obj_id' => $iObjectId));
2650			$oPage->add("<div><fieldset><legend>$sAvailableImagesLegend</legend>");
2651
2652			if ($oSet->Count() == 0)
2653			{
2654				$oPage->add("<p style=\"text-align:center\">$sNoInlineImage</p>");
2655			}
2656			else
2657			{
2658				while ($oAttachment = $oSet->Fetch())
2659				{
2660					$oDoc = $oAttachment->Get('contents');
2661					if ($oDoc->GetMainMimeType() == 'image')
2662					{
2663						$sDocName = addslashes(htmlentities($oDoc->GetFileName(), ENT_QUOTES, 'UTF-8'));
2664						$iAttId = $oAttachment->GetKey();
2665						$sSecret = $oAttachment->Get('secret');
2666						$oPage->add("<div style=\"float:left;margin:1em;text-align:center;\"><img class=\"img-picker\" style=\"max-width:300px;cursor:zoom-in\" href=\"{$sImgUrl}{$iAttId}&s={$sSecret}\" alt=\"$sDocName\" title=\"$sDocName\" src=\"{$sImgUrl}{$iAttId}&s={$sSecret}\"><br/><button onclick=\"returnFileUrl($iAttId, '$sDocName', '$sSecret')\">$sInsertBtnLabel</button></div>");
2667					}
2668				}
2669			}
2670			$oPage->add("</fieldset></div>");
2671			break;
2672
2673		case 'custom_fields_update':
2674			$oPage->SetContentType('application/json');
2675			$sAttCode = utils::ReadParam('attcode', '');
2676			$aRequestedFields = utils::ReadParam('requested_fields', array());
2677			$sRequestedFieldsFormPath = utils::ReadParam('form_path', '');
2678			$sJson = utils::ReadParam('json_obj', '', false, 'raw_data');
2679
2680			$aResult = array();
2681
2682			try
2683			{
2684				$oWizardHelper = WizardHelper::FromJSON($sJson);
2685				$oObj = $oWizardHelper->GetTargetObject();
2686
2687				$oOrmCustomFieldValue = $oObj->Get($sAttCode);
2688				$oForm = $oOrmCustomFieldValue->GetForm();
2689				$oSubForm = $oForm->FindSubForm($sRequestedFieldsFormPath);
2690				$oRenderer = new \Combodo\iTop\Renderer\Console\ConsoleFormRenderer($oSubForm);
2691				$aRenderRes = $oRenderer->Render($aRequestedFields);
2692
2693				$aResult['form']['updated_fields'] = $aRenderRes;
2694			} catch (Exception $e)
2695			{
2696				$aResult['error'] = $e->getMessage();
2697			}
2698			$oPage->add(json_encode($aResult));
2699			break;
2700
2701		default:
2702			$oPage->p("Invalid query.");
2703	}
2704
2705	$oPage->output();
2706} catch (Exception $e)
2707{
2708	// note: transform to cope with XSS attacks
2709	echo htmlentities($e->GetMessage(), ENT_QUOTES, 'utf-8');
2710	IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString());
2711}
2712