applicationhelper.class.inc.php 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050
  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. // Filter to add itopversion to an url
  205. $oApp['twig']->addFilter(new Twig_SimpleFilter('add_itop_version', function($sUrl)
  206. {
  207. if (strpos($sUrl, '?') === false)
  208. {
  209. $sUrl = $sUrl . "?itopversion=" . ITOP_VERSION;
  210. }
  211. else
  212. {
  213. $sUrl = $sUrl . "&itopversion=" . ITOP_VERSION;
  214. }
  215. return $sUrl;
  216. }));
  217. }
  218. /**
  219. * Registers an exception handler that will intercept controllers exceptions and display them in a nice template.
  220. * Note : It is only active when $oApp['debug'] is false
  221. *
  222. * @param Application $oApp
  223. */
  224. static function RegisterExceptionHandler(Application $oApp)
  225. {
  226. ErrorHandler::register();
  227. ExceptionHandler::register(($oApp['debug'] === true));
  228. if (!$oApp['debug'])
  229. {
  230. $oApp->error(function(Exception $e, $code) use ($oApp)
  231. {
  232. $aData = array(
  233. 'exception' => $e,
  234. 'code' => $code,
  235. 'error_title' => '',
  236. 'error_message' => $e->getMessage()
  237. );
  238. switch ($code)
  239. {
  240. case 404:
  241. $aData['error_title'] = Dict::S('Error:HTTP:404');
  242. break;
  243. default:
  244. $aData['error_title'] = Dict::S('Error:HTTP:500');
  245. break;
  246. }
  247. IssueLog::Error($aData['error_title'] . ' : ' . $aData['error_message']);
  248. if ($oApp['request']->isXmlHttpRequest())
  249. {
  250. $oResponse = $oApp->json($aData, $code);
  251. }
  252. else
  253. {
  254. $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/errors/layout.html.twig', $aData);
  255. }
  256. return $oResponse;
  257. });
  258. }
  259. }
  260. /**
  261. * Loads the portal instance configuration from its module design into the Silex application
  262. *
  263. * @param \Silex\Application $oApp
  264. * @throws Exception
  265. */
  266. static function LoadPortalConfiguration(Application $oApp)
  267. {
  268. try
  269. {
  270. // Loading file
  271. if (!defined('PORTAL_ID'))
  272. {
  273. throw new Exception('Cannot load module design, Portal ID is not defined');
  274. }
  275. $oDesign = new ModuleDesign(PORTAL_ID);
  276. // Parsing file
  277. // - Default values
  278. $aPortalConf = array(
  279. 'properties' => array(
  280. 'id' => PORTAL_ID,
  281. 'name' => 'Page:DefaultTitle',
  282. 'logo' => (file_exists(MODULESROOT . 'branding/portal-logo.png')) ? utils::GetAbsoluteUrlModulesRoot() . 'branding/portal-logo.png' : '../images/logo-itop-dark-bg.svg',
  283. 'themes' => array(
  284. 'bootstrap' => 'itop-portal-base/portal/web/css/bootstrap-theme-combodo.scss',
  285. 'portal' => 'itop-portal-base/portal/web/css/portal.scss',
  286. 'others' => array(),
  287. ),
  288. 'templates' => array(
  289. 'layout' => 'itop-portal-base/portal/src/views/layout.html.twig',
  290. 'home' => 'itop-portal-base/portal/src/views/home/layout.html.twig'
  291. ),
  292. 'triggers_query' => null,
  293. 'attachments' => array(
  294. 'allow_delete' => true
  295. )
  296. ),
  297. 'portals' => array(),
  298. 'forms' => array(),
  299. 'bricks' => array(),
  300. 'bricks_total_width' => 0
  301. );
  302. // - Global portal properties
  303. foreach ($oDesign->GetNodes('/module_design/properties/*') as $oPropertyNode)
  304. {
  305. $bPropertyNodeError = false;
  306. switch ($oPropertyNode->nodeName)
  307. {
  308. case 'name':
  309. case 'triggers_query':
  310. $aPortalConf['properties'][$oPropertyNode->nodeName] = $oPropertyNode->GetText($aPortalConf['properties'][$oPropertyNode->nodeName]);
  311. break;
  312. case 'logo':
  313. $aPortalConf['properties'][$oPropertyNode->nodeName] = $oPropertyNode->GetText($aPortalConf['properties'][$oPropertyNode->nodeName]);
  314. break;
  315. case 'themes':
  316. case 'templates':
  317. foreach ($oPropertyNode->GetNodes('template|theme') as $oSubNode)
  318. {
  319. if (!$oSubNode->hasAttribute('id') || $oSubNode->GetText(null) === null)
  320. {
  321. throw new DOMFormatException('Tag ' . $oSubNode->nodeName . ' must have a "id" attribute as well as a value', null, null, $oSubNode);
  322. }
  323. $sNodeId = $oSubNode->getAttribute('id');
  324. switch ($oSubNode->nodeName)
  325. {
  326. case 'theme':
  327. switch ($sNodeId)
  328. {
  329. case 'bootstrap':
  330. case 'portal':
  331. case 'custom':
  332. $aPortalConf['properties']['themes'][$sNodeId] = $oSubNode->GetText(null);
  333. break;
  334. default:
  335. $aPortalConf['properties']['themes']['others'][] = $oSubNode->GetText(null);
  336. break;
  337. }
  338. break;
  339. case 'template':
  340. switch ($sNodeId)
  341. {
  342. case 'layout':
  343. case 'home':
  344. $aPortalConf['properties']['templates'][$sNodeId] = $oSubNode->GetText(null);
  345. break;
  346. default:
  347. throw new DOMFormatException('Value "' . $sNodeId . '" is not handled for template[@id]', null, null, $oSubNode);
  348. break;
  349. }
  350. break;
  351. }
  352. }
  353. break;
  354. case 'attachments':
  355. foreach ($oPropertyNode->GetNodes('*') as $oSubNode)
  356. {
  357. switch ($oSubNode->nodeName)
  358. {
  359. case 'allow_delete':
  360. $sValue = $oSubNode->GetText();
  361. // If the text is null, we keep the default value
  362. // Else we set it
  363. if ($sValue !== null)
  364. {
  365. $aPortalConf['properties']['attachments'][$oSubNode->nodeName] = ($sValue === 'true') ? true : false;
  366. }
  367. break;
  368. }
  369. }
  370. break;
  371. }
  372. }
  373. // - Rectifying portal logo url
  374. $sLogoUri = $aPortalConf['properties']['logo'];
  375. if (!preg_match('/^http/', $sLogoUri))
  376. {
  377. // We prefix it with the server base url
  378. $sLogoUri = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/' . $sLogoUri;
  379. }
  380. $aPortalConf['properties']['logo'] = $sLogoUri;
  381. // - User allowed portals
  382. $aPortalConf['portals'] = UserRights::GetAllowedPortals();
  383. // - Bricks
  384. $aPortalConf = static::LoadBricksConfiguration($oApp, $oDesign) + $aPortalConf;
  385. // - Forms
  386. $aPortalConf['forms'] = static::LoadFormsConfiguration($oApp, $oDesign);
  387. // - Scopes
  388. static::LoadScopesConfiguration($oApp, $oDesign);
  389. // - Presentation lists
  390. $aPortalConf['lists'] = static::LoadListsConfiguration($oApp, $oDesign);
  391. // - Action rules
  392. static::LoadActionRulesConfiguration($oApp, $oDesign);
  393. // - Generating CSS files
  394. $aImportPaths = array($oApp['combodo.portal.base.absolute_path'] . 'css/');
  395. foreach ($aPortalConf['properties']['themes'] as $key => $value)
  396. {
  397. if (!is_array($value))
  398. {
  399. $aPortalConf['properties']['themes'][$key] = $oApp['combodo.absolute_url'] . utils::GetCSSFromSASS('env-' . utils::GetCurrentEnvironment() . '/' . $value, $aImportPaths);
  400. }
  401. else
  402. {
  403. $aValues = array();
  404. foreach ($value as $sSubvalue)
  405. {
  406. $aValues[] = $oApp['combodo.absolute_url'] . utils::GetCSSFromSASS('env-' . utils::GetCurrentEnvironment() . '/' . $sSubvalue, $aImportPaths);
  407. }
  408. $aPortalConf['properties']['themes'][$key] = $aValues;
  409. }
  410. }
  411. $oApp['combodo.portal.instance.conf'] = $aPortalConf;
  412. }
  413. catch (Exception $e)
  414. {
  415. throw new Exception('Error while parsing portal configuration file : ' . $e->getMessage());
  416. }
  417. }
  418. /**
  419. * Loads the current user and stores it in the Silex application so we can use it wherever in the application
  420. *
  421. * @param \Silex\Application $oApp
  422. * @throws Exception
  423. */
  424. static function LoadCurrentUser(Application $oApp)
  425. {
  426. // User
  427. $oUser = UserRights::GetUserObject();
  428. if ($oUser === null)
  429. {
  430. throw new Exception('Could not load connected user.');
  431. }
  432. $oApp['combodo.current_user'] = $oUser;
  433. // Contact
  434. $sContactPhotoUrl = $oApp['combodo.portal.base.absolute_url'] . 'img/user-profile-default-256px.png';
  435. $oContact = UserRights::GetContactObject();
  436. if ($oContact)
  437. {
  438. if (MetaModel::IsValidAttCode(get_class($oContact), 'picture'))
  439. {
  440. $oImage = $oContact->Get('picture');
  441. if (is_object($oImage) && !$oImage->IsEmpty())
  442. {
  443. $sContactPhotoUrl = $oImage->GetDownloadURL(get_class($oContact), $oContact->GetKey(), 'picture');
  444. }
  445. else
  446. {
  447. $sContactPhotoUrl = MetaModel::GetAttributeDef(get_class($oContact), 'picture')->Get('default_image');
  448. }
  449. }
  450. }
  451. $oApp['combodo.current_contact.photo_url'] = $sContactPhotoUrl;
  452. }
  453. /**
  454. * Loads the brick's security from the OQL queries to profiles arrays
  455. *
  456. * @param \Combodo\iTop\Portal\Helper\AbstractBrick $oBrick
  457. */
  458. static function LoadBrickSecurity(AbstractBrick &$oBrick)
  459. {
  460. try
  461. {
  462. // Allowed profiles
  463. if ($oBrick->GetAllowedProfilesOql() !== null && $oBrick->GetAllowedProfilesOql() !== '')
  464. {
  465. $oSearch = DBObjectSearch::FromOQL($oBrick->GetAllowedProfilesOql());
  466. $oSet = new DBObjectSet($oSearch);
  467. while ($oProfile = $oSet->Fetch())
  468. {
  469. $oBrick->AddAllowedProfile($oProfile->Get('name'));
  470. }
  471. }
  472. // Denied profiles
  473. if ($oBrick->GetDeniedProfilesOql() !== null && $oBrick->GetDeniedProfilesOql() !== '')
  474. {
  475. $oSearch = DBObjectSearch::FromOQL($oBrick->GetDeniedProfilesOql());
  476. $oSet = new DBObjectSet($oSearch);
  477. while ($oProfile = $oSet->Fetch())
  478. {
  479. $oBrick->AddDeniedProfile($oProfile->Get('name'));
  480. }
  481. }
  482. }
  483. catch (Exception $e)
  484. {
  485. throw new Exception('Error while loading security from ' . $oBrick->GetId() . ' brick');
  486. }
  487. }
  488. /**
  489. * Finds an AbstractBrick loaded in the $oApp instance configuration from its ID.
  490. *
  491. * @param \Silex\Application $oApp
  492. * @param string $sBrickId
  493. * @return \Combodo\iTop\Portal\Brick\AbstractBrick
  494. * @throws Exception
  495. */
  496. static function GetLoadedBrickFromId(Application $oApp, $sBrickId)
  497. {
  498. $bFound = false;
  499. foreach ($oApp['combodo.portal.instance.conf']['bricks'] as $oBrick)
  500. {
  501. if ($oBrick->GetId() === $sBrickId)
  502. {
  503. $bFound = true;
  504. break;
  505. }
  506. }
  507. if (!$bFound)
  508. {
  509. throw new Exception('Brick with id = "' . $sBrickId . '" was not found among loaded bricks.');
  510. }
  511. return $oBrick;
  512. }
  513. /**
  514. * Return the form properties for the $sClassname in $sMode.
  515. *
  516. * If not found, tries to find one from the closest parent class.
  517. * Else returns a default form based on zlist 'details'
  518. *
  519. * @param Application $oApp
  520. * @param string $sClass Object class to find a form for
  521. * @param string $sMode Form mode to find (view|edit|create)
  522. * @return array
  523. */
  524. static function GetLoadedFormFromClass(Application $oApp, $sClass, $sMode)
  525. {
  526. $aForms = $oApp['combodo.portal.instance.conf']['forms'];
  527. // We try to find the form for that class
  528. if (isset($aForms[$sClass]) && isset($aForms[$sClass][$sMode]))
  529. {
  530. $aForm = $aForms[$sClass][$sMode];
  531. }
  532. // If not found, we try find one from the closest parent class
  533. else
  534. {
  535. $bFound = false;
  536. foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass)
  537. {
  538. if (isset($aForms[$sParentClass]) && isset($aForms[$sParentClass][$sMode]))
  539. {
  540. $aForm = $aForms[$sParentClass][$sMode];
  541. $bFound = true;
  542. break;
  543. }
  544. }
  545. // If we have still not found one, we return a default form
  546. if (!$bFound)
  547. {
  548. $aForm = array(
  549. 'id' => 'default',
  550. 'type' => 'zlist',
  551. 'fields' => 'details',
  552. 'layout' => null
  553. );
  554. }
  555. }
  556. return $aForm;
  557. }
  558. /**
  559. * Return the attribute list for the $sClassname in $sList.
  560. *
  561. * If not found, tries to find one from the closest parent class.
  562. * Else returns a default attribute list based on zlist 'list'
  563. *
  564. * @param Application $oApp
  565. * @param string $sClass Object class to find a list for
  566. * @param string $sList List name to find
  567. * @return array Array of attribute codes
  568. */
  569. static function GetLoadedListFromClass(Application $oApp, $sClass, $sList = 'default')
  570. {
  571. $aLists = $oApp['combodo.portal.instance.conf']['lists'];
  572. $aList = null;
  573. $aAttCodes = array();
  574. // We try to find the list for that class
  575. if (isset($aLists[$sClass]) && isset($aLists[$sClass][$sList]))
  576. {
  577. $aList = $aLists[$sClass][$sList];
  578. }
  579. // Else we try to found the default list for that class
  580. elseif (isset($aLists[$sClass]) && isset($aLists[$sClass]['default']))
  581. {
  582. $aList = $aLists[$sClass]['default'];
  583. }
  584. // If not found, we try find one from the closest parent class
  585. else
  586. {
  587. $bFound = false;
  588. foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass)
  589. {
  590. // Trying to find the right list
  591. if (isset($aLists[$sParentClass]) && isset($aLists[$sParentClass][$sList]))
  592. {
  593. $aList = $aLists[$sParentClass][$sList];
  594. $bFound = true;
  595. break;
  596. }
  597. // Or the default list
  598. elseif (isset($aLists[$sParentClass]) && isset($aLists[$sParentClass]['default']))
  599. {
  600. $aList = $aLists[$sParentClass]['default'];
  601. $bFound = true;
  602. break;
  603. }
  604. }
  605. // If we have still not found one, we return a default form
  606. if (!$bFound)
  607. {
  608. $aForm = array(
  609. 'id' => 'default',
  610. 'type' => 'zlist',
  611. 'fields' => 'details',
  612. 'layout' => null
  613. );
  614. }
  615. }
  616. // If found, we flatten the list to keep only the attribute codes (not the rank)
  617. if ($aList !== null)
  618. {
  619. foreach ($aList as $aItem)
  620. {
  621. $aAttCodes[] = $aItem['att_code'];
  622. }
  623. }
  624. else
  625. {
  626. $aAttCodes = MetaModel::FlattenZList(MetaModel::GetZListItems($sClass, 'list'));
  627. }
  628. return $aAttCodes;
  629. }
  630. /**
  631. * Loads the bricks configuration from the module design XML and returns it as an hash array containing :
  632. * - 'brick' => array of PortalBrick objects
  633. * - 'bricks_total_width' => an integer used to create the home page grid
  634. *
  635. * @param \Silex\Application $oApp
  636. * @param ModuleDesign $oDesign
  637. * @return array
  638. * @throws Exception
  639. * @throws DOMFormatException
  640. */
  641. static protected function LoadBricksConfiguration(Application $oApp, ModuleDesign $oDesign)
  642. {
  643. $aPortalConf = array(
  644. 'bricks' => array(),
  645. 'bricks_total_width' => 0,
  646. 'bricks_home_count' => 0,
  647. 'bricks_navigation_menu_count' => 0
  648. );
  649. foreach ($oDesign->GetNodes('/module_design/bricks/brick') as $oBrickNode)
  650. {
  651. try
  652. {
  653. $sBrickClass = $oBrickNode->getAttribute('xsi:type');
  654. if (class_exists($sBrickClass))
  655. {
  656. $oBrick = new $sBrickClass();
  657. $oBrick->LoadFromXml($oBrickNode);
  658. static::LoadBrickSecurity($oBrick);
  659. // GLA : This didn't work has the modal flag was set for all instances of that brick
  660. // // Checking brick modal flag
  661. // if ($oBrick->GetModal())
  662. // {
  663. // // We have to extract / replace the array as we can modify $oApp values directly
  664. // $aRoutes = $oApp['combodo.portal.instance.routes'];
  665. // // Init brick's array if necessary
  666. // if (!isset($aRoutes[$oBrick->GetRouteName()]['navigation_menu_attr']))
  667. // {
  668. // $aRoutes[$oBrick->GetRouteName()]['navigation_menu_attr'] = array();
  669. // }
  670. // // Add modal datas for the brick
  671. // $aRoutes[$oBrick->GetRouteName()]['navigation_menu_attr']['data-toggle'] = 'modal';
  672. // $aRoutes[$oBrick->GetRouteName()]['navigation_menu_attr']['data-target'] = '#modal-for-all';
  673. // // Finally, replace array in $oApp
  674. // $oApp['combodo.portal.instance.routes'] = $aRoutes;
  675. // }
  676. // Checking brick security
  677. if ($oBrick->GetActive() && $oBrick->IsGrantedForProfiles(UserRights::ListProfiles()))
  678. {
  679. $aPortalConf['bricks'][] = $oBrick;
  680. $aPortalConf['bricks_total_width'] += $oBrick->GetWidth();
  681. if ($oBrick->GetVisibleHome())
  682. {
  683. $aPortalConf['bricks_home_count']++;
  684. }
  685. if ($oBrick->GetVisibleNavigationMenu())
  686. {
  687. $aPortalConf['bricks_navigation_menu_count']++;
  688. }
  689. }
  690. }
  691. else
  692. {
  693. throw new DOMFormatException('Unknown brick class "' . $sBrickClass . '" from xsi:type attribute', null, null, $oBrickNode);
  694. }
  695. }
  696. catch (DOMFormatException $e)
  697. {
  698. throw new Exception('Could not create brick (' . $sBrickClass . ') from XML because of a DOM problem : ' . $e->getMessage());
  699. }
  700. catch (Exception $e)
  701. {
  702. throw new Exception('Could not create brick (' . $sBrickClass . ') from XML : ' . $oBrickNode->Dump() . ' ' . $e->getMessage());
  703. }
  704. }
  705. // - Sorting bricks by rank
  706. $aPortalConf['bricks_ordering'] = array();
  707. // - Home
  708. $aPortalConf['bricks_ordering']['home'] = $aPortalConf['bricks'];
  709. usort($aPortalConf['bricks_ordering']['home'], function($a, $b)
  710. {
  711. return $a->GetRankHome() > $b->GetRankHome();
  712. });
  713. // - Navigation menu
  714. $aPortalConf['bricks_ordering']['navigation_menu'] = $aPortalConf['bricks'];
  715. usort($aPortalConf['bricks_ordering']['navigation_menu'], function($a, $b)
  716. {
  717. return $a->GetRankNavigationMenu() > $b->GetRankNavigationMenu();
  718. });
  719. return $aPortalConf;
  720. }
  721. /**
  722. * Loads the forms configuration from the module design XML and returns it as an array containing :
  723. * - <CLASSNAME> => array(
  724. * 'view'|'edit'|'create' => array(
  725. * 'fields_type' => 'custom_list'|'twig'|'zlist',
  726. * 'fields' => <CONTENT>
  727. * ),
  728. * ...
  729. * ),
  730. * ...
  731. *
  732. * @param \Silex\Application $oApp
  733. * @param ModuleDesign $oDesign
  734. * @return array
  735. * @throws Exception
  736. * @throws DOMFormatException
  737. */
  738. static protected function LoadFormsConfiguration(Application $oApp, ModuleDesign $oDesign)
  739. {
  740. $aForms = array();
  741. foreach ($oDesign->GetNodes('/module_design/forms/form') as $oFormNode)
  742. {
  743. try
  744. {
  745. // Parsing form id
  746. if ($oFormNode->getAttribute('id') === '')
  747. {
  748. throw new DOMFormatException('form tag must have an id attribute', null, null, $oFormNode);
  749. }
  750. // Parsing form object class
  751. if ($oFormNode->GetUniqueElement('class')->GetText() !== null)
  752. {
  753. $sFormClass = $oFormNode->GetUniqueElement('class')->GetText();
  754. // Parsing availables modes for that form (view, edit, create)
  755. if (($oFormNode->GetOptionalElement('modes') !== null) && ($oFormNode->GetOptionalElement('modes')->GetNodes('mode')->length > 0))
  756. {
  757. $aModes = array();
  758. foreach ($oFormNode->GetOptionalElement('modes')->GetNodes('mode') as $oModeNode)
  759. {
  760. if ($oModeNode->getAttribute('id') !== '')
  761. {
  762. $aModes[] = $oModeNode->getAttribute('id');
  763. }
  764. else
  765. {
  766. throw new DOMFormatException('Mode tag must have an id attribute', null, null, $oFormNode);
  767. }
  768. }
  769. }
  770. else
  771. {
  772. $aModes = array('view', 'edit', 'create');
  773. }
  774. // Parsing fields
  775. $aFields = array(
  776. 'id' => $oFormNode->getAttribute('id'),
  777. 'type' => null,
  778. 'fields' => null,
  779. 'layout' => null
  780. );
  781. // ... either enumerated fields ...
  782. if ($oFormNode->GetOptionalElement('fields') !== null)
  783. {
  784. $aFields['type'] = 'custom_list';
  785. $aFields['fields'] = array();
  786. foreach ($oFormNode->GetOptionalElement('fields')->GetNodes('field') as $oFieldNode)
  787. {
  788. $sFieldId = $oFieldNode->getAttribute('id');
  789. if ($sFieldId !== '')
  790. {
  791. $aField = array();
  792. // Parsing field options like read_only, hidden and mandatory
  793. if ($oFieldNode->GetOptionalElement('read_only'))
  794. {
  795. $aField['readonly'] = ($oFieldNode->GetOptionalElement('read_only')->GetText('true') === 'true') ? true : false;
  796. }
  797. if ($oFieldNode->GetOptionalElement('mandatory'))
  798. {
  799. $aField['mandatory'] = ($oFieldNode->GetOptionalElement('mandatory')->GetText('true') === 'true') ? true : false;
  800. }
  801. if ($oFieldNode->GetOptionalElement('hidden'))
  802. {
  803. $aField['hidden'] = ($oFieldNode->GetOptionalElement('hidden')->GetText('true') === 'true') ? true : false;
  804. }
  805. $aFields['fields'][$sFieldId] = $aField;
  806. }
  807. else
  808. {
  809. throw new DOMFormatException('Field tag must have an id attribute', null, null, $oFormNode);
  810. }
  811. }
  812. }
  813. // // ... or a specified zlist
  814. // elseif ($oFormNode->GetOptionalElement('presentation') !== null)
  815. // {
  816. // // This is not implemented yet as it was rejected until futher notice.
  817. // }
  818. // ... or the default zlist
  819. else
  820. {
  821. $aFields['type'] = 'zlist';
  822. $aFields['fields'] = 'details';
  823. }
  824. // Parsing presentation
  825. if ($oFormNode->GetOptionalElement('twig') !== null)
  826. {
  827. // Extracting the twig template and removing the first and last lines (twig tags)
  828. $sXml = $oDesign->saveXML($oFormNode->GetOptionalElement('twig'));
  829. $sXml = preg_replace('/^.+\n/', '', $sXml);
  830. $sXml = preg_replace('/\n.+$/', '', $sXml);
  831. $aFields['layout'] = array(
  832. 'type' => (preg_match('/\{\{|\{\#|\{\%/', $sXml) === 1) ? 'twig' : 'xhtml',
  833. 'content' => $sXml
  834. );
  835. }
  836. // Adding form for each class / mode
  837. foreach ($aModes as $sMode)
  838. {
  839. if (!isset($aForms[$sFormClass]))
  840. {
  841. $aForms[$sFormClass] = array();
  842. }
  843. if (!isset($aForms[$sFormClass][$sMode]))
  844. {
  845. $aForms[$sFormClass][$sMode] = $aFields;
  846. }
  847. else
  848. {
  849. throw new DOMFormatException('There is already a form for the class "' . $sFormClass . '" in "' . $sMode . '"', null, null, $oFormNode);
  850. }
  851. }
  852. }
  853. else
  854. {
  855. throw new DOMFormatException('Class tag must be defined', null, null, $oFormNode);
  856. }
  857. }
  858. catch (DOMFormatException $e)
  859. {
  860. throw new Exception('Could not create from [id="' . $oFormNode->getAttribute('id') . '"] from XML because of a DOM problem : ' . $e->getMessage());
  861. }
  862. catch (Exception $e)
  863. {
  864. throw new Exception('Could not create from from XML : ' . $oFormNode->Dump() . ' ' . $e->getMessage());
  865. }
  866. }
  867. return $aForms;
  868. }
  869. /**
  870. * Loads the scopes configuration from the module design XML
  871. *
  872. * @param \Silex\Application $oApp
  873. * @param ModuleDesign $oDesign
  874. */
  875. static protected function LoadScopesConfiguration(Application $oApp, ModuleDesign $oDesign)
  876. {
  877. $oApp['scope_validator']->Init($oDesign->GetNodes('/module_design/classes/class'));
  878. }
  879. /**
  880. * Loads the context helper from the module design XML
  881. *
  882. * @param \Silex\Application $oApp
  883. * @param ModuleDesign $oDesign
  884. */
  885. static protected function LoadActionRulesConfiguration(Application $oApp, ModuleDesign $oDesign)
  886. {
  887. $oApp['context_manipulator']->Init($oDesign->GetNodes('/module_design/action_rules/action_rule'));
  888. }
  889. /**
  890. * 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
  891. *
  892. * @param \Silex\Application $oApp
  893. * @param ModuleDesign $oDesign
  894. * @return array
  895. */
  896. static protected function LoadListsConfiguration(Application $oApp, ModuleDesign $oDesign)
  897. {
  898. $iDefaultItemRank = 0;
  899. $aClassesLists = array();
  900. // Parsing XML file
  901. // - Each classes
  902. foreach ($oDesign->GetNodes('/module_design/classes/class') as $oClassNode)
  903. {
  904. $aClassLists = array();
  905. $sClassId = $oClassNode->getAttribute('id');
  906. if ($sClassId === null)
  907. {
  908. throw new DOMFormatException('Class tag must have an id attribute', null, null, $oClassNode);
  909. }
  910. // - Each lists
  911. foreach ($oClassNode->GetNodes('./lists/list') as $oListNode)
  912. {
  913. $aListItems = array();
  914. $sListId = $oListNode->getAttribute('id');
  915. if ($sListId === null)
  916. {
  917. throw new DOMFormatException('List tag of "' . $sClassId . '" class must have an id attribute', null, null, $oListNode);
  918. }
  919. // - Each items
  920. foreach ($oListNode->GetNodes('./items/item') as $oItemNode)
  921. {
  922. $sItemId = $oItemNode->getAttribute('id');
  923. if ($sItemId === null)
  924. {
  925. throw new DOMFormatException('Item tag of "' . $sItemId . '" list must have an id attribute', null, null, $oItemNode);
  926. }
  927. $aItem = array(
  928. 'att_code' => $sItemId,
  929. 'rank' => $iDefaultItemRank
  930. );
  931. $oRankNode = $oItemNode->GetOptionalElement('rank');
  932. if ($oRankNode !== null)
  933. {
  934. $aItem['rank'] = $oRankNode->GetText($iDefaultItemRank);
  935. }
  936. $aListItems[] = $aItem;
  937. }
  938. // - Sorting list items by rank
  939. usort($aListItems, function($a, $b)
  940. {
  941. return $a['rank'] > $b['rank'];
  942. });
  943. $aClassLists[$sListId] = $aListItems;
  944. }
  945. // - Adding class only if it has at least one list
  946. if (!empty($aClassLists))
  947. {
  948. $aClassesLists[$sClassId] = $aClassLists;
  949. }
  950. }
  951. // Creating lists for child classes
  952. // 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
  953. /* $aParentClasses = array_keys($aClassesLists);
  954. foreach ($aParentClasses as $sParentClass)
  955. {
  956. foreach (MetaModel::EnumChildClasses($sParentClass) as $sChildClass)
  957. {
  958. // If the child class is not in the scope, we are going to try to add it
  959. if (!in_array($sChildClass, $aParentClasses))
  960. {
  961. $aClassesLists[$sChildClass] = $aClassesLists[$sParentClass];
  962. }
  963. }
  964. } */
  965. return $aClassesLists;
  966. }
  967. }
  968. ?>