applicationhelper.class.inc.php 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032
  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\Helper;
  19. use \Exception;
  20. use \Silex\Application;
  21. use \Symfony\Component\Debug\ErrorHandler;
  22. use \Symfony\Component\Debug\ExceptionHandler;
  23. use \Symfony\Component\HttpFoundation\Request;
  24. use \Twig_SimpleFilter;
  25. use \Dict;
  26. use \utils;
  27. use \IssueLog;
  28. use \UserRights;
  29. use \DOMFormatException;
  30. use \ModuleDesign;
  31. use \MetaModel;
  32. use \DBObjectSearch;
  33. use \DBObjectSet;
  34. use \Combodo\iTop\Portal\Brick\AbstractBrick;
  35. /**
  36. * Contains static methods to help loading / registering classes of the application.
  37. * Mostly used for Controllers / Routers / Entities initialization.
  38. *
  39. * @author Guillaume Lajarige
  40. */
  41. class ApplicationHelper
  42. {
  43. /**
  44. * Loads classes from the base portal
  45. *
  46. * @param string $sScannedDir Directory to load the files from
  47. * @param string $sFilePattern Pattern of files to load
  48. * @param string $sType Type of files to load, used only in the Exception message, can be anything
  49. * @throws \Exception
  50. */
  51. static function LoadClasses($sScannedDir, $sFilePattern, $sType)
  52. {
  53. // Loading classes from base portal
  54. foreach (scandir($sScannedDir) as $sFile)
  55. {
  56. if (strpos($sFile, $sFilePattern) !== false && file_exists($sFilepath = $sScannedDir . '/' . $sFile))
  57. {
  58. try
  59. {
  60. require_once $sFilepath;
  61. }
  62. catch (Exception $e)
  63. {
  64. throw new Exception('Error while trying to load ' . $sType . ' ' . $sFile);
  65. }
  66. }
  67. }
  68. }
  69. /**
  70. * Loads controllers from the base portal
  71. *
  72. * @param string $sScannedDir Directory to load the controllers from
  73. * @throws \Exception
  74. */
  75. static function LoadControllers($sScannedDir = null)
  76. {
  77. if ($sScannedDir === null)
  78. {
  79. $sScannedDir = __DIR__ . '/../controllers';
  80. }
  81. // Loading controllers from base portal (those from modules have already been loaded by module.xxx.php files)
  82. self::LoadClasses($sScannedDir, 'controller.class.inc.php', 'controller');
  83. }
  84. /**
  85. * Loads routers from the base portal
  86. *
  87. * @param string $sScannedDir Directory to load the routers from
  88. * @throws \Exception
  89. */
  90. static function LoadRouters($sScannedDir = null)
  91. {
  92. if ($sScannedDir === null)
  93. {
  94. $sScannedDir = __DIR__ . '/../routers';
  95. }
  96. // Loading routers from base portal (those from modules have already been loaded by module.xxx.php files)
  97. self::LoadClasses($sScannedDir, 'router.class.inc.php', 'router');
  98. }
  99. /**
  100. * Loads bricks from the base portal
  101. *
  102. * @param string $sScannedDir Directory to load the bricks from
  103. * @throws \Exception
  104. */
  105. static function LoadBricks($sScannedDir = null)
  106. {
  107. if ($sScannedDir === null)
  108. {
  109. $sScannedDir = __DIR__ . '/../entities';
  110. }
  111. // Loading bricks from base portal (those from modules have already been loaded by module.xxx.php files)
  112. self::LoadClasses($sScannedDir, 'brick.class.inc.php', 'brick');
  113. }
  114. /**
  115. * Loads form managers from the base portal
  116. *
  117. * @param string $sScannedDir Directory to load the managers from
  118. * @throws \Exception
  119. */
  120. static function LoadFormManagers($sScannedDir = null)
  121. {
  122. if ($sScannedDir === null)
  123. {
  124. $sScannedDir = __DIR__ . '/../forms';
  125. }
  126. // Loading form managers from base portal (those from modules have already been loaded by module.xxx.php files)
  127. self::LoadClasses($sScannedDir, 'formmanager.class.inc.php', 'brick');
  128. }
  129. /**
  130. * Registers routes in the Silex Application from all declared Router classes
  131. *
  132. * @param \Silex\Application $oApp
  133. * @throws \Exception
  134. */
  135. static function RegisterRoutes(Application $oApp)
  136. {
  137. $aAllRoutes = array();
  138. foreach (get_declared_classes() as $sPHPClass)
  139. {
  140. if (is_subclass_of($sPHPClass, 'Combodo\\iTop\\Portal\\Router\\AbstractRouter'))
  141. {
  142. try
  143. {
  144. // Registering to Silex Application
  145. $sPHPClass::RegisterAllRoutes($oApp);
  146. // Registering them together so we can access them from everywhere
  147. foreach ($sPHPClass::GetRoutes() as $aRoute)
  148. {
  149. $aAllRoutes[$aRoute['bind']] = $aRoute;
  150. }
  151. }
  152. catch (Exception $e)
  153. {
  154. throw new Exception('Error while trying to register routes');
  155. }
  156. }
  157. }
  158. $oApp['combodo.portal.instance.routes'] = $aAllRoutes;
  159. }
  160. /**
  161. * Returns all registered routes for the current portal instance
  162. *
  163. * @param \Silex\Application $oApp
  164. * @param boolean $bNamesOnly If set to true, function will return only the routes' names, not the objects
  165. * @return array
  166. */
  167. static function GetRoutes(Application $oApp, $bNamesOnly = false)
  168. {
  169. return ($bNamesOnly) ? array_keys($oApp['combodo.portal.instance.routes']) : $oApp['combodo.portal.instance.routes'];
  170. }
  171. /**
  172. * Registers Twig extensions such as filters or functions.
  173. * It allows us to access some stuff directly in twig.
  174. *
  175. * @param \Silex\Application $oApp
  176. */
  177. static function RegisterTwigExtensions(Application $oApp)
  178. {
  179. // A filter to translate a string via the Dict::S function
  180. // Usage in twig : {{ 'String:ToTranslate'|dict_s }}
  181. $oApp['twig']->addFilter(new Twig_SimpleFilter('dict_s', function($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
  182. {
  183. return Dict::S($sStringCode, $sDefault, $bUserLanguageOnly);
  184. })
  185. );
  186. // A filter to format a string via the Dict::Format function
  187. // Usage in twig : {{ 'String:ToTranslate'|dict_format() }}
  188. $oApp['twig']->addFilter(new Twig_SimpleFilter('dict_format', function($sStringCode, $sParam01 = null, $sParam02 = null, $sParam03 = null, $sParam04 = null)
  189. {
  190. return Dict::Format($sStringCode, $sParam01, $sParam02, $sParam03, $sParam04);
  191. })
  192. );
  193. // Filters to enable base64 encode/decode
  194. // Usage in twig : {{ 'String to encode'|base64_encode }}
  195. $oApp['twig']->addFilter(new Twig_SimpleFilter('base64_encode', 'base64_encode'));
  196. $oApp['twig']->addFilter(new Twig_SimpleFilter('base64_decode', 'base64_decode'));
  197. // Filters to enable json decode (encode already exists)
  198. // Usage in twig : {{ aSomeArray|json_decode }}
  199. $oApp['twig']->addFilter(new Twig_SimpleFilter('json_decode', function($sJsonString, $bAssoc = false)
  200. {
  201. return json_decode($sJsonString, $bAssoc);
  202. })
  203. );
  204. }
  205. /**
  206. * Registers an exception handler that will intercept controllers exceptions and display them in a nice template.
  207. * Note : It is only active when $oApp['debug'] is false
  208. *
  209. * @param Application $oApp
  210. */
  211. static function RegisterExceptionHandler(Application $oApp)
  212. {
  213. ErrorHandler::register();
  214. ExceptionHandler::register(($oApp['debug'] === true));
  215. if (!$oApp['debug'])
  216. {
  217. $oApp->error(function(Exception $e, $code) use ($oApp)
  218. {
  219. $aData = array(
  220. 'exception' => $e,
  221. 'code' => $code,
  222. 'error_title' => '',
  223. 'error_message' => $e->getMessage()
  224. );
  225. switch ($code)
  226. {
  227. case 404:
  228. $aData['error_title'] = Dict::S('Error:HTTP:404');
  229. break;
  230. default:
  231. $aData['error_title'] = Dict::S('Error:HTTP:500');
  232. break;
  233. }
  234. IssueLog::Error($aData['error_title'] . ' : ' . $aData['error_message']);
  235. if ($oApp['request']->isXmlHttpRequest())
  236. {
  237. $oResponse = $oApp->json($aData, $code);
  238. }
  239. else
  240. {
  241. $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/errors/layout.html.twig', $aData);
  242. }
  243. return $oResponse;
  244. });
  245. }
  246. }
  247. /**
  248. * Loads the portal instance configuration from its module design into the Silex application
  249. *
  250. * @param \Silex\Application $oApp
  251. * @throws Exception
  252. */
  253. static function LoadPortalConfiguration(Application $oApp)
  254. {
  255. try
  256. {
  257. // Loading file
  258. if (!defined('PORTAL_ID'))
  259. {
  260. throw new Exception('Cannot load module design, Portal ID is not defined');
  261. }
  262. $oDesign = new ModuleDesign(PORTAL_ID);
  263. // Parsing file
  264. // - Default values
  265. $aPortalConf = array(
  266. 'properties' => array(
  267. 'id' => PORTAL_ID,
  268. 'name' => 'Page:DefaultTitle',
  269. 'logo' => (file_exists(MODULESROOT . 'branding/portal-logo.png')) ? utils::GetAbsoluteUrlModulesRoot() . 'branding/portal-logo.png' : '../images/logo-itop-dark-bg.svg',
  270. 'themes' => array(
  271. 'bootstrap' => 'itop-portal-base/portal/web/css/bootstrap-theme-combodo.scss',
  272. 'portal' => 'itop-portal-base/portal/web/css/portal.scss',
  273. 'others' => array(),
  274. ),
  275. 'templates' => array(
  276. 'layout' => 'itop-portal-base/portal/src/views/layout.html.twig',
  277. 'home' => 'itop-portal-base/portal/src/views/home/layout.html.twig'
  278. ),
  279. 'triggers_query' => null,
  280. 'attachments' => array(
  281. 'allow_delete' => true
  282. )
  283. ),
  284. 'portals' => array(),
  285. 'forms' => array(),
  286. 'bricks' => array(),
  287. 'bricks_total_width' => 0
  288. );
  289. // - Global portal properties
  290. foreach ($oDesign->GetNodes('/module_design/properties/*') as $oPropertyNode)
  291. {
  292. $bPropertyNodeError = false;
  293. switch ($oPropertyNode->nodeName)
  294. {
  295. case 'name':
  296. case 'triggers_query':
  297. $aPortalConf['properties'][$oPropertyNode->nodeName] = $oPropertyNode->GetText($aPortalConf['properties'][$oPropertyNode->nodeName]);
  298. break;
  299. case 'logo':
  300. $aPortalConf['properties'][$oPropertyNode->nodeName] = $oPropertyNode->GetText($aPortalConf['properties'][$oPropertyNode->nodeName]);
  301. break;
  302. case 'themes':
  303. case 'templates':
  304. foreach ($oPropertyNode->GetNodes('template|theme') as $oSubNode)
  305. {
  306. if (!$oSubNode->hasAttribute('id') || $oSubNode->GetText(null) === null)
  307. {
  308. throw new DOMFormatException('Tag ' . $oSubNode->nodeName . ' must have a "id" attribute as well as a value', null, null, $oSubNode);
  309. }
  310. $sNodeId = $oSubNode->getAttribute('id');
  311. switch ($oSubNode->nodeName)
  312. {
  313. case 'theme':
  314. switch ($sNodeId)
  315. {
  316. case 'bootstrap':
  317. case 'portal':
  318. case 'custom':
  319. $aPortalConf['properties']['themes'][$sNodeId] = $oSubNode->GetText(null);
  320. break;
  321. default:
  322. $aPortalConf['properties']['themes']['others'][] = $oSubNode->GetText(null);
  323. break;
  324. }
  325. break;
  326. case 'template':
  327. switch ($sNodeId)
  328. {
  329. case 'layout':
  330. case 'home':
  331. $aPortalConf['properties']['templates'][$sNodeId] = $oSubNode->GetText(null);
  332. break;
  333. default:
  334. throw new DOMFormatException('Value "' . $sNodeId . '" is not handled for template[@id]', null, null, $oSubNode);
  335. break;
  336. }
  337. break;
  338. }
  339. }
  340. break;
  341. case 'attachments':
  342. foreach ($oPropertyNode->GetNodes('*') as $oSubNode)
  343. {
  344. switch ($oSubNode->nodeName)
  345. {
  346. case 'allow_delete':
  347. $sValue = $oSubNode->GetText();
  348. // If the text is null, we keep the default value
  349. // Else we set it
  350. if ($sValue !== null)
  351. {
  352. $aPortalConf['properties']['attachments'][$oSubNode->nodeName] = ($sValue === 'true') ? true : false;
  353. }
  354. break;
  355. }
  356. }
  357. break;
  358. }
  359. }
  360. // - Rectifying portal logo url
  361. $sLogoUri = $aPortalConf['properties']['logo'];
  362. if (!preg_match('/^http/', $sLogoUri))
  363. {
  364. // We prefix it with the server base url
  365. $sLogoUri = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/' . $sLogoUri;
  366. }
  367. $aPortalConf['properties']['logo'] = $sLogoUri;
  368. // - User allowed portals
  369. $aPortalConf['portals'] = UserRights::GetAllowedPortals();
  370. // - Bricks
  371. $aPortalConf = static::LoadBricksConfiguration($oApp, $oDesign) + $aPortalConf;
  372. // - Forms
  373. $aPortalConf['forms'] = static::LoadFormsConfiguration($oApp, $oDesign);
  374. // - Scopes
  375. static::LoadScopesConfiguration($oApp, $oDesign);
  376. // - Presentation lists
  377. $aPortalConf['lists'] = static::LoadListsConfiguration($oApp, $oDesign);
  378. // - Action rules
  379. static::LoadActionRulesConfiguration($oApp, $oDesign);
  380. // - Generating CSS files
  381. $aImportPaths = array($oApp['combodo.portal.base.absolute_path'] . 'css/');
  382. foreach ($aPortalConf['properties']['themes'] as $key => $value)
  383. {
  384. if (!is_array($value))
  385. {
  386. $aPortalConf['properties']['themes'][$key] = $oApp['combodo.absolute_url'] . utils::GetCSSFromSASS('env-' . utils::GetCurrentEnvironment() . '/' . $value, $aImportPaths);
  387. }
  388. else
  389. {
  390. $aValues = array();
  391. foreach ($value as $sSubvalue)
  392. {
  393. $aValues[] = $oApp['combodo.absolute_url'] . utils::GetCSSFromSASS('env-' . utils::GetCurrentEnvironment() . '/' . $sSubvalue, $aImportPaths);
  394. }
  395. $aPortalConf['properties']['themes'][$key] = $aValues;
  396. }
  397. }
  398. $oApp['combodo.portal.instance.conf'] = $aPortalConf;
  399. }
  400. catch (Exception $e)
  401. {
  402. throw new Exception('Error while parsing portal configuration file : ' . $e->getMessage());
  403. }
  404. }
  405. /**
  406. * Loads the current user and stores it in the Silex application so we can use it wherever in the application
  407. *
  408. * @param \Silex\Application $oApp
  409. * @throws Exception
  410. */
  411. static function LoadCurrentUser(Application $oApp)
  412. {
  413. // User
  414. $oUser = UserRights::GetUserObject();
  415. if ($oUser === null)
  416. {
  417. throw new Exception('Could not load connected user.');
  418. }
  419. $oApp['combodo.current_user'] = $oUser;
  420. // Contact
  421. $sContactPhotoUrl = $oApp['combodo.portal.base.absolute_url'] . 'img/user-profile-default-256px.png';
  422. $oContact = UserRights::GetContactObject();
  423. if ($oContact)
  424. {
  425. if (MetaModel::IsValidAttCode(get_class($oContact), 'picture'))
  426. {
  427. $oImage = $oContact->Get('picture');
  428. if (is_object($oImage) && !$oImage->IsEmpty())
  429. {
  430. $sContactPhotoUrl = $oImage->GetDownloadURL(get_class($oContact), $oContact->GetKey(), 'picture');
  431. }
  432. else
  433. {
  434. $sContactPhotoUrl = MetaModel::GetAttributeDef(get_class($oContact), 'picture')->Get('default_image');
  435. }
  436. }
  437. }
  438. $oApp['combodo.current_contact.photo_url'] = $sContactPhotoUrl;
  439. }
  440. /**
  441. * Loads the brick's security from the OQL queries to profiles arrays
  442. *
  443. * @param \Combodo\iTop\Portal\Helper\AbstractBrick $oBrick
  444. */
  445. static function LoadBrickSecurity(AbstractBrick &$oBrick)
  446. {
  447. try
  448. {
  449. // Allowed profiles
  450. if ($oBrick->GetAllowedProfilesOql() !== null && $oBrick->GetAllowedProfilesOql() !== '')
  451. {
  452. $oSearch = DBObjectSearch::FromOQL($oBrick->GetAllowedProfilesOql());
  453. $oSet = new DBObjectSet($oSearch);
  454. while ($oProfile = $oSet->Fetch())
  455. {
  456. $oBrick->AddAllowedProfile($oProfile->Get('name'));
  457. }
  458. }
  459. // Denied profiles
  460. if ($oBrick->GetDeniedProfilesOql() !== null && $oBrick->GetDeniedProfilesOql() !== '')
  461. {
  462. $oSearch = DBObjectSearch::FromOQL($oBrick->GetDeniedProfilesOql());
  463. $oSet = new DBObjectSet($oSearch);
  464. while ($oProfile = $oSet->Fetch())
  465. {
  466. $oBrick->AddDeniedProfile($oProfile->Get('name'));
  467. }
  468. }
  469. }
  470. catch (Exception $e)
  471. {
  472. throw new Exception('Error while loading security from ' . $oBrick->GetId() . ' brick');
  473. }
  474. }
  475. /**
  476. * Finds an AbstractBrick loaded in the $oApp instance configuration from its ID.
  477. *
  478. * @param \Silex\Application $oApp
  479. * @param string $sBrickId
  480. * @return \Combodo\iTop\Portal\Brick\AbstractBrick
  481. * @throws Exception
  482. */
  483. static function GetLoadedBrickFromId(Application $oApp, $sBrickId)
  484. {
  485. $bFound = false;
  486. foreach ($oApp['combodo.portal.instance.conf']['bricks'] as $oBrick)
  487. {
  488. if ($oBrick->GetId() === $sBrickId)
  489. {
  490. $bFound = true;
  491. break;
  492. }
  493. }
  494. if (!$bFound)
  495. {
  496. throw new Exception('Brick with id = "' . $sBrickId . '" was not found among loaded bricks.');
  497. }
  498. return $oBrick;
  499. }
  500. /**
  501. * Return the form properties for the $sClassname in $sMode.
  502. *
  503. * If not found, tries to find one from the closest parent class.
  504. * Else returns a default form based on zlist 'details'
  505. *
  506. * @param Application $oApp
  507. * @param string $sClass Object class to find a form for
  508. * @param string $sMode Form mode to find (view|edit|create)
  509. * @return array
  510. */
  511. static function GetLoadedFormFromClass(Application $oApp, $sClass, $sMode)
  512. {
  513. $aForms = $oApp['combodo.portal.instance.conf']['forms'];
  514. // We try to find the form for that class
  515. if (isset($aForms[$sClass]) && isset($aForms[$sClass][$sMode]))
  516. {
  517. $aForm = $aForms[$sClass][$sMode];
  518. }
  519. // If not found, we try find one from the closest parent class
  520. else
  521. {
  522. $bFound = false;
  523. foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass)
  524. {
  525. if (isset($aForms[$sParentClass]) && isset($aForms[$sParentClass][$sMode]))
  526. {
  527. $aForm = $aForms[$sParentClass][$sMode];
  528. $bFound = true;
  529. break;
  530. }
  531. }
  532. // If we have still not found one, we return a default form
  533. if (!$bFound)
  534. {
  535. $aForm = array(
  536. 'id' => 'default',
  537. 'type' => 'zlist',
  538. 'fields' => 'details',
  539. 'layout' => null
  540. );
  541. }
  542. }
  543. return $aForm;
  544. }
  545. /**
  546. * Return the attribute list for the $sClassname in $sList.
  547. *
  548. * If not found, tries to find one from the closest parent class.
  549. * Else returns a default attribute list based on zlist 'list'
  550. *
  551. * @param Application $oApp
  552. * @param string $sClass Object class to find a list for
  553. * @param string $sList List name to find
  554. * @return array Array of attribute codes
  555. */
  556. static function GetLoadedListFromClass(Application $oApp, $sClass, $sList = 'default')
  557. {
  558. $aLists = $oApp['combodo.portal.instance.conf']['lists'];
  559. $aList = null;
  560. $aAttCodes = array();
  561. // We try to find the list for that class
  562. if (isset($aLists[$sClass]) && isset($aLists[$sClass][$sList]))
  563. {
  564. $aList = $aLists[$sClass][$sList];
  565. }
  566. // Else we try to found the default list for that class
  567. elseif (isset($aLists[$sClass]) && isset($aLists[$sClass]['default']))
  568. {
  569. $aList = $aLists[$sClass]['default'];
  570. }
  571. // If not found, we try find one from the closest parent class
  572. else
  573. {
  574. $bFound = false;
  575. foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass)
  576. {
  577. // Trying to find the right list
  578. if (isset($aLists[$sParentClass]) && isset($aLists[$sParentClass][$sList]))
  579. {
  580. $aList = $aLists[$sParentClass][$sList];
  581. $bFound = true;
  582. break;
  583. }
  584. // Or the default list
  585. elseif (isset($aLists[$sParentClass]) && isset($aLists[$sParentClass]['default']))
  586. {
  587. $aList = $aLists[$sParentClass]['default'];
  588. $bFound = true;
  589. break;
  590. }
  591. }
  592. // If we have still not found one, we return a default form
  593. if (!$bFound)
  594. {
  595. $aForm = array(
  596. 'id' => 'default',
  597. 'type' => 'zlist',
  598. 'fields' => 'details',
  599. 'layout' => null
  600. );
  601. }
  602. }
  603. // If found, we flatten the list to keep only the attribute codes (not the rank)
  604. if ($aList !== null)
  605. {
  606. foreach ($aList as $aItem)
  607. {
  608. $aAttCodes[] = $aItem['att_code'];
  609. }
  610. }
  611. else
  612. {
  613. $aAttCodes = MetaModel::FlattenZList(MetaModel::GetZListItems($sClass, 'list'));
  614. }
  615. return $aAttCodes;
  616. }
  617. /**
  618. * Loads the bricks configuration from the module design XML and returns it as an hash array containing :
  619. * - 'brick' => array of PortalBrick objects
  620. * - 'bricks_total_width' => an integer used to create the home page grid
  621. *
  622. * @param \Silex\Application $oApp
  623. * @param ModuleDesign $oDesign
  624. * @return array
  625. * @throws Exception
  626. * @throws DOMFormatException
  627. */
  628. static protected function LoadBricksConfiguration(Application $oApp, ModuleDesign $oDesign)
  629. {
  630. $aPortalConf = array(
  631. 'bricks' => array(),
  632. 'bricks_total_width' => 0,
  633. 'bricks_home_count' => 0,
  634. 'bricks_navigation_menu_count' => 0
  635. );
  636. foreach ($oDesign->GetNodes('/module_design/bricks/brick') as $oBrickNode)
  637. {
  638. try
  639. {
  640. $sBrickClass = $oBrickNode->getAttribute('xsi:type');
  641. if (class_exists($sBrickClass))
  642. {
  643. $oBrick = new $sBrickClass();
  644. $oBrick->LoadFromXml($oBrickNode);
  645. static::LoadBrickSecurity($oBrick);
  646. // GLA : This didn't work has the modal flag was set for all instances of that brick
  647. // // Checking brick modal flag
  648. // if ($oBrick->GetModal())
  649. // {
  650. // // We have to extract / replace the array as we can modify $oApp values directly
  651. // $aRoutes = $oApp['combodo.portal.instance.routes'];
  652. // // Init brick's array if necessary
  653. // if (!isset($aRoutes[$oBrick->GetRouteName()]['navigation_menu_attr']))
  654. // {
  655. // $aRoutes[$oBrick->GetRouteName()]['navigation_menu_attr'] = array();
  656. // }
  657. // // Add modal datas for the brick
  658. // $aRoutes[$oBrick->GetRouteName()]['navigation_menu_attr']['data-toggle'] = 'modal';
  659. // $aRoutes[$oBrick->GetRouteName()]['navigation_menu_attr']['data-target'] = '#modal-for-all';
  660. // // Finally, replace array in $oApp
  661. // $oApp['combodo.portal.instance.routes'] = $aRoutes;
  662. // }
  663. // Checking brick security
  664. if ($oBrick->GetActive() && $oBrick->IsGrantedForProfiles(UserRights::ListProfiles()))
  665. {
  666. $aPortalConf['bricks'][] = $oBrick;
  667. $aPortalConf['bricks_total_width'] += $oBrick->GetWidth();
  668. if ($oBrick->GetVisibleHome())
  669. {
  670. $aPortalConf['bricks_home_count']++;
  671. }
  672. if ($oBrick->GetVisibleNavigationMenu())
  673. {
  674. $aPortalConf['bricks_navigation_menu_count']++;
  675. }
  676. }
  677. }
  678. else
  679. {
  680. throw new DOMFormatException('Unknown brick class "' . $sBrickClass . '" from xsi:type attribute', null, null, $oBrickNode);
  681. }
  682. }
  683. catch (DOMFormatException $e)
  684. {
  685. throw new Exception('Could not create brick (' . $sBrickClass . ') from XML because of a DOM problem : ' . $e->getMessage());
  686. }
  687. catch (Exception $e)
  688. {
  689. throw new Exception('Could not create brick (' . $sBrickClass . ') from XML : ' . $oBrickNode->Dump() . ' ' . $e->getMessage());
  690. }
  691. }
  692. // - Sorting bricks by rank
  693. $aPortalConf['bricks_ordering'] = array();
  694. // - Home
  695. $aPortalConf['bricks_ordering']['home'] = $aPortalConf['bricks'];
  696. usort($aPortalConf['bricks_ordering']['home'], function($a, $b)
  697. {
  698. return $a->GetRankHome() > $b->GetRankHome();
  699. });
  700. // - Navigation menu
  701. $aPortalConf['bricks_ordering']['navigation_menu'] = $aPortalConf['bricks'];
  702. usort($aPortalConf['bricks_ordering']['navigation_menu'], function($a, $b)
  703. {
  704. return $a->GetRankNavigationMenu() > $b->GetRankNavigationMenu();
  705. });
  706. return $aPortalConf;
  707. }
  708. /**
  709. * Loads the forms configuration from the module design XML and returns it as an array containing :
  710. * - <CLASSNAME> => array(
  711. * 'view'|'edit'|'create' => array(
  712. * 'fields_type' => 'custom_list'|'twig'|'zlist',
  713. * 'fields' => <CONTENT>
  714. * ),
  715. * ...
  716. * ),
  717. * ...
  718. *
  719. * @param \Silex\Application $oApp
  720. * @param ModuleDesign $oDesign
  721. * @return array
  722. * @throws Exception
  723. * @throws DOMFormatException
  724. */
  725. static protected function LoadFormsConfiguration(Application $oApp, ModuleDesign $oDesign)
  726. {
  727. $aForms = array();
  728. foreach ($oDesign->GetNodes('/module_design/forms/form') as $oFormNode)
  729. {
  730. try
  731. {
  732. // Parsing form id
  733. if ($oFormNode->getAttribute('id') === '')
  734. {
  735. throw new DOMFormatException('form tag must have an id attribute', null, null, $oFormNode);
  736. }
  737. // Parsing form object class
  738. if ($oFormNode->GetUniqueElement('class')->GetText() !== null)
  739. {
  740. $sFormClass = $oFormNode->GetUniqueElement('class')->GetText();
  741. // Parsing availables modes for that form (view, edit, create)
  742. if (($oFormNode->GetOptionalElement('modes') !== null) && ($oFormNode->GetOptionalElement('modes')->GetNodes('mode')->length > 0))
  743. {
  744. $aModes = array();
  745. foreach ($oFormNode->GetOptionalElement('modes')->GetNodes('mode') as $oModeNode)
  746. {
  747. if ($oModeNode->getAttribute('id') !== '')
  748. {
  749. $aModes[] = $oModeNode->getAttribute('id');
  750. }
  751. else
  752. {
  753. throw new DOMFormatException('Mode tag must have an id attribute', null, null, $oFormNode);
  754. }
  755. }
  756. }
  757. else
  758. {
  759. $aModes = array('view', 'edit', 'create');
  760. }
  761. // Parsing fields
  762. $aFields = array(
  763. 'id' => $oFormNode->getAttribute('id'),
  764. 'type' => null,
  765. 'fields' => null,
  766. 'layout' => null
  767. );
  768. // ... either enumerated fields ...
  769. if ($oFormNode->GetOptionalElement('fields') !== null)
  770. {
  771. $aFields['type'] = 'custom_list';
  772. $aFields['fields'] = array();
  773. foreach ($oFormNode->GetOptionalElement('fields')->GetNodes('field') as $oFieldNode)
  774. {
  775. $sFieldId = $oFieldNode->getAttribute('id');
  776. if ($sFieldId !== '')
  777. {
  778. $aField = array();
  779. // Parsing field options like read_only, hidden and mandatory
  780. if ($oFieldNode->GetOptionalElement('read_only'))
  781. {
  782. $aField['readonly'] = ($oFieldNode->GetOptionalElement('read_only')->GetText('true') === 'true') ? true : false;
  783. }
  784. if ($oFieldNode->GetOptionalElement('mandatory'))
  785. {
  786. $aField['mandatory'] = ($oFieldNode->GetOptionalElement('mandatory')->GetText('true') === 'true') ? true : false;
  787. }
  788. if ($oFieldNode->GetOptionalElement('hidden'))
  789. {
  790. $aField['hidden'] = ($oFieldNode->GetOptionalElement('hidden')->GetText('true') === 'true') ? true : false;
  791. }
  792. $aFields['fields'][$sFieldId] = $aField;
  793. }
  794. else
  795. {
  796. throw new DOMFormatException('Field tag must have an id attribute', null, null, $oFormNode);
  797. }
  798. }
  799. }
  800. // // ... or a specified zlist
  801. // elseif ($oFormNode->GetOptionalElement('presentation') !== null)
  802. // {
  803. // // This is not implemented yet as it was rejected until futher notice.
  804. // }
  805. // ... or the default zlist
  806. else
  807. {
  808. $aFields['type'] = 'zlist';
  809. $aFields['fields'] = 'details';
  810. }
  811. // Parsing presentation
  812. if ($oFormNode->GetOptionalElement('twig') !== null)
  813. {
  814. // Extracting the twig template and removing the first and last lines (twig tags)
  815. $sXml = $oDesign->saveXML($oFormNode->GetOptionalElement('twig'));
  816. $sXml = preg_replace('/^.+\n/', '', $sXml);
  817. $sXml = preg_replace('/\n.+$/', '', $sXml);
  818. $aFields['layout'] = array(
  819. 'type' => (preg_match('/\{\{|\{\#|\{\%/', $sXml) === 1) ? 'twig' : 'xhtml',
  820. 'content' => $sXml
  821. );
  822. }
  823. // Adding form for each class / mode
  824. foreach ($aModes as $sMode)
  825. {
  826. if (!isset($aForms[$sFormClass]))
  827. {
  828. $aForms[$sFormClass] = array();
  829. }
  830. if (!isset($aForms[$sFormClass][$sMode]))
  831. {
  832. $aForms[$sFormClass][$sMode] = $aFields;
  833. }
  834. else
  835. {
  836. throw new DOMFormatException('There is already a form for the class "' . $sFormClass . '" in "' . $sMode . '"', null, null, $oFormNode);
  837. }
  838. }
  839. }
  840. else
  841. {
  842. throw new DOMFormatException('Class tag must be defined', null, null, $oFormNode);
  843. }
  844. }
  845. catch (DOMFormatException $e)
  846. {
  847. throw new Exception('Could not create from [id="' . $oFormNode->getAttribute('id') . '"] from XML because of a DOM problem : ' . $e->getMessage());
  848. }
  849. catch (Exception $e)
  850. {
  851. throw new Exception('Could not create from from XML : ' . $oFormNode->Dump() . ' ' . $e->getMessage());
  852. }
  853. }
  854. return $aForms;
  855. }
  856. /**
  857. * Loads the scopes configuration from the module design XML
  858. *
  859. * @param \Silex\Application $oApp
  860. * @param ModuleDesign $oDesign
  861. */
  862. static protected function LoadScopesConfiguration(Application $oApp, ModuleDesign $oDesign)
  863. {
  864. $oApp['scope_validator']->Init($oDesign->GetNodes('/module_design/classes/class'));
  865. }
  866. /**
  867. * Loads the context helper from the module design XML
  868. *
  869. * @param \Silex\Application $oApp
  870. * @param ModuleDesign $oDesign
  871. */
  872. static protected function LoadActionRulesConfiguration(Application $oApp, ModuleDesign $oDesign)
  873. {
  874. $oApp['context_manipulator']->Init($oDesign->GetNodes('/module_design/action_rules/action_rule'));
  875. }
  876. /**
  877. * Loads the classes lists from the module design XML. They are mainly used when searching an external key but could be used more extensively later
  878. *
  879. * @param \Silex\Application $oApp
  880. * @param ModuleDesign $oDesign
  881. * @return array
  882. */
  883. static protected function LoadListsConfiguration(Application $oApp, ModuleDesign $oDesign)
  884. {
  885. $iDefaultItemRank = 0;
  886. $aClassesLists = array();
  887. // Parsing XML file
  888. // - Each classes
  889. foreach ($oDesign->GetNodes('/module_design/classes/class') as $oClassNode)
  890. {
  891. $aClassLists = array();
  892. $sClassId = $oClassNode->getAttribute('id');
  893. if ($sClassId === null)
  894. {
  895. throw new DOMFormatException('Class tag must have an id attribute', null, null, $oClassNode);
  896. }
  897. // - Each lists
  898. foreach ($oClassNode->GetNodes('./lists/list') as $oListNode)
  899. {
  900. $aListItems = array();
  901. $sListId = $oListNode->getAttribute('id');
  902. if ($sListId === null)
  903. {
  904. throw new DOMFormatException('List tag of "' . $sClassId . '" class must have an id attribute', null, null, $oListNode);
  905. }
  906. // - Each items
  907. foreach ($oListNode->GetNodes('./items/item') as $oItemNode)
  908. {
  909. $sItemId = $oItemNode->getAttribute('id');
  910. if ($sItemId === null)
  911. {
  912. throw new DOMFormatException('Item tag of "' . $sItemId . '" list must have an id attribute', null, null, $oItemNode);
  913. }
  914. $aItem = array(
  915. 'att_code' => $sItemId,
  916. 'rank' => $iDefaultItemRank
  917. );
  918. $oRankNode = $oItemNode->GetOptionalElement('rank');
  919. if ($oRankNode !== null)
  920. {
  921. $aItem['rank'] = $oRankNode->GetText($iDefaultItemRank);
  922. }
  923. $aListItems[] = $aItem;
  924. }
  925. // - Sorting list items by rank
  926. usort($aListItems, function($a, $b)
  927. {
  928. return $a['rank'] > $b['rank'];
  929. });
  930. $aClassLists[$sListId] = $aListItems;
  931. }
  932. // - Adding class only if it has at least one list
  933. if (!empty($aClassLists))
  934. {
  935. $aClassesLists[$sClassId] = $aClassLists;
  936. }
  937. }
  938. // Creating lists for child classes
  939. // Note : This has been removed has we now dynamically look for the closest parent list only when necessary instead of generating list for child classes everytime
  940. /* $aParentClasses = array_keys($aClassesLists);
  941. foreach ($aParentClasses as $sParentClass)
  942. {
  943. foreach (MetaModel::EnumChildClasses($sParentClass) as $sChildClass)
  944. {
  945. // If the child class is not in the scope, we are going to try to add it
  946. if (!in_array($sChildClass, $aParentClasses))
  947. {
  948. $aClassesLists[$sChildClass] = $aClassesLists[$sParentClass];
  949. }
  950. }
  951. } */
  952. return $aClassesLists;
  953. }
  954. }
  955. ?>