diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/helpers/applicationhelper.class.inc.php b/datamodels/2.x/itop-portal-base/portal-silex/src/helpers/applicationhelper.class.inc.php deleted file mode 100644 index 62b664e16b..0000000000 --- a/datamodels/2.x/itop-portal-base/portal-silex/src/helpers/applicationhelper.class.inc.php +++ /dev/null @@ -1,1502 +0,0 @@ - - -namespace Combodo\iTop\Portal\Helper; - -use ApplicationContext; -use cmdbAbstractObject; -use Combodo\iTop\Portal\Brick\AbstractBrick; -use Combodo\iTop\Portal\Brick\PortalBrick; -use DBObjectSearch; -use DBObjectSet; -use Dict; -use DOMFormatException; -use Exception; -use iPortalUIExtension; -use IssueLog; -use MetaModel; -use ModuleDesign; -use Silex\Application; -use Symfony\Component\Debug\ErrorHandler; -use Symfony\Component\Debug\ExceptionHandler; -use Symfony\Component\HttpKernel\Exception\HttpException; -use Twig_Environment; -use Twig_SimpleFilter; -use Twig_SimpleFunction; -use UserRights; -use utils; - -/** - * Contains static methods to help loading / registering classes of the application. - * Mostly used for Controllers / Routers / Entities initialization. - * - * @author Guillaume Lajarige - */ -class ApplicationHelper -{ - const FORM_ENUM_DISPLAY_MODE_COSY = 'cosy'; - const FORM_ENUM_DISPLAY_MODE_COMPACT = 'compact'; - const FORM_DEFAULT_DISPLAY_MODE = self::FORM_ENUM_DISPLAY_MODE_COSY; - const FORM_DEFAULT_ALWAYS_SHOW_SUBMIT = false; - - /** - * Loads classes from the base portal - * - * @param string $sScannedDir Directory to load the files from - * @param string $sFilePattern Pattern of files to load - * @param string $sType Type of files to load, used only in the Exception message, can be anything - * - * @throws \Exception - */ - public static function LoadClasses($sScannedDir, $sFilePattern, $sType) - { - // Loading classes from base portal - foreach (scandir($sScannedDir) as $sFile) - { - if (strpos($sFile, $sFilePattern) !== false && file_exists($sFilepath = $sScannedDir.'/'.$sFile)) - { - try - { - require_once $sFilepath; - } - catch (Exception $e) - { - throw new Exception('Error while trying to load '.$sType.' '.$sFile); - } - } - } - } - - /** - * Loads controllers from the base portal - * - * @param string $sScannedDir Directory to load the controllers from - * - * @throws \Exception - */ - public static function LoadControllers($sScannedDir = null) - { - if ($sScannedDir === null) - { - $sScannedDir = __DIR__.'/../controllers'; - } - - // Loading controllers from base portal (those from modules have already been loaded by module.xxx.php files) - self::LoadClasses($sScannedDir, 'controller.class.inc.php', 'controller'); - } - - /** - * Loads routers from the base portal - * - * @param string $sScannedDir Directory to load the routers from - * - * @throws \Exception - */ - public static function LoadRouters($sScannedDir = null) - { - if ($sScannedDir === null) - { - $sScannedDir = __DIR__.'/../routers'; - } - - // Loading routers from base portal (those from modules have already been loaded by module.xxx.php files) - self::LoadClasses($sScannedDir, 'router.class.inc.php', 'router'); - } - - /** - * Loads bricks from the base portal - * - * @param string $sScannedDir Directory to load the bricks from - * - * @throws \Exception - */ - public static function LoadBricks($sScannedDir = null) - { - if ($sScannedDir === null) - { - $sScannedDir = __DIR__.'/../entities'; - } - - // Loading bricks from base portal (those from modules have already been loaded by module.xxx.php files) - self::LoadClasses($sScannedDir, 'brick.class.inc.php', 'brick'); - } - - /** - * Loads form managers from the base portal - * - * @param string $sScannedDir Directory to load the managers from - * - * @throws \Exception - */ - public static function LoadFormManagers($sScannedDir = null) - { - if ($sScannedDir === null) - { - $sScannedDir = __DIR__.'/../forms'; - } - - // Loading form managers from base portal (those from modules have already been loaded by module.xxx.php files) - self::LoadClasses($sScannedDir, 'formmanager.class.inc.php', 'brick'); - } - - /** - * Registers routes in the Silex Application from all declared Router classes - * - * @param \Silex\Application $oApp - * - * @throws \Exception - */ - public static function RegisterRoutes(Application $oApp) - { - $aAllRoutes = array(); - - foreach (get_declared_classes() as $sPHPClass) - { - if (is_subclass_of($sPHPClass, 'Combodo\\iTop\\Portal\\Router\\AbstractRouter')) - { - try - { - // Registering to Silex Application - $sPHPClass::RegisterAllRoutes($oApp); - - // Registering them together so we can access them from everywhere - foreach ($sPHPClass::GetRoutes() as $aRoute) - { - $aAllRoutes[$aRoute['bind']] = $aRoute; - } - } - catch (Exception $e) - { - throw new Exception('Error while trying to register routes'); - } - } - } - - $oApp['combodo.portal.instance.routes'] = $aAllRoutes; - } - - /** - * Returns all registered routes for the current portal instance - * - * @param \Silex\Application $oApp - * @param boolean $bNamesOnly If set to true, function will return only the routes' names, not the objects - * - * @return array - */ - public static function GetRoutes(Application $oApp, $bNamesOnly = false) - { - return ($bNamesOnly) ? array_keys($oApp['combodo.portal.instance.routes']) : $oApp['combodo.portal.instance.routes']; - } - - /** - * Registers Twig extensions such as filters or functions. - * It allows us to access some stuff directly in twig. - * - * @param \Twig_Environment $oTwigEnv - */ - public static function RegisterTwigExtensions(Twig_Environment &$oTwigEnv) - { - // Filter to translate a string via the Dict::S function - // Usage in twig: {{ 'String:ToTranslate'|dict_s }} - $oTwigEnv->addFilter(new Twig_SimpleFilter('dict_s', - function ($sStringCode, $sDefault = null, $bUserLanguageOnly = false) { - return Dict::S($sStringCode, $sDefault, $bUserLanguageOnly); - }) - ); - - // Filter to format a string via the Dict::Format function - // Usage in twig: {{ 'String:ToTranslate'|dict_format() }} - $oTwigEnv->addFilter(new Twig_SimpleFilter('dict_format', - function ($sStringCode, $sParam01 = null, $sParam02 = null, $sParam03 = null, $sParam04 = null) { - return Dict::Format($sStringCode, $sParam01, $sParam02, $sParam03, $sParam04); - }) - ); - - // Filter to enable base64 encode/decode - // Usage in twig: {{ 'String to encode'|base64_encode }} - $oTwigEnv->addFilter(new Twig_SimpleFilter('base64_encode', 'base64_encode')); - $oTwigEnv->addFilter(new Twig_SimpleFilter('base64_decode', 'base64_decode')); - - // Filter to enable json decode (encode already exists) - // Usage in twig: {{ aSomeArray|json_decode }} - $oTwigEnv->addFilter(new Twig_SimpleFilter('json_decode', function ($sJsonString, $bAssoc = false) { - return json_decode($sJsonString, $bAssoc); - }) - ); - - // Filter to add itopversion to an url - $oTwigEnv->addFilter(new Twig_SimpleFilter('add_itop_version', function ($sUrl) { - if (strpos($sUrl, '?') === false) - { - $sUrl = $sUrl."?itopversion=".ITOP_VERSION; - } - else - { - $sUrl = $sUrl."&itopversion=".ITOP_VERSION; - } - - return $sUrl; - })); - - // Filter to add a module's version to an url - $oTwigEnv->addFilter(new Twig_SimpleFilter('add_module_version', function ($sUrl, $sModuleName) { - $sModuleVersion = utils::GetCompiledModuleVersion($sModuleName); - - if (strpos($sUrl, '?') === false) - { - $sUrl = $sUrl."?moduleversion=".$sModuleVersion; - } - else - { - $sUrl = $sUrl."&moduleversion=".$sModuleVersion; - } - - return $sUrl; - })); - - // Function to check our current environment - // Usage in twig: {% if is_development_environment() %} - $oTwigEnv->addFunction(new Twig_SimpleFunction('is_development_environment', function() - { - return utils::IsDevelopmentEnvironment(); - })); - - // Function to get configuration parameter - // Usage in twig: {{ get_config_parameter('foo') }} - $oTwigEnv->addFunction(new Twig_SimpleFunction('get_config_parameter', function($sParamName) - { - $oConfig = MetaModel::GetConfig(); - return $oConfig->Get($sParamName); - })); - } - - /** - * Registers an exception handler that will intercept controllers exceptions and display them in a nice template. - * Note : It is only active when $oApp['debug'] is false - * - * @param Application $oApp - */ - public static function RegisterExceptionHandler(Application $oApp) - { - // Intercepting fatal errors and exceptions - ErrorHandler::register(); - ExceptionHandler::register(($oApp['debug'] === true)); - - // Intercepting manually aborted request - if (1 || !$oApp['debug']) - { - $oApp->error(function (Exception $oException /*, Request $oRequest*/) use ($oApp) { - $iErrorCode = ($oException instanceof HttpException) ? $oException->getStatusCode() : 500; - - $aData = array( - 'exception' => $oException, - 'code' => $iErrorCode, - 'error_title' => '', - 'error_message' => $oException->getMessage() - ); - - switch ($iErrorCode) - { - case 404: - $aData['error_title'] = Dict::S('Error:HTTP:404'); - break; - default: - $aData['error_title'] = Dict::S('Error:HTTP:500'); - break; - } - - IssueLog::Error($aData['error_title'].' : '.$aData['error_message']); - - if ($oApp['request_stack']->getCurrentRequest()->isXmlHttpRequest()) - { - $oResponse = $oApp->json($aData, $iErrorCode); - } - else - { - // Preparing debug trace - $aSteps = array(); - foreach ($oException->getTrace() as $aStep) - { - // - Default file name - if (!isset($aStep['file'])) - { - $aStep['file'] = ''; - } - $aFileParts = explode('\\', $aStep['file']); - // - Default line number - if (!isset($aStep['line'])) - { - $aStep['line'] = 'unknown'; - } - // - Default class name - if (isset($aStep['class']) && isset($aStep['function']) && isset($aStep['type'])) - { - $aClassParts = explode('\\', $aStep['class']); - $sClassName = $aClassParts[count($aClassParts) - 1]; - $sClassFQ = $aStep['class']; - - $aArgsAsString = array(); - foreach ($aStep['args'] as $arg) - { - if (is_array($arg)) - { - $aArgsAsString[] = 'array(...)'; - } - elseif (is_object($arg)) - { - $aArgsAsString[] = 'object('.get_class($arg).')'; - } - else - { - $aArgsAsString[] = $arg; - } - } - - $sFunctionCall = $sClassName.$aStep['type'].$aStep['function'].'('.implode(', ', - $aArgsAsString).')'; - } - else - { - $sClassName = null; - $sClassFQ = null; - $sFunctionCall = null; - } - - $aSteps[] = array( - 'file_fq' => $aStep['file'], - 'file_name' => $aFileParts[count($aFileParts) - 1], - 'line' => $aStep['line'], - 'class_name' => $sClassName, - 'class_fq' => $sClassFQ, - 'function_call' => $sFunctionCall, - ); - } - - $aData['debug_trace_steps'] = $aSteps; - - $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/errors/layout.html.twig', - $aData); - } - - return $oResponse; - }); - } - } - - /** - * Loads the portal instance configuration from its module design into the Silex application - * - * @param \Silex\Application $oApp - * - * @throws Exception - */ - public static function LoadPortalConfiguration(Application $oApp) - { - try - { - // Loading file - if (!defined('PORTAL_ID')) - { - throw new Exception('Cannot load module design, Portal ID is not defined'); - } - $oDesign = new ModuleDesign(PORTAL_ID); - - // Parsing file - // - Default values - $aPortalConf = array( - 'properties' => array( - 'id' => PORTAL_ID, - 'name' => 'Page:DefaultTitle', - 'logo' => (file_exists(MODULESROOT.'branding/portal-logo.png')) ? utils::GetAbsoluteUrlModulesRoot().'branding/portal-logo.png' : '../images/logo-itop-dark-bg.svg', - 'themes' => array( - 'bootstrap' => 'itop-portal-base/portal/web/css/bootstrap-theme-combodo.scss', - 'portal' => 'itop-portal-base/portal/web/css/portal.scss', - 'others' => array(), - ), - 'templates' => array( - 'layout' => 'itop-portal-base/portal/src/views/layout.html.twig', - 'home' => 'itop-portal-base/portal/src/views/home/layout.html.twig' - ), - 'urlmaker_class' => null, - 'triggers_query' => null, - 'attachments' => array( - 'allow_delete' => true - ), - 'allowed_portals' => array( - 'opening_mode' => null, - ), - ), - 'portals' => array(), - 'forms' => array(), - 'ui_extensions' => array( - 'css_files' => array(), - 'css_inline' => null, - 'js_files' => array(), - 'js_inline' => null, - 'html' => array(), - ), - 'bricks' => array(), - 'bricks_total_width' => 0, - ); - // - Global portal properties - foreach ($oDesign->GetNodes('/module_design/properties/*') as $oPropertyNode) - { - switch ($oPropertyNode->nodeName) - { - case 'name': - case 'urlmaker_class': - case 'triggers_query': - $aPortalConf['properties'][$oPropertyNode->nodeName] = $oPropertyNode->GetText($aPortalConf['properties'][$oPropertyNode->nodeName]); - break; - case 'logo': - $aPortalConf['properties'][$oPropertyNode->nodeName] = $oPropertyNode->GetText($aPortalConf['properties'][$oPropertyNode->nodeName]); - break; - case 'themes': - case 'templates': - foreach ($oPropertyNode->GetNodes('template|theme') as $oSubNode) - { - if (!$oSubNode->hasAttribute('id') || $oSubNode->GetText(null) === null) - { - throw new DOMFormatException('Tag '.$oSubNode->nodeName.' must have a "id" attribute as well as a value', - null, null, $oSubNode); - } - - $sNodeId = $oSubNode->getAttribute('id'); - switch ($oSubNode->nodeName) - { - case 'theme': - switch ($sNodeId) - { - case 'bootstrap': - case 'portal': - case 'custom': - $aPortalConf['properties']['themes'][$sNodeId] = $oSubNode->GetText(null); - break; - default: - $aPortalConf['properties']['themes']['others'][] = $oSubNode->GetText(null); - break; - } - break; - case 'template': - switch ($sNodeId) - { - case 'layout': - case 'home': - $aPortalConf['properties']['templates'][$sNodeId] = $oSubNode->GetText(null); - break; - default: - throw new DOMFormatException('Value "'.$sNodeId.'" is not handled for template[@id]', - null, null, $oSubNode); - break; - } - break; - } - } - break; - case 'attachments': - foreach ($oPropertyNode->GetNodes('*') as $oSubNode) - { - switch ($oSubNode->nodeName) - { - case 'allow_delete': - $sValue = $oSubNode->GetText(); - // If the text is null, we keep the default value - // Else we set it - if ($sValue !== null) - { - $aPortalConf['properties']['attachments'][$oSubNode->nodeName] = ($sValue === 'true') ? true : false; - } - break; - } - } - break; - case 'allowed_portals': - foreach ($oPropertyNode->GetNodes('*') as $oSubNode) - { - switch ($oSubNode->nodeName) - { - case 'opening_mode': - $sValue = $oSubNode->GetText(); - // If the text is null, we keep the default value - // Else we set it - if ($sValue !== null) - { - $aPortalConf['properties']['allowed_portals'][$oSubNode->nodeName] = ($sValue === 'self') ? 'self' : 'tab'; - } - break; - } - } - break; - } - } - // - Rectifying portal logo url - $sLogoUri = $aPortalConf['properties']['logo']; - if (!preg_match('/^http/', $sLogoUri)) - { - // We prefix it with the server base url - $sLogoUri = utils::GetAbsoluteUrlAppRoot().'env-'.utils::GetCurrentEnvironment().'/'.$sLogoUri; - } - $aPortalConf['properties']['logo'] = $sLogoUri; - // - User allowed portals - $aPortalConf['portals'] = UserRights::GetAllowedPortals(); - // - Bricks - $aPortalConf = static::LoadBricksConfiguration($oApp, $oDesign) + $aPortalConf; - // - Forms - $aPortalConf['forms'] = static::LoadFormsConfiguration($oApp, $oDesign); - // - Scopes - static::LoadScopesConfiguration($oApp, $oDesign); - // - Lifecycle - static::LoadLifecycleConfiguration($oApp, $oDesign); - // - Presentation lists - $aPortalConf['lists'] = static::LoadListsConfiguration($oApp, $oDesign); - // - UI extensions - $aPortalConf['ui_extensions'] = static::LoadUIExtensions($oApp); - // - Action rules - static::LoadActionRulesConfiguration($oApp, $oDesign); - // - Setting UrlMakerClass - if ($aPortalConf['properties']['urlmaker_class'] !== null) - { - ApplicationContext::SetUrlMakerClass($aPortalConf['properties']['urlmaker_class']); - } - // - Generating CSS files - $aImportPaths = array($oApp['combodo.portal.base.absolute_path'].'css/'); - foreach ($aPortalConf['properties']['themes'] as $key => $value) - { - if (!is_array($value)) - { - $aPortalConf['properties']['themes'][$key] = $oApp['combodo.absolute_url'].utils::GetCSSFromSASS('env-'.utils::GetCurrentEnvironment().'/'.$value, - $aImportPaths); - } - else - { - $aValues = array(); - foreach ($value as $sSubvalue) - { - $aValues[] = $oApp['combodo.absolute_url'].utils::GetCSSFromSASS('env-'.utils::GetCurrentEnvironment().'/'.$sSubvalue, - $aImportPaths); - } - $aPortalConf['properties']['themes'][$key] = $aValues; - } - } - - $oApp['combodo.portal.instance.conf'] = $aPortalConf; - } - catch (Exception $e) - { - throw new Exception('Error while parsing portal configuration file : '.$e->getMessage()); - } - } - - /** - * Loads the current user and stores it in the Silex application so we can use it wherever in the application - * - * @param \Silex\Application $oApp - * - * @throws \Exception - */ - public static function LoadCurrentUser(Application $oApp) - { - // User - $oUser = UserRights::GetUserObject(); - if ($oUser === null) - { - throw new Exception('Could not load connected user.'); - } - - $oApp['combodo.current_user'] = $oUser; - - // Contact - $sContactPhotoUrl = $oApp['combodo.portal.base.absolute_url'].'img/user-profile-default-256px.png'; - // - Checking if we can load the contact - try - { - $oContact = UserRights::GetContactObject(); - } - catch (Exception $e) - { - $oAllowedOrgSet = $oUser->Get('allowed_org_list'); - if ($oAllowedOrgSet->Count() > 0) - { - throw new Exception('Could not load contact related to connected user. (Tip: Make sure the contact\'s organization is among the user\'s allowed organizations)'); - } - else - { - throw new Exception('Could not load contact related to connected user.'); - } - } - // - Retrieving picture - if ($oContact) - { - if (MetaModel::IsValidAttCode(get_class($oContact), 'picture')) - { - $oImage = $oContact->Get('picture'); - if (is_object($oImage) && !$oImage->IsEmpty()) - { - $sContactPhotoUrl = $oImage->GetDownloadURL(get_class($oContact), $oContact->GetKey(), 'picture'); - } - else - { - $sContactPhotoUrl = MetaModel::GetAttributeDef(get_class($oContact), - 'picture')->Get('default_image'); - } - } - } - $oApp['combodo.current_contact.photo_url'] = $sContactPhotoUrl; - } - - /** - * Loads the brick's security from the OQL queries to profiles arrays - * - * @param \Combodo\iTop\Portal\Brick\AbstractBrick $oBrick - * - * @throws \Exception - */ - public static function LoadBrickSecurity(AbstractBrick &$oBrick) - { - try - { - // Allowed profiles - if ($oBrick->GetAllowedProfilesOql() !== null && $oBrick->GetAllowedProfilesOql() !== '') - { - $oSearch = DBObjectSearch::FromOQL($oBrick->GetAllowedProfilesOql()); - $oSet = new DBObjectSet($oSearch); - while ($oProfile = $oSet->Fetch()) - { - $oBrick->AddAllowedProfile($oProfile->Get('name')); - } - } - - // Denied profiles - if ($oBrick->GetDeniedProfilesOql() !== null && $oBrick->GetDeniedProfilesOql() !== '') - { - $oSearch = DBObjectSearch::FromOQL($oBrick->GetDeniedProfilesOql()); - $oSet = new DBObjectSet($oSearch); - while ($oProfile = $oSet->Fetch()) - { - $oBrick->AddDeniedProfile($oProfile->Get('name')); - } - } - } - catch (Exception $e) - { - throw new Exception('Error while loading security from '.$oBrick->GetId().' brick'); - } - } - - /** - * Finds an AbstractBrick loaded in the $oApp instance configuration from its ID. - * - * @param \Silex\Application $oApp - * @param string $sBrickId - * - * @return \Combodo\iTop\Portal\Brick\PortalBrick - * - * @throws \Exception - */ - public static function GetLoadedBrickFromId(Application $oApp, $sBrickId) - { - foreach ($oApp['combodo.portal.instance.conf']['bricks'] as $oBrick) - { - if ($oBrick->GetId() === $sBrickId) - { - return $oBrick; - } - } - - throw new Exception('Brick with id = "'.$sBrickId.'" was not found among loaded bricks.'); - } - - /** - * Return the form properties for the $sClassname in $sMode. - * - * If not found, tries to find one from the closest parent class. - * Else returns a default form based on zlist 'details' - * - * @param Application $oApp - * @param string $sClass Object class to find a form for - * @param string $sMode Form mode to find (view|edit|create) - * - * @return array - * - * @throws \CoreException - */ - public static function GetLoadedFormFromClass(Application $oApp, $sClass, $sMode) - { - $aForms = $oApp['combodo.portal.instance.conf']['forms']; - - // We try to find the form for that class - if (isset($aForms[$sClass]) && isset($aForms[$sClass][$sMode])) - { - $aForm = $aForms[$sClass][$sMode]; - } - // If not found, we try find one from the closest parent class - else - { - $bFound = false; - foreach (MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_EXCLUDELEAF, false) as $sParentClass) - { - if (isset($aForms[$sParentClass]) && isset($aForms[$sParentClass][$sMode])) - { - $aForm = $aForms[$sParentClass][$sMode]; - $bFound = true; - break; - } - } - - // If we have still not found one, we return a default form - if (!$bFound) - { - $aForm = static::GenerateDefaultFormForClass($sClass); - } - } - - return $aForm; - } - - /** - * Return the attribute list for the $sClassname in $sList. - * - * If not found, tries to find one from the closest parent class. - * Else returns a default attribute list based on zlist 'list' - * - * @param Application $oApp - * @param string $sClass Object class to find a list for - * @param string $sList List name to find - * - * @return array Array of attribute codes - * - * @throws \CoreException - */ - public static function GetLoadedListFromClass(Application $oApp, $sClass, $sList = 'default') - { - $aLists = $oApp['combodo.portal.instance.conf']['lists']; - $aList = null; - $aAttCodes = array(); - - // We try to find the list for that class - if (isset($aLists[$sClass]) && isset($aLists[$sClass][$sList])) - { - $aList = $aLists[$sClass][$sList]; - } - // Else we try to found the default list for that class - elseif (isset($aLists[$sClass]) && isset($aLists[$sClass]['default'])) - { - $aList = $aLists[$sClass]['default']; - } - // If not found, we try find one from the closest parent class - else - { - foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass) - { - // Trying to find the right list - if (isset($aLists[$sParentClass]) && isset($aLists[$sParentClass][$sList])) - { - $aList = $aLists[$sParentClass][$sList]; - break; - } - // Or the default list - elseif (isset($aLists[$sParentClass]) && isset($aLists[$sParentClass]['default'])) - { - $aList = $aLists[$sParentClass]['default']; - break; - } - } - } - - // If found, we flatten the list to keep only the attribute codes (not the rank) - if ($aList !== null) - { - foreach ($aList as $aItem) - { - $aAttCodes[] = $aItem['att_code']; - } - } - else - { - $aAttCodes = MetaModel::FlattenZList(MetaModel::GetZListItems($sClass, 'list')); - } - - return $aAttCodes; - } - - /** - * Loads the bricks configuration from the module design XML and returns it as an hash array containing : - * - 'brick' => array of PortalBrick objects - * - 'bricks_total_width' => an integer used to create the home page grid - * - * @param \Silex\Application $oApp - * @param \ModuleDesign $oDesign - * - * @return array - * - * @throws \Exception - */ - protected static function LoadBricksConfiguration(Application $oApp, ModuleDesign $oDesign) - { - $aPortalConf = array( - 'bricks' => array(), - 'bricks_total_width' => 0, - 'bricks_home_count' => 0, - 'bricks_navigation_menu_count' => 0 - ); - - foreach ($oDesign->GetNodes('/module_design/bricks/brick') as $oBrickNode) - { - $sBrickClass = $oBrickNode->getAttribute('xsi:type'); - try - { - if (class_exists($sBrickClass)) - { - /** @var \Combodo\iTop\Portal\Brick\PortalBrick $oBrick */ - $oBrick = new $sBrickClass(); - $oBrick->LoadFromXml($oBrickNode); - static::LoadBrickSecurity($oBrick); - - // Checking brick security - if ($oBrick->GetActive() && $oBrick->IsGrantedForProfiles(UserRights::ListProfiles())) - { - $aPortalConf['bricks'][] = $oBrick; - $aPortalConf['bricks_total_width'] += $oBrick->GetWidth(); - if ($oBrick->GetVisibleHome()) - { - $aPortalConf['bricks_home_count']++; - } - if ($oBrick->GetVisibleNavigationMenu()) - { - $aPortalConf['bricks_navigation_menu_count']++; - } - } - } - else - { - throw new DOMFormatException('Unknown brick class "'.$sBrickClass.'" from xsi:type attribute', null, - null, $oBrickNode); - } - } - catch (DOMFormatException $e) - { - throw new Exception('Could not create brick ('.$sBrickClass.') from XML because of a DOM problem : '.$e->getMessage()); - } - catch (Exception $e) - { - throw new Exception('Could not create brick ('.$sBrickClass.') from XML : '.$oBrickNode->Dump().' '.$e->getMessage()); - } - } - // - Sorting bricks by rank - $aPortalConf['bricks_ordering'] = array(); - // - Home - $aPortalConf['bricks_ordering']['home'] = $aPortalConf['bricks']; - usort($aPortalConf['bricks_ordering']['home'], function (PortalBrick $a, PortalBrick $b) { - return $a->GetRankHome() > $b->GetRankHome(); - }); - // - Navigation menu - $aPortalConf['bricks_ordering']['navigation_menu'] = $aPortalConf['bricks']; - usort($aPortalConf['bricks_ordering']['navigation_menu'], function (PortalBrick $a, PortalBrick $b) { - return $a->GetRankNavigationMenu() > $b->GetRankNavigationMenu(); - }); - - return $aPortalConf; - } - - /** - * Loads the forms configuration from the module design XML and returns it as an array containing : - * - => array( - * 'view'|'edit'|'create' => array( - * 'fields_type' => 'custom_list'|'twig'|'zlist', - * 'fields' => - * ), - * ... - * ), - * ... - * - * @param \Silex\Application $oApp - * @param \ModuleDesign $oDesign - * - * @return array - * - * @throws \Exception - */ - protected static function LoadFormsConfiguration(Application $oApp, ModuleDesign $oDesign) - { - $aForms = array(); - - foreach ($oDesign->GetNodes('/module_design/forms/form') as $oFormNode) - { - try - { - // Parsing form id - if ($oFormNode->getAttribute('id') === '') - { - throw new DOMFormatException('form tag must have an id attribute', null, null, $oFormNode); - } - - // Parsing form object class - if ($oFormNode->GetUniqueElement('class')->GetText() !== null) - { - // Parsing class - $sFormClass = $oFormNode->GetUniqueElement('class')->GetText(); - - // Parsing properties - $aFormProperties = array( - 'display_mode' => static::FORM_DEFAULT_DISPLAY_MODE, - 'always_show_submit' => static::FORM_DEFAULT_ALWAYS_SHOW_SUBMIT, - ); - if ($oFormNode->GetOptionalElement('properties') !== null) - { - foreach ($oFormNode->GetOptionalElement('properties')->childNodes as $oPropertyNode) - { - switch ($oPropertyNode->nodeName) - { - case 'display_mode': - $aFormProperties['display_mode'] = $oPropertyNode->GetText(static::FORM_DEFAULT_DISPLAY_MODE); - break; - case 'always_show_submit': - $aFormProperties['always_show_submit'] = ($oPropertyNode->GetText('false') === 'true') ? true : false; - break; - } - } - } - - // Parsing availables modes for that form (view, edit, create, apply_stimulus) - $aFormStimuli = array(); - if (($oFormNode->GetOptionalElement('modes') !== null) && ($oFormNode->GetOptionalElement('modes')->GetNodes('mode')->length > 0)) - { - $aModes = array(); - foreach ($oFormNode->GetOptionalElement('modes')->GetNodes('mode') as $oModeNode) - { - if ($oModeNode->getAttribute('id') !== '') - { - $aModes[] = $oModeNode->getAttribute('id'); - } - else - { - throw new DOMFormatException('Mode tag must have an id attribute', null, null, - $oFormNode); - } - - // If apply_stimulus mode, checking if stimuli are defined - if ($oModeNode->getAttribute('id') === 'apply_stimulus') - { - $oStimuliNode = $oModeNode->GetOptionalElement('stimuli'); - // if stimuli are defined, we overwrite the form that could have been set by the generic form - if ($oStimuliNode !== null) - { - foreach ($oStimuliNode->GetNodes('stimulus') as $oStimulusNode) - { - $sStimulusCode = $oStimulusNode->getAttribute('id'); - - // Removing default form is present (in case the default forms were parsed before the current one (from current or parent class)) - if (isset($aForms[$sFormClass]['apply_stimulus'][$sStimulusCode])) - { - unset($aForms[$sFormClass]['apply_stimulus'][$sStimulusCode]); - } - - $aFormStimuli[] = $oStimulusNode->getAttribute('id'); - } - } - } - } - } - else - { - // If no mode was specified, we set it all but stimuli as it would have no sense that every transition forms - // have as many fields displayed as a regular edit form for example. - $aModes = array('view', 'edit', 'create'); - } - - // Parsing fields - $aFields = array( - 'id' => $oFormNode->getAttribute('id'), - 'type' => null, - 'properties' => $aFormProperties, - 'fields' => null, - 'layout' => null - ); - // ... either enumerated fields ... - if ($oFormNode->GetOptionalElement('fields') !== null) - { - $aFields['type'] = 'custom_list'; - $aFields['fields'] = array(); - - foreach ($oFormNode->GetOptionalElement('fields')->GetNodes('field') as $oFieldNode) - { - $sFieldId = $oFieldNode->getAttribute('id'); - if ($sFieldId !== '') - { - $aField = array(); - // Parsing field options like read_only, hidden and mandatory - if ($oFieldNode->GetOptionalElement('read_only')) - { - $aField['readonly'] = ($oFieldNode->GetOptionalElement('read_only')->GetText('true') === 'true') ? true : false; - } - if ($oFieldNode->GetOptionalElement('mandatory')) - { - $aField['mandatory'] = ($oFieldNode->GetOptionalElement('mandatory')->GetText('true') === 'true') ? true : false; - } - if ($oFieldNode->GetOptionalElement('hidden')) - { - $aField['hidden'] = ($oFieldNode->GetOptionalElement('hidden')->GetText('true') === 'true') ? true : false; - } - - $aFields['fields'][$sFieldId] = $aField; - } - else - { - throw new DOMFormatException('Field tag must have an id attribute', null, null, - $oFormNode); - } - } - } - // ... or the default zlist - else - { - $aFields['type'] = 'zlist'; - $aFields['fields'] = 'details'; - } - - // Parsing presentation - if ($oFormNode->GetOptionalElement('twig') !== null) - { - // Extracting the twig template and removing the first and last lines (twig tags) - $sXml = $oDesign->saveXML($oFormNode->GetOptionalElement('twig')); - $sXml = preg_replace('/^.+\n/', '', $sXml); - $sXml = preg_replace('/\n.+$/', '', $sXml); - - $aFields['layout'] = array( - 'type' => (preg_match('/\{\{|\{\#|\{\%/', $sXml) === 1) ? 'twig' : 'xhtml', - 'content' => $sXml - ); - } - - // Adding form for each class / mode - foreach ($aModes as $sMode) - { - // Initializing current class if necessary - if (!isset($aForms[$sFormClass])) - { - $aForms[$sFormClass] = array(); - } - - if ($sMode === 'apply_stimulus') - { - // Iterating over current class and child classes to fill stimuli forms - foreach (MetaModel::EnumChildClasses($sFormClass, ENUM_CHILD_CLASSES_ALL) as $sChildClass) - { - // Initializing child class if necessary - if (!isset($aForms[$sChildClass][$sMode])) - { - $aForms[$sChildClass][$sMode] = array(); - } - - // If stimuli are implicitly defined (empty tag), we define all those that have not already been by other forms. - $aChildStimuli = $aFormStimuli; - if (empty($aChildStimuli)) - { - // Stimuli already declared - $aDeclaredStimuli = array(); - if (array_key_exists($sChildClass, $aForms) && array_key_exists('apply_stimulus', - $aForms[$sChildClass])) - { - $aDeclaredStimuli = array_keys($aForms[$sChildClass]['apply_stimulus']); - } - // All stimuli - $aDatamodelStimuli = array_keys(MetaModel::EnumStimuli($sChildClass)); - // Missing stimuli - $aChildStimuli = array_diff($aDatamodelStimuli, $aDeclaredStimuli); - } - - foreach ($aChildStimuli as $sFormStimulus) - { - // Setting form if not defined OR if it was defined by a parent (abstract) class - if (!isset($aForms[$sChildClass][$sMode][$sFormStimulus]) || !empty($aFormStimuli)) - { - $aForms[$sChildClass][$sMode][$sFormStimulus] = $aFields; - $aForms[$sChildClass][$sMode][$sFormStimulus]['id'] = 'apply_stimulus-'.$sChildClass.'-'.$sFormStimulus; - } - } - } - } - elseif (!isset($aForms[$sFormClass][$sMode])) - { - $aForms[$sFormClass][$sMode] = $aFields; - } - else - { - throw new DOMFormatException('There is already a form for the class "'.$sFormClass.'" in "'.$sMode.'"', - null, null, $oFormNode); - } - } - } - else - { - throw new DOMFormatException('Class tag must be defined', null, null, $oFormNode); - } - } - catch (DOMFormatException $e) - { - throw new Exception('Could not create from [id="'.$oFormNode->getAttribute('id').'"] from XML because of a DOM problem : '.$e->getMessage()); - } - catch (Exception $e) - { - throw new Exception('Could not create from from XML : '.$oFormNode->Dump().' '.$e->getMessage()); - } - } - - return $aForms; - } - - /** - * Loads the scopes configuration from the module design XML - * - * @param \Silex\Application $oApp - * @param ModuleDesign $oDesign - */ - public static function LoadScopesConfiguration(Application $oApp, ModuleDesign $oDesign) - { - $oApp['scope_validator']->Init($oDesign->GetNodes('/module_design/classes/class')); - } - - /** - * Loads the lifecycle configuration from the module design XML - * - * @param \Silex\Application $oApp - * @param ModuleDesign $oDesign - */ - protected static function LoadLifecycleConfiguration(Application $oApp, ModuleDesign $oDesign) - { - $oApp['lifecycle_validator']->Init($oDesign->GetNodes('/module_design/classes/class')); - } - - /** - * Loads the context helper from the module design XML - * - * @param \Silex\Application $oApp - * @param ModuleDesign $oDesign - */ - protected static function LoadActionRulesConfiguration(Application $oApp, ModuleDesign $oDesign) - { - $oApp['context_manipulator']->Init($oDesign->GetNodes('/module_design/action_rules/action_rule')); - } - - /** - * 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 - * - * @param \Silex\Application $oApp - * @param ModuleDesign $oDesign - * - * @return array - * @throws \DOMFormatException - */ - protected static function LoadListsConfiguration(Application $oApp, ModuleDesign $oDesign) - { - $iDefaultItemRank = 0; - $aClassesLists = array(); - - // Parsing XML file - // - Each classes - foreach ($oDesign->GetNodes('/module_design/classes/class') as $oClassNode) - { - $aClassLists = array(); - $sClassId = $oClassNode->getAttribute('id'); - if ($sClassId === null) - { - throw new DOMFormatException('Class tag must have an id attribute', null, null, $oClassNode); - } - - // - Each lists - foreach ($oClassNode->GetNodes('./lists/list') as $oListNode) - { - $aListItems = array(); - $sListId = $oListNode->getAttribute('id'); - if ($sListId === null) - { - throw new DOMFormatException('List tag of "'.$sClassId.'" class must have an id attribute', null, - null, $oListNode); - } - - // - Each items - foreach ($oListNode->GetNodes('./items/item') as $oItemNode) - { - $sItemId = $oItemNode->getAttribute('id'); - if ($sItemId === null) - { - throw new DOMFormatException('Item tag of "'.$sItemId.'" list must have an id attribute', null, - null, $oItemNode); - } - - $aItem = array( - 'att_code' => $sItemId, - 'rank' => $iDefaultItemRank - ); - - $oRankNode = $oItemNode->GetOptionalElement('rank'); - if ($oRankNode !== null) - { - $aItem['rank'] = $oRankNode->GetText($iDefaultItemRank); - } - - $aListItems[] = $aItem; - } - // - Sorting list items by rank - usort($aListItems, function ($a, $b) { - return $a['rank'] > $b['rank']; - }); - $aClassLists[$sListId] = $aListItems; - } - - // - Adding class only if it has at least one list - if (!empty($aClassLists)) - { - $aClassesLists[$sClassId] = $aClassLists; - } - } - - return $aClassesLists; - } - - /** - * Loads portal UI extensions - * - * @param \Silex\Application $oApp - * - * @return array - */ - protected static function LoadUIExtensions(Application $oApp) - { - $aUIExtensions = array( - 'css_files' => array(), - 'css_inline' => null, - 'js_files' => array(), - 'js_inline' => null, - 'html' => array(), - ); - $aUIExtensionHooks = array( - iPortalUIExtension::ENUM_PORTAL_EXT_UI_BODY, - iPortalUIExtension::ENUM_PORTAL_EXT_UI_NAVIGATION_MENU, - iPortalUIExtension::ENUM_PORTAL_EXT_UI_MAIN_CONTENT, - ); - - /** @var iPortalUIExtension $oExtensionInstance */ - foreach (MetaModel::EnumPlugins('iPortalUIExtension') as $oExtensionInstance) - { - // Adding CSS files - $aImportPaths = array($oApp['combodo.portal.base.absolute_path'].'css/'); - foreach($oExtensionInstance->GetCSSFiles($oApp) as $sCSSFile) - { - // Removing app root url as we need to pass a path on the file system (relative to app root) - $sCSSFilePath = str_replace(utils::GetAbsoluteUrlAppRoot(), '', $sCSSFile); - // Compiling SCSS file - $sCSSFileCompiled = $oApp['combodo.absolute_url'].utils::GetCSSFromSASS($sCSSFilePath, - $aImportPaths); - - if(!in_array($sCSSFileCompiled, $aUIExtensions['css_files'])) - { - $aUIExtensions['css_files'][] = $sCSSFileCompiled; - } - } - - // Adding CSS inline - $sCSSInline = $oExtensionInstance->GetCSSInline($oApp); - if ($sCSSInline !== null) - { - $aUIExtensions['css_inline'] .= "\n\n".$sCSSInline; - } - - // Adding JS files - $aUIExtensions['js_files'] = array_merge($aUIExtensions['js_files'], - $oExtensionInstance->GetJSFiles($oApp)); - - // Adding JS inline - $sJSInline = $oExtensionInstance->GetJSInline($oApp); - if ($sJSInline !== null) - { - // Note: Semi-colon is to prevent previous script that would have omitted it. - $aUIExtensions['js_inline'] .= "\n\n;\n".$sJSInline; - } - - // Adding HTML for each hook - foreach ($aUIExtensionHooks as $sUIExtensionHook) - { - $sFunctionName = 'Get'.$sUIExtensionHook.'HTML'; - $sHTML = $oExtensionInstance->$sFunctionName($oApp); - if ($sHTML !== null) - { - if (!array_key_exists($sUIExtensionHook, $aUIExtensions['html'])) - { - $aUIExtensions['html'][$sUIExtensionHook] = ''; - } - $aUIExtensions['html'][$sUIExtensionHook] .= "\n\n".$sHTML; - } - } - } - - return $aUIExtensions; - } - - public static function LoadSessionMessages(Application $oApp) - { - $aAllMessages = array(); - if ((array_key_exists('obj_messages', $_SESSION)) && (!empty($_SESSION['obj_messages']))) - { - foreach ($_SESSION['obj_messages'] as $sMessageKey => $aMessageObjectData) - { - $aObjectMessages = array(); - $aRanks = array(); - foreach ($aMessageObjectData as $sMessageId => $aMessageData) - { - $sMsgClass = 'alert alert-'; - switch ($aMessageData['severity']) - { - case 'info': - $sMsgClass .= 'info'; - break; - case 'error': - $sMsgClass .= 'danger'; - break; - case 'ok': - default: - $sMsgClass .= 'success'; - break; - } - $aObjectMessages[] = array('cssClass' => $sMsgClass, 'message' => $aMessageData['message']); - $aRanks[] = $aMessageData['rank']; - } - unset($_SESSION['obj_messages'][$sMessageKey]); - array_multisort($aRanks, $aObjectMessages); - foreach ($aObjectMessages as $aObjectMessage) - { - $aAllMessages[] = $aObjectMessage; - } - } - } - $oApp['combodo.current_user.session_messages'] = $aAllMessages; - } - - /** - * Generate the form data for the $sClass. - * Form will look like the "Properties" tab of a $sClass object in the console. - * - * @param string $sClass - * @param bool $bAddLinksets - * - * @return array - */ - protected static function GenerateDefaultFormForClass($sClass, $bAddLinksets = false) - { - $aForm = array( - 'id' => strtolower($sClass)."-default-".uniqid(), - 'type' => 'custom_list', - 'properties' => array( - 'display_mode' => static::FORM_DEFAULT_DISPLAY_MODE, - 'always_show_submit' => static::FORM_DEFAULT_ALWAYS_SHOW_SUBMIT, - ), - 'fields' => array(), - 'layout' => array( - 'type' => 'xhtml', - 'content' => '', - ), - ); - - // Generate layout - $sContent = ""; - - // - Retrieve zlist details - $aDetailsList = MetaModel::GetZListItems($sClass, 'details'); - $aDetailsStruct = cmdbAbstractObject::ProcessZlist($aDetailsList, array(), 'UI:PropertiesTab', 'col1', ''); - $aPropertiesStruct = $aDetailsStruct['UI:PropertiesTab']; - - // Count cols (not linksets) - $iColCount = 0; - foreach($aPropertiesStruct as $sColId => $aColFieldsets) - { - if(substr($sColId, 0, 1) !== '_') - { - foreach($aColFieldsets as $sFieldsetName => $aAttCodes) - { - if(substr($sFieldsetName, 0, 1) !== '_') - { - $iColCount++; - break; - } - } - } - } - // If no cols, return a default form with all fields one after another - if($iColCount === 0) - { - return array( - 'id' => 'default', - 'type' => 'zlist', - 'fields' => 'details', - 'layout' => null - ); - } - // Warning, this might not be great when 12 modulo $iColCount is greater than 0. - $sColCSSClass = 'col-sm-'.floor(12/$iColCount); - - $sLinksetsHTML = ""; - $sRowHTML = "
\n"; - foreach($aPropertiesStruct as $sColId => $aColFieldsets) - { - $sColsHTML = "\t
\n"; - foreach($aColFieldsets as $sFieldsetName => $aAttCodes) - { - // Add fieldset, not linkset - if(substr($sFieldsetName, 0, 1) !== '_') - { - $sFieldsetHTML = "\t\t
\n"; - $sFieldsetHTML .= "\t\t\t".htmlentities(Dict::S($sFieldsetName), ENT_QUOTES, 'UTF-8')."\n"; - - foreach($aAttCodes as $sAttCode) - { - $sFieldsetHTML .= "\t\t\t
\n"; - } - - $sFieldsetHTML .= "\t\t
\n"; - - // Add to col - $sColsHTML .= $sFieldsetHTML; - } - elseif($bAddLinksets) - { - foreach($aAttCodes as $sAttCode) - { - $sLinksetsHTML .= "
\n"; - } - } - } - $sColsHTML .= "\t
\n"; - - // Add col to row - $sRowHTML .= $sColsHTML; - } - $sRowHTML .= "
\n"; - - // Add row to twig - $sContent .= $sRowHTML; - // Add linksets to twig - $sContent .= $sLinksetsHTML; - - $aForm['layout']['content'] = $sContent; - - return $aForm; - } - -} diff --git a/datamodels/2.x/itop-portal-base/portal/src/Helper/ApplicationHelper.php b/datamodels/2.x/itop-portal-base/portal/src/Helper/ApplicationHelper.php new file mode 100644 index 0000000000..4ce4929f54 --- /dev/null +++ b/datamodels/2.x/itop-portal-base/portal/src/Helper/ApplicationHelper.php @@ -0,0 +1,472 @@ + + */ +class ApplicationHelper +{ + const FORM_ENUM_DISPLAY_MODE_COSY = 'cosy'; + const FORM_ENUM_DISPLAY_MODE_COMPACT = 'compact'; + const FORM_DEFAULT_DISPLAY_MODE = self::FORM_ENUM_DISPLAY_MODE_COSY; + const FORM_DEFAULT_ALWAYS_SHOW_SUBMIT = false; + + /** + * Loads classes from the base portal + * + * @deprecated Since 2.7.0 + * + * @param string $sScannedDir Directory to load the files from + * @param string $sFilePattern Pattern of files to load + * @param string $sType Type of files to load, used only in the Exception message, can be anything + * + * @throws \Exception + */ + public static function LoadClasses($sScannedDir, $sFilePattern, $sType) + { + @trigger_error( + sprintf( + 'Usage of legacy LoadClasses is deprecated. You should rely on autoloading (and therefore follow PSR4).', + __FILE__ + ), + E_USER_DEPRECATED + ); + + // Loading classes from base portal + foreach (scandir($sScannedDir) as $sFile) + { + if (strpos($sFile, $sFilePattern) !== false && file_exists($sFilepath = $sScannedDir.'/'.$sFile)) + { + try + { + require_once $sFilepath; + } + catch (Exception $e) + { + throw new Exception('Error while trying to load '.$sType.' '.$sFile); + } + } + } + } + + /** + * Registers an exception handler that will intercept controllers exceptions and display them in a nice template. + * Note : It is only active when $oApp['debug'] is false + * + * @param Application $oApp + */ + public static function RegisterExceptionHandler(Application $oApp) + { + // Intercepting fatal errors and exceptions + ErrorHandler::register(); + ExceptionHandler::register(($oApp['debug'] === true)); + + // Intercepting manually aborted request + if (1 || !$oApp['debug']) + { + $oApp->error(function (Exception $oException /*, Request $oRequest*/) use ($oApp) { + $iErrorCode = ($oException instanceof HttpException) ? $oException->getStatusCode() : 500; + + $aData = array( + 'exception' => $oException, + 'code' => $iErrorCode, + 'error_title' => '', + 'error_message' => $oException->getMessage() + ); + + switch ($iErrorCode) + { + case 404: + $aData['error_title'] = Dict::S('Error:HTTP:404'); + break; + default: + $aData['error_title'] = Dict::S('Error:HTTP:500'); + break; + } + + IssueLog::Error($aData['error_title'].' : '.$aData['error_message']); + + if ($oApp['request_stack']->getCurrentRequest()->isXmlHttpRequest()) + { + $oResponse = $oApp->json($aData, $iErrorCode); + } + else + { + // Preparing debug trace + $aSteps = array(); + foreach ($oException->getTrace() as $aStep) + { + // - Default file name + if (!isset($aStep['file'])) + { + $aStep['file'] = ''; + } + $aFileParts = explode('\\', $aStep['file']); + // - Default line number + if (!isset($aStep['line'])) + { + $aStep['line'] = 'unknown'; + } + // - Default class name + if (isset($aStep['class']) && isset($aStep['function']) && isset($aStep['type'])) + { + $aClassParts = explode('\\', $aStep['class']); + $sClassName = $aClassParts[count($aClassParts) - 1]; + $sClassFQ = $aStep['class']; + + $aArgsAsString = array(); + foreach ($aStep['args'] as $arg) + { + if (is_array($arg)) + { + $aArgsAsString[] = 'array(...)'; + } + elseif (is_object($arg)) + { + $aArgsAsString[] = 'object('.get_class($arg).')'; + } + else + { + $aArgsAsString[] = $arg; + } + } + + $sFunctionCall = $sClassName.$aStep['type'].$aStep['function'].'('.implode(', ', + $aArgsAsString).')'; + } + else + { + $sClassName = null; + $sClassFQ = null; + $sFunctionCall = null; + } + + $aSteps[] = array( + 'file_fq' => $aStep['file'], + 'file_name' => $aFileParts[count($aFileParts) - 1], + 'line' => $aStep['line'], + 'class_name' => $sClassName, + 'class_fq' => $sClassFQ, + 'function_call' => $sFunctionCall, + ); + } + + $aData['debug_trace_steps'] = $aSteps; + + $oResponse = $oApp['twig']->render('itop-portal-base/portal/templates/errors/layout.html.twig', + $aData); + } + + return $oResponse; + }); + } + } + + /** + * Loads the brick's security from the OQL queries to profiles arrays + * + * @param \Combodo\iTop\Portal\Brick\AbstractBrick $oBrick + * + * @throws \Exception + */ + public static function LoadBrickSecurity(AbstractBrick $oBrick) + { + try + { + // Allowed profiles + if ($oBrick->GetAllowedProfilesOql() !== null && $oBrick->GetAllowedProfilesOql() !== '') + { + $oSearch = DBObjectSearch::FromOQL($oBrick->GetAllowedProfilesOql()); + $oSet = new DBObjectSet($oSearch); + while ($oProfile = $oSet->Fetch()) + { + $oBrick->AddAllowedProfile($oProfile->Get('name')); + } + } + + // Denied profiles + if ($oBrick->GetDeniedProfilesOql() !== null && $oBrick->GetDeniedProfilesOql() !== '') + { + $oSearch = DBObjectSearch::FromOQL($oBrick->GetDeniedProfilesOql()); + $oSet = new DBObjectSet($oSearch); + while ($oProfile = $oSet->Fetch()) + { + $oBrick->AddDeniedProfile($oProfile->Get('name')); + } + } + } + catch (Exception $e) + { + throw new Exception('Error while loading security from '.$oBrick->GetId().' brick'); + } + } + + /** + * Return the form properties for the $sClassname in $sMode. + * + * If not found, tries to find one from the closest parent class. + * Else returns a default form based on zlist 'details' + * + * @param Application $oApp + * @param string $sClass Object class to find a form for + * @param string $sMode Form mode to find (view|edit|create) + * + * @return array + * + * @throws \CoreException + */ + public static function GetLoadedFormFromClass($aForms, $sClass, $sMode) + { + + // We try to find the form for that class + if (isset($aForms[$sClass]) && isset($aForms[$sClass][$sMode])) + { + $aForm = $aForms[$sClass][$sMode]; + } + // If not found, we try find one from the closest parent class + else + { + $bFound = false; + foreach (MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_EXCLUDELEAF, false) as $sParentClass) + { + if (isset($aForms[$sParentClass]) && isset($aForms[$sParentClass][$sMode])) + { + $aForm = $aForms[$sParentClass][$sMode]; + $bFound = true; + break; + } + } + + // If we have still not found one, we return a default form + if (!$bFound) + { + $aForm = static::GenerateDefaultFormForClass($sClass); + } + } + + return $aForm; + } + + /** + * Return the attribute list for the $sClassname in $sList. + * + * If not found, tries to find one from the closest parent class. + * Else returns a default attribute list based on zlist 'list' + * + * @param array $aLists + * @param string $sClass Object class to find a list for + * @param string $sList List name to find + * + * @return string[] Array of attribute codes + * + * @throws \CoreException + */ + public static function GetLoadedListFromClass($aLists, $sClass, $sList = 'default') + { + $aFoundList = null; + $aAttCodes = array(); + + // We try to find the list for that class + if (isset($aLists[$sClass]) && isset($aLists[$sClass][$sList])) + { + $aFoundList = $aLists[$sClass][$sList]; + } + // Else we try to found the default list for that class + elseif (isset($aLists[$sClass]) && isset($aLists[$sClass]['default'])) + { + $aFoundList = $aLists[$sClass]['default']; + } + // If not found, we try find one from the closest parent class + else + { + foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass) + { + // Trying to find the right list + if (isset($aLists[$sParentClass]) && isset($aLists[$sParentClass][$sList])) + { + $aFoundList = $aLists[$sParentClass][$sList]; + break; + } + // Or the default list + elseif (isset($aLists[$sParentClass]) && isset($aLists[$sParentClass]['default'])) + { + $aFoundList = $aLists[$sParentClass]['default']; + break; + } + } + } + + // If found, we flatten the list to keep only the attribute codes (not the rank) + if ($aFoundList !== null) + { + foreach ($aFoundList as $aItem) + { + $aAttCodes[] = $aItem['att_code']; + } + } + else + { + $aAttCodes = MetaModel::FlattenZList(MetaModel::GetZListItems($sClass, 'list')); + } + + return $aAttCodes; + } + + /** + * Generate the form data for the $sClass. + * Form will look like the "Properties" tab of a $sClass object in the console. + * + * @param string $sClass + * @param bool $bAddLinksets + * + * @return array + */ + protected static function GenerateDefaultFormForClass($sClass, $bAddLinksets = false) + { + $aForm = array( + 'id' => strtolower($sClass)."-default-".uniqid(), + 'type' => 'custom_list', + 'properties' => array( + 'display_mode' => static::FORM_DEFAULT_DISPLAY_MODE, + 'always_show_submit' => static::FORM_DEFAULT_ALWAYS_SHOW_SUBMIT, + ), + 'fields' => array(), + 'layout' => array( + 'type' => 'xhtml', + 'content' => '', + ), + ); + + // Generate layout + $sContent = ""; + + // - Retrieve zlist details + $aDetailsList = MetaModel::GetZListItems($sClass, 'details'); + $aDetailsStruct = cmdbAbstractObject::ProcessZlist($aDetailsList, array(), 'UI:PropertiesTab', 'col1', ''); + $aPropertiesStruct = $aDetailsStruct['UI:PropertiesTab']; + + // Count cols (not linksets) + $iColCount = 0; + foreach($aPropertiesStruct as $sColId => $aColFieldsets) + { + if(substr($sColId, 0, 1) !== '_') + { + foreach($aColFieldsets as $sFieldsetName => $aAttCodes) + { + if(substr($sFieldsetName, 0, 1) !== '_') + { + $iColCount++; + break; + } + } + } + } + // If no cols, return a default form with all fields one after another + if($iColCount === 0) + { + return array( + 'id' => 'default', + 'type' => 'zlist', + 'fields' => 'details', + 'layout' => null + ); + } + // Warning, this might not be great when 12 modulo $iColCount is greater than 0. + $sColCSSClass = 'col-sm-'.floor(12/$iColCount); + + $sLinksetsHTML = ""; + $sRowHTML = "
\n"; + foreach($aPropertiesStruct as $sColId => $aColFieldsets) + { + $sColsHTML = "\t
\n"; + foreach($aColFieldsets as $sFieldsetName => $aAttCodes) + { + // Add fieldset, not linkset + if(substr($sFieldsetName, 0, 1) !== '_') + { + $sFieldsetHTML = "\t\t
\n"; + $sFieldsetHTML .= "\t\t\t".htmlentities(Dict::S($sFieldsetName), ENT_QUOTES, 'UTF-8')."\n"; + + foreach($aAttCodes as $sAttCode) + { + $sFieldsetHTML .= "\t\t\t
\n"; + } + + $sFieldsetHTML .= "\t\t
\n"; + + // Add to col + $sColsHTML .= $sFieldsetHTML; + } + elseif($bAddLinksets) + { + foreach($aAttCodes as $sAttCode) + { + $sLinksetsHTML .= "
\n"; + } + } + } + $sColsHTML .= "\t
\n"; + + // Add col to row + $sRowHTML .= $sColsHTML; + } + $sRowHTML .= "
\n"; + + // Add row to twig + $sContent .= $sRowHTML; + // Add linksets to twig + $sContent .= $sLinksetsHTML; + + $aForm['layout']['content'] = $sContent; + + return $aForm; + } + +}