objectcontroller.class.inc.php 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357
  1. <?php
  2. // Copyright (C) 2010-2015 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. namespace Combodo\iTop\Portal\Controller;
  19. use \Silex\Application;
  20. use \Symfony\Component\HttpFoundation\Request;
  21. use \Symfony\Component\HttpFoundation\Response;
  22. use \Symfony\Component\HttpFoundation\RedirectResponse;
  23. use \Symfony\Component\HttpKernel\HttpKernelInterface;
  24. use \Exception;
  25. use \FileUploadException;
  26. use \utils;
  27. use \Dict;
  28. use \IssueLog;
  29. use \MetaModel;
  30. use \DBSearch;
  31. use \DBObjectSearch;
  32. use \BinaryExpression;
  33. use \FieldExpression;
  34. use \VariableExpression;
  35. use \ListExpression;
  36. use \ScalarExpression;
  37. use \DBObjectSet;
  38. use \cmdbAbstractObject;
  39. use \UserRights;
  40. use \Combodo\iTop\Portal\Helper\ApplicationHelper;
  41. use \Combodo\iTop\Portal\Helper\SecurityHelper;
  42. use \Combodo\iTop\Portal\Helper\ContextManipulatorHelper;
  43. use \Combodo\iTop\Portal\Form\ObjectFormManager;
  44. use \Combodo\iTop\Renderer\Bootstrap\BsFormRenderer;
  45. /**
  46. * Controller to handle basic view / edit / create of cmdbAbstractObject
  47. */
  48. class ObjectController extends AbstractController
  49. {
  50. const ENUM_MODE_VIEW = 'view';
  51. const ENUM_MODE_EDIT = 'edit';
  52. const ENUM_MODE_CREATE = 'create';
  53. const DEFAULT_COUNT_PER_PAGE_LIST = 10;
  54. /**
  55. * Displays an cmdbAbstractObject if the connected user is allowed to.
  56. *
  57. * @param Request $oRequest
  58. * @param Application $oApp
  59. * @param string $sObjectClass (Class must be instance of cmdbAbstractObject)
  60. * @param string $sObjectId
  61. * @return Response
  62. */
  63. public function ViewAction(Request $oRequest, Application $oApp, $sObjectClass, $sObjectId)
  64. {
  65. // Checking parameters
  66. if ($sObjectClass === '' || $sObjectId === '')
  67. {
  68. IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : sObjectClass and sObjectId expected, "' . $sObjectClass . '" and "' . $sObjectId . '" given.');
  69. $oApp->abort(500, Dict::Format('UI:Error:2ParametersMissing', 'class', 'id'));
  70. }
  71. // Checking security layers
  72. if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sObjectClass, $sObjectId))
  73. {
  74. IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to read ' . $sObjectClass . '::' . $sObjectId . ' object.');
  75. $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
  76. }
  77. // Retrieving object
  78. $oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */);
  79. if ($oObject === null)
  80. {
  81. // We should never be there as the secuirty helper makes sure that the object exists, but just in case.
  82. IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : Could not load object ' . $sObjectClass . '::' . $sObjectId . '.');
  83. $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
  84. }
  85. $aData = array('sMode' => 'view');
  86. $aData['form'] = $this->HandleForm($oRequest, $oApp, $aData['sMode'], $sObjectClass, $sObjectId);
  87. $aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:View:Title', MetaModel::GetName($sObjectClass), $oObject->GetName());
  88. // Add an edit button if user is allowed
  89. if (SecurityHelper::IsActionAllowed($oApp, UR_ACTION_MODIFY, $sObjectClass, $sObjectId))
  90. {
  91. $aData['form']['buttons']['links'][] = array(
  92. 'label' => Dict::S('UI:Menu:Modify'),
  93. 'url' => $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId))
  94. );
  95. }
  96. // Preparing response
  97. if ($oRequest->isXmlHttpRequest())
  98. {
  99. // We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
  100. if ($oRequest->request->get('operation') === null)
  101. {
  102. $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
  103. }
  104. else
  105. {
  106. $oResponse = $oApp->json($aData);
  107. }
  108. }
  109. else
  110. {
  111. // Adding brick if it was passed
  112. $sBrickId = $oRequest->get('sBrickId');
  113. if ($sBrickId !== null)
  114. {
  115. $oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
  116. if ($oBrick !== null)
  117. {
  118. $aData['oBrick'] = $oBrick;
  119. }
  120. }
  121. $aData['sPageTitle'] = $aData['form']['title'];
  122. $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
  123. }
  124. return $oResponse;
  125. }
  126. public function EditAction(Request $oRequest, Application $oApp, $sObjectClass, $sObjectId)
  127. {
  128. // Checking parameters
  129. if ($sObjectClass === '' || $sObjectId === '')
  130. {
  131. IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : sObjectClass and sObjectId expected, "' . $sObjectClass . '" and "' . $sObjectId . '" given.');
  132. $oApp->abort(500, Dict::Format('UI:Error:2ParametersMissing', 'class', 'id'));
  133. }
  134. // Checking security layers
  135. // Warning : This is a dirty quick fix to allow editing its own contact information
  136. $bAllowWrite = ($sObjectClass === 'Person' && $sObjectId == UserRights::GetContactId());
  137. if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_MODIFY, $sObjectClass, $sObjectId) && !$bAllowWrite)
  138. {
  139. IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to modify ' . $sObjectClass . '::' . $sObjectId . ' object.');
  140. $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
  141. }
  142. // Retrieving object
  143. $oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */);
  144. if ($oObject === null)
  145. {
  146. // We should never be there as the secuirty helper makes sure that the object exists, but just in case.
  147. IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : Could not load object ' . $sObjectClass . '::' . $sObjectId . '.');
  148. $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
  149. }
  150. $aData = array('sMode' => 'edit');
  151. $aData['form'] = $this->HandleForm($oRequest, $oApp, $aData['sMode'], $sObjectClass, $sObjectId);
  152. $aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:Edit:Title', MetaModel::GetName($sObjectClass), $aData['form']['object_name']);
  153. // Preparing response
  154. if ($oRequest->isXmlHttpRequest())
  155. {
  156. // We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
  157. if ($oRequest->request->get('operation') === null)
  158. {
  159. $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
  160. }
  161. else
  162. {
  163. $oResponse = $oApp->json($aData);
  164. }
  165. }
  166. else
  167. {
  168. // Adding brick if it was passed
  169. $sBrickId = $oRequest->get('sBrickId');
  170. if ($sBrickId !== null)
  171. {
  172. $oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
  173. if ($oBrick !== null)
  174. {
  175. $aData['oBrick'] = $oBrick;
  176. }
  177. }
  178. $aData['sPageTitle'] = $aData['form']['title'];
  179. $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
  180. }
  181. return $oResponse;
  182. }
  183. /**
  184. * Creates an cmdbAbstractObject of the $sObjectClass
  185. *
  186. * @param Request $oRequest
  187. * @param Application $oApp
  188. * @param string $sObjectClass
  189. * @return Response
  190. */
  191. public function CreateAction(Request $oRequest, Application $oApp, $sObjectClass)
  192. {
  193. // Checking security layers
  194. if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_CREATE, $sObjectClass))
  195. {
  196. IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to create ' . $sObjectClass . ' object.');
  197. $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
  198. }
  199. $aData = array('sMode' => 'create');
  200. $aData['form'] = $this->HandleForm($oRequest, $oApp, $aData['sMode'], $sObjectClass);
  201. $aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:Create:Title', MetaModel::GetName($sObjectClass));
  202. // Preparing response
  203. if ($oRequest->isXmlHttpRequest())
  204. {
  205. // We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
  206. if ($oRequest->request->get('operation') === null)
  207. {
  208. $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
  209. }
  210. else
  211. {
  212. $oResponse = $oApp->json($aData);
  213. }
  214. }
  215. else
  216. {
  217. // Adding brick if it was passed
  218. $sBrickId = $oRequest->get('sBrickId');
  219. if ($sBrickId !== null)
  220. {
  221. $oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
  222. if ($oBrick !== null)
  223. {
  224. $aData['oBrick'] = $oBrick;
  225. }
  226. }
  227. $aData['sPageTitle'] = $aData['form']['title'];
  228. $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
  229. }
  230. return $oResponse;
  231. }
  232. /**
  233. * Creates an cmdbAbstractObject of a class determined by the method encoded in $sEncodedMethodName.
  234. * This method use an origin DBObject in order to determine the created cmdbAbstractObject.
  235. *
  236. * @param Request $oRequest
  237. * @param Application $oApp
  238. * @param string $sObjectClass Class of the origin object
  239. * @param string $sObjectId ID of the origin object
  240. * @param string $sEncodedMethodName Base64 encoded factory method name
  241. * @return Response
  242. */
  243. public function CreateFromFactoryAction(Request $oRequest, Application $oApp, $sObjectClass, $sObjectId, $sEncodedMethodName)
  244. {
  245. $sMethodName = base64_decode($sEncodedMethodName);
  246. // Checking that the factory method is valid
  247. if (!is_callable($sMethodName))
  248. {
  249. IssueLog::Error(__METHOD__ . ' at line ' . __LINE__ . ' : Invalid factory method "' . $sMethodName . '" used when creating an object.');
  250. $oApp->abort(500, 'Invalid factory method "' . $sMethodName . '" used when creating an object');
  251. }
  252. // Retrieving origin object
  253. $oOriginObject = MetaModel::GetObject($sObjectClass, $sObjectId);
  254. // Retrieving target object (We check if the method is a simple function or if it's part of a class in which case only static function are supported)
  255. if (!strpos($sMethodName, '::'))
  256. {
  257. $sTargetObject = $sMethodName($oOriginObject);
  258. }
  259. else
  260. {
  261. $aMethodNameParts = explode('::', $sMethodName);
  262. $sTargetObject = $aMethodNameParts[0]::$aMethodNameParts[1]($oOriginObject);
  263. }
  264. // Preparing redirection
  265. // - Route
  266. $aRouteParams = array(
  267. 'sObjectClass' => get_class($sTargetObject)
  268. );
  269. $sRedirectRoute = $oApp['url_generator']->generate('p_object_create', $aRouteParams);
  270. // - Request
  271. $oSubRequest = Request::create($sRedirectRoute, 'GET', $oRequest->query->all(), $oRequest->cookies->all(), array(), $oRequest->server->all());
  272. return $oApp->handle($oSubRequest, HttpKernelInterface::SUB_REQUEST, true);
  273. }
  274. /**
  275. * Applies a stimulus $sStimulus on an cmdbAbstractObject
  276. *
  277. * @param Request $oRequest
  278. * @param Application $oApp
  279. * @param string $sObjectClass
  280. * @param string $sObjectId
  281. * @param string $sStimulusCode
  282. * @return Response
  283. */
  284. public function ApplyStimulusAction(Request $oRequest, Application $oApp, $sObjectClass, $sObjectId, $sStimulusCode)
  285. {
  286. // Checking parameters
  287. if ($sObjectClass === '' || $sObjectId === '' || $sStimulusCode === '')
  288. {
  289. IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : sObjectClass, sObjectId and $sStimulusCode expected, "' . $sObjectClass . '", "' . $sObjectId . '" and "' . $sStimulusCode . '" given.');
  290. $oApp->abort(500, Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus'));
  291. }
  292. // Checking security layers
  293. // TODO : This should call the stimulus check in the security helper
  294. // if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_MODIFY, $sObjectClass, $sObjectId))
  295. // {
  296. // $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
  297. // }
  298. // Retrieving object
  299. $oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */);
  300. if ($oObject === null)
  301. {
  302. // We should never be there as the secuirty helper makes sure that the object exists, but just in case.
  303. IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : Could not load object ' . $sObjectClass . '::' . $sObjectId . '.');
  304. $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
  305. }
  306. // Preparing a dedicated form for the stimulus application
  307. $aFormProperties = array(
  308. 'id' => 'apply-stimulus',
  309. 'type' => 'static',
  310. 'fields' => array(),
  311. 'layout' => null
  312. );
  313. // Checking which fields need to be prompt
  314. $aTransitions = MetaModel::EnumTransitions($sObjectClass, $oObject->GetState());
  315. $aTargetStates = MetaModel::EnumStates($sObjectClass);
  316. $aTargetState = $aTargetStates[$aTransitions[$sStimulusCode]['target_state']];
  317. $aExpectedAttributes = $aTargetState['attribute_list'];
  318. foreach ($aExpectedAttributes as $sAttCode => $iFlags)
  319. {
  320. if (($iFlags & (OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) ||
  321. (($iFlags & OPT_ATT_MANDATORY) && ($oObject->Get($sAttCode) == '')))
  322. {
  323. $aFormProperties['fields'][$sAttCode] = array();
  324. // Settings flags for the field
  325. if ($iFlags & OPT_ATT_MUSTCHANGE)
  326. $aFormProperties['fields'][$sAttCode]['must_change'] = true;
  327. if ($iFlags & OPT_ATT_MUSTPROMPT)
  328. $aFormProperties['fields'][$sAttCode]['must_prompt'] = true;
  329. if (($iFlags & OPT_ATT_MANDATORY) && ($oObject->Get($sAttCode) == ''))
  330. $aFormProperties['fields'][$sAttCode]['mandatory'] = true;
  331. }
  332. }
  333. // Adding target_state to current_values
  334. $oRequest->request->set('apply_stimulus', array('code' => $sStimulusCode));
  335. $aData = array('sMode' => 'apply_stimulus');
  336. $aData['form'] = $this->HandleForm($oRequest, $oApp, $aData['sMode'], $sObjectClass, $sObjectId, $aFormProperties);
  337. $aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:Stimulus:Title');
  338. $aData['form']['validation']['redirection'] = array(
  339. 'url' => $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId))
  340. );
  341. // Preparing response
  342. if ($oRequest->isXmlHttpRequest())
  343. {
  344. // We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
  345. if ($oRequest->request->get('operation') === null)
  346. {
  347. $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
  348. }
  349. else
  350. {
  351. $oResponse = $oApp->json($aData);
  352. }
  353. }
  354. else
  355. {
  356. $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
  357. }
  358. return $oResponse;
  359. }
  360. public static function HandleForm(Request $oRequest, Application $oApp, $sMode, $sObjectClass, $sObjectId = null, $aFormProperties = null)
  361. {
  362. $aFormData = array();
  363. $oRequestParams = $oRequest->request;
  364. $sOperation = $oRequestParams->get('operation');
  365. $bModal = ($oRequest->isXmlHttpRequest() && ($oRequest->request->get('operation') === null) );
  366. // - Retrieve form properties
  367. if ($aFormProperties === null)
  368. {
  369. $aFormProperties = ApplicationHelper::GetLoadedFormFromClass($oApp, $sObjectClass, $sMode);
  370. }
  371. // - Create and
  372. if ($sOperation === null)
  373. {
  374. // Retrieving action rules
  375. //
  376. // Note : The action rules must be a base64-encoded JSON object, this is just so users are tempted to changes values.
  377. // But it would not be a security issue as it only presets values in the form.
  378. $sActionRulesToken = $oRequest->get('ar_token');
  379. $aActionRules = ($sActionRulesToken !== null) ? ContextManipulatorHelper::DecodeRulesToken($sActionRulesToken) : array();
  380. // Preparing object
  381. if ($sObjectId === null)
  382. {
  383. // Create new UserRequest
  384. $oObject = MetaModel::NewObject($sObjectClass);
  385. // Retrieve action rules information to auto-fill the form if available
  386. // Preparing object
  387. $oApp['context_manipulator']->PrepareObject($aActionRules, $oObject);
  388. }
  389. else
  390. {
  391. $oObject = MetaModel::GetObject($sObjectClass, $sObjectId);
  392. }
  393. // Preparing transitions only if we are currently going through one
  394. $aFormData['buttons'] = array(
  395. 'transitions' => array()
  396. );
  397. if ($sMode !== 'apply_stimulus')
  398. {
  399. $oSetToCheckRights = DBObjectSet::FromObject($oObject);
  400. $aStimuli = Metamodel::EnumStimuli($sObjectClass);
  401. foreach ($oObject->EnumTransitions() as $sStimulusCode => $aTransitionDef)
  402. {
  403. $iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sObjectClass, $sStimulusCode, $oSetToCheckRights) : UR_ALLOWED_NO;
  404. // Careful, $iAction is an integer whereas UR_ALLOWED_YES is a boolean, therefore we can't use a '===' operator.
  405. if ($iActionAllowed == UR_ALLOWED_YES)
  406. {
  407. $aFormData['buttons']['transitions'][$sStimulusCode] = $aStimuli[$sStimulusCode]->GetLabel();
  408. }
  409. }
  410. }
  411. // Preparing callback urls
  412. $aCallbackUrls = $oApp['context_manipulator']->GetCallbackUrls($oApp, $aActionRules, $oObject, $bModal);
  413. $aFormData['submit_callback'] = $aCallbackUrls['submit'];
  414. $aFormData['cancel_callback'] = $aCallbackUrls['cancel'];
  415. // Preparing renderer
  416. // Note : We might need to distinguish form & renderer endpoints
  417. if (in_array($sMode, array('create', 'edit', 'view')))
  418. {
  419. $sFormEndpoint = $oApp['url_generator']->generate('p_object_' . $sMode, array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId));
  420. }
  421. else
  422. {
  423. $sFormEndpoint = $_SERVER['REQUEST_URI'];
  424. }
  425. $oFormRenderer = new BsFormRenderer();
  426. $oFormRenderer->SetEndpoint($sFormEndpoint);
  427. $oFormManager = new ObjectFormManager();
  428. $oFormManager->SetApplication($oApp)
  429. ->SetObject($oObject)
  430. ->SetMode($sMode)
  431. ->SetActionRulesToken($sActionRulesToken)
  432. ->SetRenderer($oFormRenderer)
  433. ->SetFormProperties($aFormProperties)
  434. ->Build();
  435. // Check the number of editable fields
  436. $aFormData['editable_fields_count'] = $oFormManager->GetForm()->GetEditableFieldCount();
  437. }
  438. else
  439. {
  440. // Update / Submit / Cancel
  441. $sFormManagerClass = $oRequestParams->get('formmanager_class');
  442. $sFormManagerData = $oRequestParams->get('formmanager_data');
  443. if ($sFormManagerClass === null || $sFormManagerData === null)
  444. {
  445. IssueLog::Error(__METHOD__ . ' at line ' . __LINE__ . ' : Parameters formmanager_class and formamanager_data must be defined.');
  446. $oApp->abort(500, 'Parameters formmanager_class and formmanager_data must be defined.');
  447. }
  448. $oFormManager = $sFormManagerClass::FromJSON($sFormManagerData);
  449. $oFormManager->SetApplication($oApp);
  450. // Applying action rules if present
  451. if (($oFormManager->GetActionRulesToken() !== null) && ($oFormManager->GetActionRulesToken() !== ''))
  452. {
  453. $aActionRules = ContextManipulatorHelper::DecodeRulesToken($oFormManager->GetActionRulesToken());
  454. $oObj = $oFormManager->GetObject();
  455. $oApp['context_manipulator']->PrepareObject($aActionRules, $oObj);
  456. $oFormManager->SetObject($oObj);
  457. }
  458. switch ($sOperation)
  459. {
  460. case 'submit':
  461. // Applying modification to object
  462. $aFormData['validation'] = $oFormManager->OnSubmit(array('currentValues' => $oRequestParams->get('current_values'), 'attachmentIds' => $oRequest->get('attachment_ids'), 'formProperties' => $aFormProperties, 'applyStimulus' => $oRequestParams->get('apply_stimulus')));
  463. if ($aFormData['validation']['valid'] === true)
  464. {
  465. // Note : We don't use $sObjectId there as it can be null if we are creating a new one. Instead we use the id from the created object once it has been seralized
  466. // Check if stimulus has to be applied
  467. $sStimulusCode = ($oRequestParams->get('stimulus_code') !== null && $oRequestParams->get('stimulus_code') !== '') ? $oRequestParams->get('stimulus_code') : null;
  468. if ($sStimulusCode !== null)
  469. {
  470. $aFormData['validation']['redirection'] = array(
  471. 'url' => $oApp['url_generator']->generate('p_object_apply_stimulus', array('sObjectClass' => $sObjectClass, 'sObjectId' => $oFormManager->GetObject()->GetKey(), 'sStimulusCode' => $sStimulusCode)),
  472. 'ajax' => true
  473. );
  474. }
  475. // Otherwise, we show the object if there is no default
  476. else
  477. {
  478. // $aFormData['validation']['redirection'] = array(
  479. // 'alternative_url' => $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $oFormManager->GetObject()->GetKey()))
  480. // );
  481. }
  482. }
  483. break;
  484. case 'update':
  485. $oFormManager->OnUpdate(array('currentValues' => $oRequestParams->get('current_values'), 'formProperties' => $aFormProperties));
  486. break;
  487. case 'cancel':
  488. $oFormManager->OnCancel();
  489. break;
  490. }
  491. }
  492. // Preparing field_set data
  493. $aFieldSetData = array(
  494. //'fields_list' => $oFormManager->GetRenderer()->Render(), // GLA : This should be done just after in the if statement.
  495. 'fields_impacts' => $oFormManager->GetForm()->GetFieldsImpacts(),
  496. 'form_path' => $oFormManager->GetForm()->GetId()
  497. );
  498. // Preparing fields list regarding the operation
  499. if ($sOperation === 'update')
  500. {
  501. $aRequestedFields = $oRequestParams->get('requested_fields');
  502. $sFormPath = $oRequestParams->get('form_path');
  503. // Checking if the update was on a subform, if so we need to make the rendering for that part only
  504. if ($sFormPath !== null && $sFormPath !== $oFormManager->GetForm()->GetId())
  505. {
  506. $oSubForm = $oFormManager->GetForm()->FindSubForm($sFormPath);
  507. $oSubFormRenderer = new BsFormRenderer($oSubForm);
  508. $oSubFormRenderer->SetEndpoint($oFormManager->GetRenderer()->GetEndpoint());
  509. $aFormData['updated_fields'] = $oSubFormRenderer->Render($aRequestedFields);
  510. }
  511. else
  512. {
  513. $aFormData['updated_fields'] = $oFormManager->GetRenderer()->Render($aRequestedFields);
  514. }
  515. }
  516. else
  517. {
  518. $aFieldSetData['fields_list'] = $oFormManager->GetRenderer()->Render();
  519. }
  520. // Preparing form data
  521. $aFormData['id'] = $oFormManager->GetForm()->GetId();
  522. $aFormData['transaction_id'] = $oFormManager->GetForm()->GetTransactionId();
  523. $aFormData['formmanager_class'] = $oFormManager->GetClass();
  524. $aFormData['formmanager_data'] = $oFormManager->ToJSON();
  525. $aFormData['renderer'] = $oFormManager->GetRenderer();
  526. $aFormData['object_name'] = $oFormManager->GetObject()->GetName();
  527. $aFormData['fieldset'] = $aFieldSetData;
  528. return $aFormData;
  529. }
  530. /**
  531. * Handles the autocomplete search
  532. *
  533. * @param Request $oRequest
  534. * @param Application $oApp
  535. * @param string $sTargetAttCode Attribute code of the host object pointing to the Object class to search
  536. * @param string $sHostObjectClass Class name of the host object
  537. * @param string $sHostObjectId Id of the host object
  538. * @return Response
  539. */
  540. public function SearchAutocompleteAction(Request $oRequest, Application $oApp, $sTargetAttCode, $sHostObjectClass, $sHostObjectId = null)
  541. {
  542. $aData = array(
  543. 'results' => array(
  544. 'count' => 0,
  545. 'items' => array()
  546. )
  547. );
  548. // Parsing parameters from request payload
  549. parse_str($oRequest->getContent(), $aRequestContent);
  550. // Checking parameters
  551. if (!isset($aRequestContent['sQuery']))
  552. {
  553. IssueLog::Error(__METHOD__ . ' at line ' . __LINE__ . ' : Parameter sQuery missing.');
  554. $oApp->abort(500, Dict::Format('UI:Error:ParameterMissing', 'sQuery'));
  555. }
  556. // Retrieving parameters
  557. $sQuery = $aRequestContent['sQuery'];
  558. // Checking security layers
  559. if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sHostObjectClass, $sHostObjectId))
  560. {
  561. IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : Could not load object ' . $sHostObjectClass . '::' . $sHostObjectId . '.');
  562. $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
  563. }
  564. // Retrieving host object for future DBSearch parameters
  565. if ($sHostObjectId !== null)
  566. {
  567. $oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId);
  568. }
  569. else
  570. {
  571. $oHostObject = MetaModel::NewObject($sHostObjectClass);
  572. // Retrieving action rules
  573. //
  574. // Note : The action rules must be a base64-encoded JSON object, this is just so users are tempted to changes values.
  575. // But it would not be a security issue as it only presets values in the form.
  576. $sActionRulesToken = $oRequest->get('ar_token');
  577. $aActionRules = ($sActionRulesToken !== null) ? ContextManipulatorHelper::DecodeRulesToken($sActionRulesToken) : array();
  578. // Preparing object
  579. $oApp['context_manipulator']->PrepareObject($aActionRules, $oHostObject);
  580. }
  581. // Updating host object with form data / values
  582. $sFormManagerClass = $aRequestContent['formmanager_class'];
  583. $sFormManagerData = $aRequestContent['formmanager_data'];
  584. if ($sFormManagerClass !== null && $sFormManagerData !== null)
  585. {
  586. $oFormManager = $sFormManagerClass::FromJSON($sFormManagerData);
  587. $oFormManager->SetApplication($oApp);
  588. $oFormManager->SetObject($oHostObject);
  589. // Applying action rules if present
  590. if (($oFormManager->GetActionRulesToken() !== null) && ($oFormManager->GetActionRulesToken() !== ''))
  591. {
  592. $aActionRules = ContextManipulatorHelper::DecodeRulesToken($oFormManager->GetActionRulesToken());
  593. $oObj = $oFormManager->GetObject();
  594. $oApp['context_manipulator']->PrepareObject($aActionRules, $oObj);
  595. $oFormManager->SetObject($oObj);
  596. }
  597. // Updating host object
  598. $oFormManager->OnUpdate(array('currentValues' => $aRequestContent['current_values']));
  599. $oHostObject = $oFormManager->GetObject();
  600. }
  601. // Building search query
  602. // - Retrieving target object class from attcode
  603. $oTargetAttDef = MetaModel::GetAttributeDef($sHostObjectClass, $sTargetAttCode);
  604. $sTargetObjectClass = $oTargetAttDef->GetTargetClass();
  605. // - Base query from meta model
  606. $oSearch = DBSearch::FromOQL($oTargetAttDef->GetValuesDef()->GetFilterExpression());
  607. // - Adding query condition
  608. $oSearch->AddConditionExpression(new BinaryExpression(new FieldExpression('friendlyname', $oSearch->GetClassAlias()), 'LIKE', new VariableExpression('ac_query')));
  609. // - Intersecting with scope constraints
  610. $oSearch = $oSearch->Intersect($oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sTargetObjectClass, UR_ACTION_READ));
  611. // Retrieving results
  612. // - Preparing object set
  613. $oSet = new DBObjectSet($oSearch, array(), array('this' => $oHostObject, 'ac_query' => '%' . $sQuery . '%'));
  614. $oSet->OptimizeColumnLoad(array($oSearch->GetClassAlias() => array('friendlyname')));
  615. // Note : This limit is also used in the field renderer by typeahead to determine how many suggestions to display
  616. $oSet->SetLimit($oTargetAttDef->GetMaximumComboLength()); // TODO : Is this the right limit value ? We might want to use another parameter
  617. // - Retrieving objects
  618. while ($oItem = $oSet->Fetch())
  619. {
  620. $aData['results']['items'][] = array('id' => $oItem->GetKey(), 'name' => html_entity_decode($oItem->GetName(), ENT_QUOTES, 'UTF-8'));
  621. $aData['results']['count'] ++;
  622. }
  623. // Preparing response
  624. if ($oRequest->isXmlHttpRequest())
  625. {
  626. $oResponse = $oApp->json($aData);
  627. }
  628. else
  629. {
  630. $oResponse = $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
  631. }
  632. return $oResponse;
  633. }
  634. /**
  635. * Handles the regular (table) search from an attribute
  636. *
  637. * @param Request $oRequest
  638. * @param Application $oApp
  639. * @param string $sTargetAttCode Attribute code of the host object pointing to the Object class to search
  640. * @param string $sHostObjectClass Class name of the host object
  641. * @param string $sHostObjectId Id of the host object
  642. * @return Response
  643. */
  644. public function SearchFromAttributeAction(Request $oRequest, Application $oApp, $sTargetAttCode, $sHostObjectClass, $sHostObjectId = null)
  645. {
  646. $aData = array(
  647. 'sMode' => 'search_regular',
  648. 'sTargetAttCode' => $sTargetAttCode,
  649. 'sHostObjectClass' => $sHostObjectClass,
  650. 'sHostObjectId' => $sHostObjectId,
  651. 'sActionRulesToken' => $oRequest->get('ar_token')
  652. );
  653. // Checking security layers
  654. if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sHostObjectClass, $sHostObjectId))
  655. {
  656. IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to read ' . $sHostObjectClass . '::' . $sHostObjectId . ' object.');
  657. $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
  658. }
  659. // Retrieving host object for future DBSearch parameters
  660. if ($sHostObjectId !== null)
  661. {
  662. $oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId);
  663. }
  664. else
  665. {
  666. $oHostObject = MetaModel::NewObject($sHostObjectClass);
  667. // Retrieving action rules
  668. //
  669. // Note : The action rules must be a base64-encoded JSON object, this is just so users are tempted to changes values.
  670. // But it would not be a security issue as it only presets values in the form.
  671. $aActionRules = ($aData['sActionRulesToken'] !== null) ? ContextManipulatorHelper::DecodeRulesToken($aData['sActionRulesToken']) : array();
  672. // Preparing object
  673. $oApp['context_manipulator']->PrepareObject($aActionRules, $oHostObject);
  674. }
  675. // Updating host object with form data / values
  676. $oRequestParams = $oRequest->request;
  677. $sFormManagerClass = $oRequestParams->get('formmanager_class');
  678. $sFormManagerData = $oRequestParams->get('formmanager_data');
  679. if ($sFormManagerClass !== null && $sFormManagerData !== null)
  680. {
  681. $oFormManager = $sFormManagerClass::FromJSON($sFormManagerData);
  682. $oFormManager->SetApplication($oApp);
  683. $oFormManager->SetObject($oHostObject);
  684. // Applying action rules if present
  685. if (($oFormManager->GetActionRulesToken() !== null) && ($oFormManager->GetActionRulesToken() !== ''))
  686. {
  687. $aActionRules = ContextManipulatorHelper::DecodeRulesToken($oFormManager->GetActionRulesToken());
  688. $oObj = $oFormManager->GetObject();
  689. $oApp['context_manipulator']->PrepareObject($aActionRules, $oObj);
  690. $oFormManager->SetObject($oObj);
  691. }
  692. // Updating host object
  693. $oFormManager->OnUpdate(array('currentValues' => $oRequestParams->get('current_values')));
  694. $oHostObject = $oFormManager->GetObject();
  695. }
  696. // Retrieving request parameters
  697. $iPageNumber = ($oRequest->get('iPageNumber') !== null) ? $oRequest->get('iPageNumber') : 1;
  698. $iCountPerPage = ($oRequest->get('iCountPerPage') !== null) ? $oRequest->get('iCountPerPage') : static::DEFAULT_COUNT_PER_PAGE_LIST;
  699. $bInitalPass = ($oRequest->get('draw') === null) ? true : false;
  700. $sQuery = $oRequest->get('sSearchValue');
  701. $sFormPath = $oRequest->get('sFormPath');
  702. $sFieldId = $oRequest->get('sFieldId');
  703. $aObjectIdsToIgnore = $oRequest->get('aObjectIdsToIgnore');
  704. // Building search query
  705. // - Retrieving target object class from attcode
  706. $oTargetAttDef = MetaModel::GetAttributeDef($sHostObjectClass, $sTargetAttCode);
  707. if ($oTargetAttDef->IsExternalKey())
  708. {
  709. $sTargetObjectClass = $oTargetAttDef->GetTargetClass();
  710. }
  711. elseif ($oTargetAttDef->IsLinkSet())
  712. {
  713. if (!$oTargetAttDef->IsIndirect())
  714. {
  715. $sTargetObjectClass = $oTargetAttDef->GetLinkedClass();
  716. }
  717. else
  718. {
  719. $oRemoteAttDef = MetaModel::GetAttributeDef($oTargetAttDef->GetLinkedClass(), $oTargetAttDef->GetExtKeyToRemote());
  720. $sTargetObjectClass = $oRemoteAttDef->GetTargetClass();
  721. }
  722. }
  723. else
  724. {
  725. throw new Exception('Search from attribute can only apply on AttributeExternalKey or AttributeLinkedSet objects, ' . get_class($oTargetAttDef) . ' given.');
  726. }
  727. // - Retrieving class attribute list
  728. $aAttCodes = MetaModel::FlattenZList(MetaModel::GetZListItems($sTargetObjectClass, 'list'));
  729. // - Adding friendlyname attribute to the list is not already in it
  730. $sTitleAttCode = MetaModel::GetFriendlyNameAttributeCode($sTargetObjectClass);
  731. if (!in_array($sTitleAttCode, $aAttCodes))
  732. {
  733. $aAttCodes = array_merge(array($sTitleAttCode), $aAttCodes);
  734. }
  735. // - Retrieving scope search
  736. $oScopeSearch = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sTargetObjectClass, UR_ACTION_READ);
  737. $aInternalParams = array();
  738. if ($oScopeSearch === null)
  739. {
  740. IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' has no scope query for ' . $sTargetObjectClass . ' class.');
  741. $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
  742. }
  743. // - Base query from meta model
  744. if ($oTargetAttDef->IsExternalKey())
  745. {
  746. $oSearch = DBSearch::FromOQL($oTargetAttDef->GetValuesDef()->GetFilterExpression());
  747. }
  748. elseif ($oTargetAttDef->IsLinkSet())
  749. {
  750. $oSearch = $oScopeSearch;
  751. }
  752. // - Filtering objects to ignore
  753. if (($aObjectIdsToIgnore !== null) && (is_array($aObjectIdsToIgnore)))
  754. {
  755. //$oSearch->AddConditionExpression('id', $aObjectIdsToIgnore, 'NOT IN');
  756. $aExpressions = array();
  757. foreach ($aObjectIdsToIgnore as $sObjectIdToIgnore)
  758. {
  759. $aExpressions[] = new ScalarExpression($sObjectIdToIgnore);
  760. }
  761. $oSearch->AddConditionExpression(new BinaryExpression(new FieldExpression('id', $oSearch->GetClassAlias()), 'NOT IN', new ListExpression($aExpressions)));
  762. }
  763. // - Adding query condition
  764. $aInternalParams['this'] = $oHostObject;
  765. if ($sQuery !== null)
  766. {
  767. $oFullExpr = null;
  768. for ($i = 0; $i < count($aAttCodes); $i++)
  769. {
  770. // Checking if the current attcode is an external key in order to search on the friendlyname
  771. $oAttDef = MetaModel::GetAttributeDef($sTargetObjectClass, $aAttCodes[$i]);
  772. $sAttCode = (!$oAttDef->IsExternalKey()) ? $aAttCodes[$i] : $aAttCodes[$i] . '_friendlyname';
  773. // Building expression for the current attcode
  774. $oBinExpr = new BinaryExpression(new FieldExpression($sAttCode, $oSearch->GetClassAlias()), 'LIKE', new VariableExpression('re_query'));
  775. // Adding expression to the full expression (all attcodes)
  776. if ($i === 0)
  777. {
  778. $oFullExpr = $oBinExpr;
  779. }
  780. else
  781. {
  782. $oFullExpr = new BinaryExpression($oFullExpr, 'OR', $oBinExpr);
  783. }
  784. }
  785. // Adding full expression to the search object
  786. $oSearch->AddConditionExpression($oFullExpr);
  787. $aInternalParams['re_query'] = '%' . $sQuery . '%';
  788. }
  789. // - Intersecting with scope constraints
  790. $oSearch = $oSearch->Intersect($oScopeSearch);
  791. // Retrieving results
  792. // - Preparing object set
  793. $oSet = new DBObjectSet($oSearch, array(), $aInternalParams);
  794. $oSet->OptimizeColumnLoad(array($oSearch->GetClassAlias() => $aAttCodes));
  795. $oSet->SetLimit($iCountPerPage, $iCountPerPage * ($iPageNumber - 1));
  796. // - Retrieving columns properties
  797. $aColumnProperties = array();
  798. foreach ($aAttCodes as $sAttCode)
  799. {
  800. $oAttDef = MetaModel::GetAttributeDef($sTargetObjectClass, $sAttCode);
  801. $aColumnProperties[$sAttCode] = array(
  802. 'title' => $oAttDef->GetLabel()
  803. );
  804. }
  805. // - Retrieving objects
  806. $aItems = array();
  807. while ($oItem = $oSet->Fetch())
  808. {
  809. $aItemProperties = array(
  810. 'id' => $oItem->GetKey(),
  811. 'name' => $oItem->GetName(),
  812. 'attributes' => array()
  813. );
  814. foreach ($aAttCodes as $sAttCode)
  815. {
  816. if ($sAttCode !== 'id')
  817. {
  818. $aAttProperties = array(
  819. 'att_code' => $sAttCode
  820. );
  821. $oAttDef = MetaModel::GetAttributeDef($sTargetObjectClass, $sAttCode);
  822. if ($oAttDef->IsExternalKey())
  823. {
  824. $aAttProperties['value'] = $oItem->Get($sAttCode . '_friendlyname');
  825. // Checking if we can view the object
  826. if ((SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $oAttDef->GetTargetClass(), $oItem->Get($sAttCode))))
  827. {
  828. $aAttProperties['url'] = $oApp['url_generator']->generate('p_object_view', array('sObjectClass' => $oAttDef->GetTargetClass(), 'sObjectId' => $oItem->GetKey()));
  829. }
  830. }
  831. else
  832. {
  833. $aAttProperties['value'] = $oAttDef->GetValueLabel($oItem->Get($sAttCode));
  834. }
  835. $aItemProperties['attributes'][$sAttCode] = $aAttProperties;
  836. }
  837. }
  838. $aItems[] = $aItemProperties;
  839. }
  840. // Preparing response
  841. if ($bInitalPass)
  842. {
  843. $aData = $aData + array(
  844. 'form' => array(
  845. 'id' => 'object_search_form_' . time(),
  846. 'title' => Dict::Format('Brick:Portal:Object:Search:Regular:Title', $oTargetAttDef->GetLabel(), MetaModel::GetName($sTargetObjectClass))
  847. ),
  848. 'aColumnProperties' => json_encode($aColumnProperties),
  849. 'aResults' => array(
  850. 'aItems' => json_encode($aItems),
  851. 'iCount' => count($aItems)
  852. ),
  853. 'bMultipleSelect' => $oTargetAttDef->IsLinkSet(),
  854. 'aSource' => array(
  855. 'sFormPath' => $sFormPath,
  856. 'sFieldId' => $sFieldId,
  857. 'aObjectIdsToIgnore' => $aObjectIdsToIgnore,
  858. 'sFormManagerClass' => $sFormManagerClass,
  859. 'sFormManagerData' => $sFormManagerData
  860. )
  861. );
  862. if ($oRequest->isXmlHttpRequest())
  863. {
  864. $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
  865. }
  866. else
  867. {
  868. //$oResponse = $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
  869. $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
  870. }
  871. }
  872. else
  873. {
  874. $aData = $aData + array(
  875. 'levelsProperties' => $aColumnProperties,
  876. 'data' => $aItems,
  877. 'recordsTotal' => $oSet->Count(),
  878. 'recordsFiltered' => $oSet->Count()
  879. );
  880. $oResponse = $oApp->json($aData);
  881. }
  882. return $oResponse;
  883. }
  884. /**
  885. * Handles the hierarchical search from an attribute
  886. *
  887. * @param Request $oRequest
  888. * @param Application $oApp
  889. * @param string $sTargetAttCode Attribute code of the host object pointing to the Object class to search
  890. * @param string $sHostObjectClass Class name of the host object
  891. * @param string $sHostObjectId Id of the host object
  892. * @return Response
  893. */
  894. public function SearchHierarchyAction(Request $oRequest, Application $oApp, $sTargetAttCode, $sHostObjectClass, $sHostObjectId = null)
  895. {
  896. $aData = array(
  897. 'sMode' => 'search_hierarchy',
  898. 'sTargetAttCode' => $sTargetAttCode,
  899. 'sHostObjectClass' => $sHostObjectClass,
  900. 'sHostObjectId' => $sHostObjectId
  901. );
  902. // Checking security layers
  903. if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sHostObjectClass, $sHostObjectId))
  904. {
  905. IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to read ' . $sHostObjectClass . '::' . $sHostObjectId . ' object.');
  906. $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
  907. }
  908. // Retrieving host object for future DBSearch parameters
  909. if ($sHostObjectId !== null)
  910. {
  911. $oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId);
  912. }
  913. else
  914. {
  915. $oHostObject = MetaModel::NewObject($sHostObjectClass);
  916. }
  917. // Retrieving request parameters
  918. $bInitalPass = ($oRequest->get('draw') === null) ? true : false;
  919. $sQuery = $oRequest->get('sSearchValue'); // Note : Not used yet
  920. $sFormPath = $oRequest->get('sFormPath');
  921. $sFieldId = $oRequest->get('sFieldId');
  922. // Building search query
  923. // - Retrieving target object class from attcode
  924. $oTargetAttDef = MetaModel::GetAttributeDef($sHostObjectClass, $sTargetAttCode);
  925. if ($oTargetAttDef->IsExternalKey())
  926. {
  927. $sTargetObjectClass = $oTargetAttDef->GetTargetClass();
  928. }
  929. elseif ($oTargetAttDef->IsLinkSet())
  930. {
  931. if (!$oTargetAttDef->IsIndirect())
  932. {
  933. $sTargetObjectClass = $oTargetAttDef->GetLinkedClass();
  934. }
  935. else
  936. {
  937. $oRemoteAttDef = MetaModel::GetAttributeDef($oTargetAttDef->GetLinkedClass(), $oTargetAttDef->GetExtKeyToRemote());
  938. $sTargetObjectClass = $oRemoteAttDef->GetTargetClass();
  939. }
  940. }
  941. else
  942. {
  943. throw new Exception('Search by hierarchy can only apply on AttributeExternalKey or AttributeLinkedSet objects, ' . get_class($oTargetAttDef) . ' given.');
  944. }
  945. // // - Retrieving class attribute list
  946. // $aAttCodes = MetaModel::FlattenZList(MetaModel::GetZListItems($sTargetObjectClass, 'list'));
  947. // // - Adding friendlyname attribute to the list is not already in it
  948. // $sTitleAttrCode = MetaModel::GetFriendlyNameAttributeCode($sTargetObjectClass);
  949. // if (!in_array($sTitleAttrCode, $aAttCodes))
  950. // {
  951. // $aAttCodes = array_merge(array($sTitleAttrCode), $aAttCodes);
  952. // }
  953. // - Retrieving scope search
  954. $oScopeSearch = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sTargetObjectClass, UR_ACTION_READ);
  955. if ($oScopeSearch === null)
  956. {
  957. IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' has no scope query for ' . $sTargetObjectClass . ' class.');
  958. $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
  959. }
  960. // - Base query from meta model
  961. if ($oTargetAttDef->IsExternalKey())
  962. {
  963. $oSearch = DBSearch::FromOQL($oTargetAttDef->GetValuesDef()->GetFilterExpression());
  964. }
  965. // elseif ($oTargetAttDef->IsLinkSet())
  966. else
  967. {
  968. $oSearch = $oScopeSearch;
  969. }
  970. // // - Adding query condition
  971. $aInternalParams = array('this' => $oHostObject);
  972. // if ($sQuery !== null)
  973. // {
  974. // for ($i = 0; $i < count($aAttCodes); $i++)
  975. // {
  976. // // Checking if the current attcode is an external key in order to search on the friendlyname
  977. // $oAttDef = MetaModel::GetAttributeDef($sTargetObjectClass, $aAttCodes[$i]);
  978. // $sAttCode = (!$oAttDef->IsExternalKey()) ? $aAttCodes[$i] : $aAttCodes[$i] . '_friendlyname';
  979. // // Building expression for the current attcode
  980. // $oBinExpr = new BinaryExpression(new FieldExpression($sAttCode, $oSearch->GetClassAlias()), 'LIKE', new VariableExpression('re_query'));
  981. // // Adding expression to the full expression (all attcodes)
  982. // if ($i === 0)
  983. // {
  984. // $oFullExpr = $oBinExpr;
  985. // }
  986. // else
  987. // {
  988. // $oFullExpr = new BinaryExpression($oFullExpr, 'OR', $oBinExpr);
  989. // }
  990. // }
  991. // // Adding full expression to the search object
  992. // $oSearch->AddConditionExpression($oFullExpr);
  993. // $aInternalParams['re_query'] = '%' . $sQuery . '%';
  994. // }
  995. // - Intersecting with scope constraints
  996. $oSearch = $oSearch->Intersect($oScopeSearch);
  997. // Retrieving results
  998. // - Preparing object set
  999. $oSet = new DBObjectSet($oSearch, array(), $aInternalParams);
  1000. $oSet->OptimizeColumnLoad(array($oSearch->GetClassAlias() => array('friendlyname')));
  1001. // $oSet->SetLimit($iCountPerPage, $iCountPerPage * ($iPageNumber - 1));
  1002. // // - Retrieving columns properties
  1003. // $aColumnProperties = array();
  1004. // foreach ($aAttCodes as $sAttCode)
  1005. // {
  1006. // $oAttDef = MetaModel::GetAttributeDef($sTargetObjectClass, $sAttCode);
  1007. // $aColumnProperties[$sAttCode] = array(
  1008. // 'title' => $oAttDef->GetLabel()
  1009. // );
  1010. // }
  1011. // - Retrieving objects
  1012. $aItems = array();
  1013. while ($oItem = $oSet->Fetch())
  1014. {
  1015. $aItemProperties = array(
  1016. 'id' => $oItem->GetKey(),
  1017. 'name' => $oItem->GetName(),
  1018. 'attributes' => array()
  1019. );
  1020. // foreach ($aAttCodes as $sAttCode)
  1021. // {
  1022. // if ($sAttCode !== 'id')
  1023. // {
  1024. // $aAttProperties = array(
  1025. // 'att_code' => $sAttCode
  1026. // );
  1027. //
  1028. // $oAttDef = MetaModel::GetAttributeDef($sTargetObjectClass, $sAttCode);
  1029. // if ($oAttDef->IsExternalKey())
  1030. // {
  1031. // $aAttProperties['value'] = $oItem->Get($sAttCode . '_friendlyname');
  1032. // // Checking if we can view the object
  1033. // if ((SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $oAttDef->GetTargetClass(), $oItem->Get($sAttCode))))
  1034. // {
  1035. // $aAttProperties['url'] = $oApp['url_generator']->generate('p_object_view', array('sObjectClass' => $oAttDef->GetTargetClass(), 'sObjectId' => $oItem->GetKey()));
  1036. // }
  1037. // }
  1038. // else
  1039. // {
  1040. // $aAttProperties['value'] = $oAttDef->GetValueLabel($oItem->Get($sAttCode));
  1041. // }
  1042. //
  1043. // $aItemProperties['attributes'][$sAttCode] = $aAttProperties;
  1044. // }
  1045. // }
  1046. $aItems[] = $aItemProperties;
  1047. }
  1048. // Preparing response
  1049. if ($bInitalPass)
  1050. {
  1051. $aData = $aData + array(
  1052. 'form' => array(
  1053. 'id' => 'object_search_form_' . time(),
  1054. 'title' => Dict::Format('Brick:Portal:Object:Search:Hierarchy:Title', $oTargetAttDef->GetLabel(), MetaModel::GetName($sTargetObjectClass))
  1055. ),
  1056. 'aResults' => array(
  1057. 'aItems' => json_encode($aItems),
  1058. 'iCount' => count($aItems)
  1059. ),
  1060. 'aSource' => array(
  1061. 'sFormPath' => $sFormPath,
  1062. 'sFieldId' => $sFieldId
  1063. )
  1064. );
  1065. if ($oRequest->isXmlHttpRequest())
  1066. {
  1067. $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
  1068. }
  1069. else
  1070. {
  1071. //$oResponse = $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
  1072. $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
  1073. }
  1074. }
  1075. else
  1076. {
  1077. $aData = $aData + array(
  1078. 'levelsProperties' => $aColumnProperties,
  1079. 'data' => $aItems
  1080. );
  1081. $oResponse = $oApp->json($aData);
  1082. }
  1083. return $oResponse;
  1084. }
  1085. /**
  1086. * Handles attachment add/remove on an object
  1087. *
  1088. * Note : This is inspired from itop-attachment/ajax.attachment.php
  1089. *
  1090. * @param Request $oRequest
  1091. * @param Application $oApp
  1092. */
  1093. public function AttachmentAction(Request $oRequest, Application $oApp, $sOperation = null)
  1094. {
  1095. $aData = array(
  1096. 'att_id' => 0,
  1097. 'preview' => false,
  1098. 'msg' => ''
  1099. );
  1100. // Retrieving sOperation from request only if it wasn't forced (determined by the route)
  1101. if ($sOperation === null)
  1102. {
  1103. $sOperation = $oRequest->get('operation');
  1104. }
  1105. switch ($sOperation)
  1106. {
  1107. case 'add':
  1108. $sFieldName = $oRequest->get('field_name');
  1109. $sObjectClass = $oRequest->get('object_class');
  1110. $sTempId = $oRequest->get('temp_id');
  1111. if (($sObjectClass === null) || ($sTempId === null))
  1112. {
  1113. $aData['error'] = Dict::Format('UI:Error:2ParametersMissing', 'object_class', 'temp_id');
  1114. }
  1115. else
  1116. {
  1117. try
  1118. {
  1119. $oDocument = utils::ReadPostedDocument($sFieldName);
  1120. $oAttachment = MetaModel::NewObject('Attachment');
  1121. $oAttachment->Set('expire', time() + 3600); // one hour...
  1122. $oAttachment->Set('temp_id', $sTempId);
  1123. $oAttachment->Set('item_class', $sObjectClass);
  1124. $oAttachment->SetDefaultOrgId();
  1125. $oAttachment->Set('contents', $oDocument);
  1126. $iAttId = $oAttachment->DBInsert();
  1127. $aData['msg'] = htmlentities($oDocument->GetFileName(), ENT_QUOTES, 'UTF-8');
  1128. // TODO : Change icon location when itop-attachment is refactored
  1129. //$aData['icon'] = utils::GetAbsoluteUrlAppRoot() . AttachmentPlugIn::GetFileIcon($oDoc->GetFileName());
  1130. $aData['icon'] = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/itop-attachments/icons/image.png';
  1131. $aData['att_id'] = $iAttId;
  1132. $aData['preview'] = $oDocument->IsPreviewAvailable() ? 'true' : 'false';
  1133. }
  1134. catch (FileUploadException $e)
  1135. {
  1136. $aData['error'] = $e->GetMessage();
  1137. }
  1138. }
  1139. $oResponse = $oApp->json($aData);
  1140. break;
  1141. case 'download':
  1142. $sAttachmentId = $oRequest->get('sAttachmentId');
  1143. $sAttachmentUrl = utils::GetAbsoluteUrlAppRoot() . ATTACHMENT_DOWNLOAD_URL . $sAttachmentId;
  1144. $oResponse = new RedirectResponse($sAttachmentUrl);
  1145. break;
  1146. default:
  1147. $oApp->abort(403);
  1148. break;
  1149. }
  1150. return $oResponse;
  1151. }
  1152. /**
  1153. * Returns a json response containing an array of objects informations.
  1154. *
  1155. * The service must be given 3 parameters :
  1156. * - sObjectClass : The class of objects to retrieve information from
  1157. * - aObjectIds : An array of object ids
  1158. * - aObjectAttCodes : An array of attribute codes to retrieve
  1159. *
  1160. * @param Request $oRequest
  1161. * @param Application $oApp
  1162. * @return Response
  1163. */
  1164. public function GetInformationsAsJsonAction(Request $oRequest, Application $oApp)
  1165. {
  1166. $aData = array();
  1167. // Retrieving parameters
  1168. $sObjectClass = $oRequest->Get('sObjectClass');
  1169. $aObjectIds = $oRequest->Get('aObjectIds');
  1170. $aObjectAttCodes = $oRequest->Get('aObjectAttCodes');
  1171. if ($sObjectClass === null || $aObjectIds === null || $aObjectAttCodes === null)
  1172. {
  1173. IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : sObjectClass, sObjectId and aObjectAttCodes expected, "' . $sObjectClass . '", "' . $sObjectId . '" given.');
  1174. $oApp->abort(500, 'Invalid request data, some informations are missing');
  1175. }
  1176. // Checking that id is in the AttCodes
  1177. if (!in_array('id', $aObjectAttCodes))
  1178. {
  1179. $aObjectAttCodes = array_merge(array('id'), $aObjectAttCodes);
  1180. }
  1181. // Retrieving attributes definitions
  1182. $aAttDefs = array();
  1183. foreach ($aObjectAttCodes as $sObjectAttCode)
  1184. {
  1185. if ($sObjectAttCode === 'id')
  1186. continue;
  1187. $aAttDefs[$sObjectAttCode] = MetaModel::GetAttributeDef($sObjectClass, $sObjectAttCode);
  1188. }
  1189. // Building the search
  1190. $oSearch = DBObjectSearch::FromOQL("SELECT " . $sObjectClass . " WHERE id IN ('" . implode("','", $aObjectIds) . "')");
  1191. $oSet = new DBObjectSet($oSearch);
  1192. $oSet->OptimizeColumnLoad($aObjectAttCodes);
  1193. // Retrieving objects
  1194. while ($oObject = $oSet->Fetch())
  1195. {
  1196. $aObjectData = array(
  1197. 'id' => $oObject->GetKey(),
  1198. 'attributes' => array()
  1199. );
  1200. foreach ($aAttDefs as $oAttDef)
  1201. {
  1202. $aAttData = array(
  1203. 'att_code' => $oAttDef->GetCode()
  1204. );
  1205. if ($oAttDef->IsExternalKey())
  1206. {
  1207. $aAttData['value'] = $oObject->Get($oAttDef->GetCode() . '_friendlyname');
  1208. if (SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $oAttDef->GetTargetClass()))
  1209. {
  1210. $aAttData['url'] = $oApp['url_generator']->generate('p_object_view', array('sObjectClass' => $oAttDef->GetTargetClass(), 'sObjectId' => $oObject->Get($oAttDef->GetCode())));
  1211. }
  1212. }
  1213. elseif ($oAttDef->IsLinkSet())
  1214. {
  1215. // We skip it
  1216. continue;
  1217. }
  1218. else
  1219. {
  1220. $aAttData['value'] = $oAttDef->GetValueLabel($oObject->Get($oAttDef->GetCode()));
  1221. }
  1222. $aObjectData['attributes'][$oAttDef->GetCode()] = $aAttData;
  1223. }
  1224. $aData['items'][] = $aObjectData;
  1225. }
  1226. return $oApp->json($aData);
  1227. }
  1228. }
  1229. ?>