diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/controllers/browsebrickcontroller.class.inc.php b/datamodels/2.x/itop-portal-base/portal-silex/src/controllers/browsebrickcontroller.class.inc.php deleted file mode 100644 index 765f397710..0000000000 --- a/datamodels/2.x/itop-portal-base/portal-silex/src/controllers/browsebrickcontroller.class.inc.php +++ /dev/null @@ -1,821 +0,0 @@ - - -namespace Combodo\iTop\Portal\Controller; - -use Silex\Application; -use Symfony\Component\HttpFoundation\Request; -use UserRights; -use Dict; -use MetaModel; -use AttributeImage; -use DBSearch; -use DBObjectSet; -use BinaryExpression; -use FieldExpression; -use VariableExpression; -use Combodo\iTop\Portal\Helper\ApplicationHelper; -use Combodo\iTop\Portal\Helper\SecurityHelper; -use Combodo\iTop\Portal\Helper\ContextManipulatorHelper; -use Combodo\iTop\Portal\Brick\AbstractBrick; -use Combodo\iTop\Portal\Brick\BrowseBrick; - -/** - * Class BrowseBrickController - * - * @package Combodo\iTop\Portal\Controller - * @author Guillaume Lajarige - * @since 2.3.0 - */ -class BrowseBrickController extends BrickController -{ - const LEVEL_SEPARATOR = '-'; - public static $aOptionalAttributes = array('tooltip_att', 'description_att', 'image_att'); - - /** - * @param \Symfony\Component\HttpFoundation\Request $oRequest - * @param \Silex\Application $oApp - * @param string $sBrickId - * @param string $sBrowseMode - * @param string $sDataLoading - * - * @return \Symfony\Component\HttpFoundation\Response - * - * @throws \Exception - * @throws \CoreException - */ - public function DisplayAction(Request $oRequest, Application $oApp, $sBrickId, $sBrowseMode = null, $sDataLoading = null) - { - /** @var \Combodo\iTop\Portal\Brick\BrowseBrick $oBrick */ - $oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId); - - // Getting availables browse modes - $aBrowseModes = $oBrick->GetAvailablesBrowseModes(); - $aBrowseButtons = array_keys($aBrowseModes); - // Getting current browse mode (First from router pamater, then default brick value) - $sBrowseMode = (!empty($sBrowseMode)) ? $sBrowseMode : $oBrick->GetDefaultBrowseMode(); - // Getting current dataloading mode (First from router parameter, then query parameter, then default brick value) - $sDataLoading = ($sDataLoading !== null) ? $sDataLoading : $oApp['request_manipulator']->ReadParam('sDataLoading', $oBrick->GetDataLoading()); - // Getting search value - $sSearchValue = $oApp['request_manipulator']->ReadParam('sSearchValue', ''); - if (!empty($sSearchValue)) - { - $sDataLoading = AbstractBrick::ENUM_DATA_LOADING_LAZY; - } - - $aData = array(); - $aLevelsProperties = array(); - $aLevelsClasses = array(); - static::TreeToFlatLevelsProperties($oApp, $oBrick->GetLevels(), $aLevelsProperties); - - // Concistency checks - if (!in_array($sBrowseMode, array_keys($aBrowseModes))) - { - $oApp->abort(500, 'Browse brick "' . $sBrickId . '" : Unknown browse mode "' . $sBrowseMode . '", availables are ' . implode(' / ', array_keys($aBrowseModes))); - } - if (empty($aLevelsProperties)) - { - $oApp->abort(500, 'Browse brick "' . $sBrickId . '" : No levels to display.'); - } - - // Building DBobjectSearch - $oQuery = null; - // ... In this case only we have to build a specific query for the current level only - if (in_array($sBrowseMode, array(BrowseBrick::ENUM_BROWSE_MODE_TREE, BrowseBrick::ENUM_BROWSE_MODE_MOSAIC)) && ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_LAZY)) - { - // Will be handled later in the pagination part - } - // .. Otherwise - else - { - // We iterate (in reverse mode /!\) over the levels to build the whole query, starting from the bottom - $aLevelsPropertiesKeys = array_keys($aLevelsProperties); - $iLoopMax = count($aLevelsPropertiesKeys) - 1; - $oFullBinExpr = null; - for ($i = $iLoopMax; $i >= 0; $i--) - { - // Retrieving class alias for all depth - array_unshift($aLevelsClasses, $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->GetClassAlias()); - - // Joining queries from bottom-up - if ($i < $iLoopMax) - { - $aRealiasingMap = array(); - $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search'] = $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->Join($aLevelsProperties[$aLevelsPropertiesKeys[$i + 1]]['search'], DBSearch::JOIN_REFERENCED_BY, $aLevelsProperties[$aLevelsPropertiesKeys[$i + 1]]['parent_att'], TREE_OPERATOR_EQUALS, $aRealiasingMap); - foreach ($aLevelsPropertiesKeys as $sLevelAlias) - { - if (array_key_exists($sLevelAlias, $aRealiasingMap)) - { - $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->RenameAlias($aRealiasingMap[$sLevelAlias], $sLevelAlias); - } - } - } - - // Adding search clause - // Note : For know the search is naive and looks only for the exact match. It doesn't search for words separately - if (!empty($sSearchValue)) - { - // - Cleaning the search value by exploding and trimming spaces - $aSearchValues = explode(' ', $sSearchValue); - array_walk($aSearchValues, function (&$sSearchValue /*, $sKey*/) { - trim($sSearchValue); - }); - - // - Retrieving fields to search - $aSearchFields = array($aLevelsProperties[$aLevelsPropertiesKeys[$i]]['name_att']); - if (!empty($aLevelsProperties[$aLevelsPropertiesKeys[$i]]['fields'])) - { - foreach ($aLevelsProperties[$aLevelsPropertiesKeys[$i]]['fields'] as $aTmpField) - { - $aSearchFields[] = $aTmpField['code']; - } - } - // - Building query for the search values parts - $oLevelBinExpr = null; - $iFieldLoopMax = count($aSearchFields) - 1; - $iSearchLoopMax = count($aSearchValues) - 1; - for ($j = 0; $j <= $iFieldLoopMax; $j++) - { - $sTmpFieldAttCode = $aSearchFields[$j]; - $oFieldBinExpr = null; - //$oFieldBinExpr = new BinaryExpression(new FieldExpression($aSearchFields[$j], $aLevelsPropertiesKeys[$i]), ) - - for ($k = 0; $k <= $iSearchLoopMax; $k++) - { - $oSearchBinExpr = new BinaryExpression(new FieldExpression($sTmpFieldAttCode, $aLevelsPropertiesKeys[$i]), 'LIKE', new VariableExpression('search_value_' . $k)); - if ($k === 0) - { - $oFieldBinExpr = $oSearchBinExpr; - } - else - { - $oFieldBinExpr = new BinaryExpression($oFieldBinExpr, 'AND', $oSearchBinExpr); - } - } - - if ($j === 0) - { - $oLevelBinExpr = $oFieldBinExpr; - } - else - { - $oLevelBinExpr = new BinaryExpression($oLevelBinExpr, 'OR', $oFieldBinExpr); - } - } - - // - Building query for the level - if ($i === $iLoopMax) - { - $oFullBinExpr = $oLevelBinExpr; - } - else - { - $oFullBinExpr = new BinaryExpression($oFullBinExpr, 'OR', $oLevelBinExpr); - } - - // - Adding it to the query when complete - if ($i === 0) - { - $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->AddConditionExpression($oFullBinExpr); - } - } - - // Setting selected classes and binding parameters - if ($i === 0) - { - $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->SetSelectedClasses($aLevelsClasses); - - if (!empty($sSearchValue)) - { - // Note : This could be way more simpler if we had a SetInternalParam($sParam, $value) verb - $aQueryParams = $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->GetInternalParams(); - // Note : $iSearchloopMax was initialized on the previous loop - for ($j = 0; $j <= $iSearchLoopMax; $j++) - { - $aQueryParams['search_value_' . $j] = '%' . $aSearchValues[$j] . '%'; - } - $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->SetInternalParams($aQueryParams); - } - } - } - $oQuery = $aLevelsProperties[$aLevelsPropertiesKeys[0]]['search']; - - // Testing appropriate data loading mode if we are in auto - if ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_AUTO) - { - // - Check how many records there is. - // - Update $sDataLoading with its new value regarding the number of record and the threshold - $oCountSet = new DBObjectSet($oQuery); - $fThreshold = (float) MetaModel::GetModuleSetting($oApp['combodo.portal.instance.id'], 'lazy_loading_threshold'); - $sDataLoading = ($oCountSet->Count() > $fThreshold) ? AbstractBrick::ENUM_DATA_LOADING_LAZY : AbstractBrick::ENUM_DATA_LOADING_FULL; - unset($oCountSet); - } - } - - // Setting query pagination if needed - if ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_LAZY) - { - switch ($sBrowseMode) - { - case BrowseBrick::ENUM_BROWSE_MODE_LIST: - // Retrieving parameters - $iPageNumber = (int) $oApp['request_manipulator']->ReadParam('iPageNumber', 1, FILTER_SANITIZE_NUMBER_INT); - $iListLength = (int) $oApp['request_manipulator']->ReadParam('iListLength', BrowseBrick::DEFAULT_LIST_LENGTH, FILTER_SANITIZE_NUMBER_INT); - - // Getting total records number - $oCountSet = new DBObjectSet($oQuery); - $aData['recordsTotal'] = $oCountSet->Count(); - $aData['recordsFiltered'] = $oCountSet->Count(); - unset($oCountSet); - - $oSet = new DBObjectSet($oQuery); - $oSet->SetLimit($iListLength, $iListLength * ($iPageNumber - 1)); - - break; - case BrowseBrick::ENUM_BROWSE_MODE_TREE: - case BrowseBrick::ENUM_BROWSE_MODE_MOSAIC: - // Retrieving parameters - $sLevelAlias = $oApp['request_manipulator']->ReadParam('sLevelAlias', ''); - $sNodeId = $oApp['request_manipulator']->ReadParam('sNodeId', ''); - - // If no values for those parameters, we might be loading page in lazy mode for the first time, therefore the URL doesn't have those informations. - if (empty($sLevelAlias)) - { - reset($aLevelsProperties); - $oQuery = $aLevelsProperties[key($aLevelsProperties)]['search']; - if (!empty($sNodeId)) - { - $oQuery->AddCondition('id', $sNodeId); - } - } - // Else we need to find the OQL for that particular level - else - { - $bFoundLevel = false; - foreach ($aLevelsProperties as $aLevelProperties) - { - if ($aLevelProperties['alias'] === $sLevelAlias) - { - if (isset($aLevelProperties['levels']) && !empty($aLevelProperties['levels']) && isset($aLevelsProperties[$aLevelProperties['levels'][0]])) - { - $oQuery = $aLevelsProperties[$aLevelProperties['levels'][0]]['search']; - if (!empty($sNodeId)) - { - $oQuery->AddCondition($aLevelsProperties[$aLevelProperties['levels'][0]]['parent_att'], $sNodeId); - } - $bFoundLevel = true; - break; - } - } - } - - if (!$bFoundLevel) - { - $oApp->abort(500, 'Browse brick "' . $sBrickId . '" : Level alias "' . $sLevelAlias . '" is not defined for that brick.'); - } - } - - $oSet = new DBObjectSet($oQuery); - break; - - default: - // We should never be there. If there is an other browse mode for that brick : - // - If it's from a custom brick extension, it should be handle by the extension router/controller - // - If it's from a base brick, it should be handle in a case above this one - // - If none of the previous statements was done, this fail safe will load all data as it's not able to know how to handle the pagination - $oSet = new DBObjectSet($oQuery); - break; - } - } - else - { - $oSet = new DBObjectSet($oQuery); - } - - // Optimizing the ObjectSet to retrieve only necessary columns - $aColumnAttrs = array(); - foreach ($oSet->GetFilter()->GetSelectedClasses() as $sTmpClassAlias => $sTmpClassName) - { - if (isset($aLevelsProperties[$sTmpClassAlias])) - { - $aTmpLevelProperties = $aLevelsProperties[$sTmpClassAlias]; - // Mandatory main attribute - $aTmpColumnAttrs = array($aTmpLevelProperties['name_att']); - // Optional attributes, only if in list mode - if ($sBrowseMode === BrowseBrick::ENUM_BROWSE_MODE_LIST) - { - foreach ($aTmpLevelProperties['fields'] as $aTmpField) - { - $aTmpColumnAttrs[] = $aTmpField['code']; - } - } - // Optional attributes - foreach (static::$aOptionalAttributes as $sOptionalAttribute) - { - if ($aTmpLevelProperties[$sOptionalAttribute] !== null) - { - $aTmpColumnAttrs[] = $aTmpLevelProperties[$sOptionalAttribute]; - } - } - - $aColumnAttrs[$sTmpClassAlias] = $aTmpColumnAttrs; - } - } - $oSet->OptimizeColumnLoad($aColumnAttrs); - - // Sorting objects through defined order (in DM) - $oSet->SetOrderByClasses(); - - // Retrieving results and organizing them for templating - $aItems = array(); - while ($aCurrentRow = $oSet->FetchAssoc()) - { - switch ($sBrowseMode) - { - case BrowseBrick::ENUM_BROWSE_MODE_TREE: - case BrowseBrick::ENUM_BROWSE_MODE_MOSAIC: - static::AddToTreeItems($aItems, $aCurrentRow, $aLevelsProperties, null, $oApp); - break; - - case BrowseBrick::ENUM_BROWSE_MODE_LIST: - default: - $aItems[] = static::AddToFlatItems($aCurrentRow, $aLevelsProperties, $oApp); - break; - } - } - - // Preparing response - if ($oRequest->isXmlHttpRequest()) - { - $aData = $aData + array( - 'data' => $aItems, - 'levelsProperties' => $aLevelsProperties, - ); - $oResponse = $oApp->json($aData); - } - else - { - $aData = $aData + array( - 'oBrick' => $oBrick, - 'sBrickId' => $sBrickId, - 'sBrowseMode' => $sBrowseMode, - 'aBrowseButtons' => $aBrowseButtons, - 'sSearchValue' => $sSearchValue, - 'sDataLoading' => $sDataLoading, - 'aItems' => json_encode($aItems), - 'iItemsCount' => count($aItems), - 'aLevelsProperties' => json_encode($aLevelsProperties), - ); - - // Note : To extend this brick's template, depending on what you want to do : - // a) Modify the whole template : - // - Create a template and specify it in the brick configuration - // b) Add a new browse mode : - // - Create a template for that browse mode, - // - Add the mode to those availables in the brick configuration, - // - Create a router and add a route for the new browse mode - if ($oBrick->GetPageTemplatePath() !== null) - { - $sTemplatePath = $oBrick->GetPageTemplatePath(); - } - else - { - $sTemplatePath = $aBrowseModes[$sBrowseMode]['template']; - } - $oResponse = $oApp['twig']->render($sTemplatePath, $aData); - } - - return $oResponse; - } - - /** - * Flattens the $aLevels into $aLevelsProperties in order to be able to build an OQL query from multiple single queries related to each - * others. As of now it only keeps search / parent_att / name_att properties. - * - * Note : This is not in the BrowseBrick class because the classes should not rely on DBObjectSearch. - * - * @param \Silex\Application $oApp - * @param array $aLevels Levels from a BrowseBrick class - * @param array $aLevelsProperties Reference to an array that will contain the flattened levels - * @param string $sLevelAliasPrefix String that will be prefixed to the level ID as an unique path identifier - * - * @throws \Exception - * @throws \OQLException - * @throws \CoreException - */ - public static function TreeToFlatLevelsProperties(Application $oApp, array $aLevels, array &$aLevelsProperties, $sLevelAliasPrefix = 'L') - { - foreach ($aLevels as $aLevel) - { - $sCurrentLevelAlias = $sLevelAliasPrefix . static::LEVEL_SEPARATOR . $aLevel['id']; - $oSearch = DBSearch::CloneWithAlias(DBSearch::FromOQL($aLevel['oql']), $sCurrentLevelAlias); - - // Restricting to the allowed scope - $oScopeSearch = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $oSearch->GetClass(), UR_ACTION_READ); - $oSearch = ($oScopeSearch !== null) ? $oSearch->Intersect($oScopeSearch) : null; - // - Allowing all data if necessary - if ($oScopeSearch !== null && $oScopeSearch->IsAllDataAllowed()) - { - $oSearch->AllowAllData(); - } - - if ($oSearch !== null) - { - $aLevelsProperties[$sCurrentLevelAlias] = array( - 'alias' => $sCurrentLevelAlias, - 'title' => ($aLevel['title'] !== null) ? Dict::S($aLevel['title']) : MetaModel::GetName($oSearch->GetClass()), - 'parent_att' => $aLevel['parent_att'], - 'name_att' => $aLevel['name_att'], - 'tooltip_att' => $aLevel['tooltip_att'], - 'description_att' => $aLevel['description_att'], - 'image_att' => $aLevel['image_att'], - 'search' => $oSearch, - 'fields' => array(), - 'actions' => array() - ); - - // Adding current level's fields - if (isset($aLevel['fields'])) - { - $aLevelsProperties[$sCurrentLevelAlias]['fields'] = array(); - - foreach ($aLevel['fields'] as $sFieldAttCode => $aFieldProperties) - { - $aLevelsProperties[$sCurrentLevelAlias]['fields'][] = array( - 'code' => $sFieldAttCode, - 'label' => MetaModel::GetAttributeDef($oSearch->GetClass(), $sFieldAttCode)->GetLabel(), - 'hidden' => $aFieldProperties['hidden'] - ); - } - } - - // Flattening and adding sublevels - if (isset($aLevel['levels'])) - { - foreach ($aLevel['levels'] as $aChildLevel) - { - // Checking if the sublevel if allowed - $oChildSearch = DBSearch::FromOQL($aChildLevel['oql']); - if (SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $oChildSearch->GetClass())) - { - // Adding the sublevel to this one - $aLevelsProperties[$sCurrentLevelAlias]['levels'][] = $sCurrentLevelAlias . static::LEVEL_SEPARATOR . $aChildLevel['id']; - - // Adding drilldown action if necessary - foreach ($aLevel['actions'] as $sId => $aAction) - { - if ($aAction['type'] === BrowseBrick::ENUM_ACTION_DRILLDOWN) - { - $aLevelsProperties[$sCurrentLevelAlias]['actions'][$sId] = $aAction; - break; - } - } - } - unset($oChildSearch); - } - static::TreeToFlatLevelsProperties($oApp, $aLevel['levels'], $aLevelsProperties, $sCurrentLevelAlias); - } - - // Adding actions to the level - foreach ($aLevel['actions'] as $sId => $aAction) - { - // ... Only if it's not already there (eg. the drilldown added with the sublevels) - if (!array_key_exists($sId, $aLevelsProperties[$sCurrentLevelAlias]['actions'])) - { - // Adding action only if allowed - if (($aAction['type'] === BrowseBrick::ENUM_ACTION_VIEW) && !SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $oSearch->GetClass())) - { - continue; - } - elseif (($aAction['type'] === BrowseBrick::ENUM_ACTION_EDIT) && !SecurityHelper::IsActionAllowed($oApp, UR_ACTION_MODIFY, $oSearch->GetClass())) - { - continue; - } - elseif ($aAction['type'] === BrowseBrick::ENUM_ACTION_DRILLDOWN) - { - continue; - } - - // Setting action title - if (isset($aAction['title'])) - { - // Note : There could be an enhancement here, by checking if the string code has the '%1' needle and use Dict::S or Dict::Format accordingly. - // But it would require to benchmark a potential performance drop as it will be done for all items - $aAction['title'] = Dict::S($aAction['title']); - } - else - { - switch ($aAction['type']) - { - case BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS: - // We can only make translate a dictionnary entry with a class placeholder when the action has a class tag. if it has a factory method, we don't know yet what class is going to be created - if ($aAction['factory']['type'] === BrowseBrick::ENUM_FACTORY_TYPE_CLASS) - { - $aAction['title'] = Dict::Format('Brick:Portal:Browse:Action:CreateObjectFromThis', MetaModel::GetName($aAction['factory']['value'])); - $aAction['url'] = $oApp['url_generator']->generate('p_object_create', array('sObjectClass' => $aAction['factory']['value'])); - } - else - { - $aAction['title'] = Dict::S('Brick:Portal:Browse:Action:Create'); - } - break; - case BrowseBrick::ENUM_ACTION_VIEW: - $aAction['title'] = Dict::S('Brick:Portal:Browse:Action:View'); - break; - case BrowseBrick::ENUM_ACTION_EDIT: - $aAction['title'] = Dict::S('Brick:Portal:Browse:Action:Edit'); - break; - case BrowseBrick::ENUM_ACTION_DRILLDOWN: - $aAction['title'] = Dict::S('Brick:Portal:Browse:Action:Drilldown'); - break; - } - } - - // Setting action icon class - if (!isset($aAction['icon_class'])) - { - switch ($aAction['type']) - { - case BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS: - $aAction['icon_class'] = BrowseBrick::ENUM_ACTION_ICON_CLASS_CREATE_FROM_THIS; - break; - case BrowseBrick::ENUM_ACTION_VIEW: - $aAction['icon_class'] = BrowseBrick::ENUM_ACTION_ICON_CLASS_VIEW; - break; - case BrowseBrick::ENUM_ACTION_EDIT: - $aAction['icon_class'] = BrowseBrick::ENUM_ACTION_ICON_CLASS_EDIT; - break; - case BrowseBrick::ENUM_ACTION_DRILLDOWN: - $aAction['icon_class'] = BrowseBrick::ENUM_ACTION_ICON_CLASS_DRILLDOWN; - break; - } - } - - // Setting action url - switch ($aAction['type']) - { - case BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS: - if ($aAction['factory']['type'] === BrowseBrick::ENUM_FACTORY_TYPE_CLASS) - { - $aAction['url'] = $oApp['url_generator']->generate('p_object_create', array('sObjectClass' => $aAction['factory']['value'])); - } - else - { - $aAction['url'] = $oApp['url_generator']->generate('p_object_create_from_factory', array('sEncodedMethodName' => base64_encode($aAction['factory']['value']), 'sObjectClass' => '-objectClass-', 'sObjectId' => '-objectId-')); - } - break; - } - - $aLevelsProperties[$sCurrentLevelAlias]['actions'][$sId] = $aAction; - } - } - } - } - } - - /** - * Prepares the action rules for an array of DBObject items. - * - * @param array $aItems - * @param string $sLevelsAlias - * @param array $aLevelsProperties - * - * @return array - */ - public static function PrepareActionRulesForItems(array $aItems, $sLevelsAlias, array &$aLevelsProperties) - { - $aActionRules = array(); - - foreach ($aLevelsProperties[$sLevelsAlias]['actions'] as $sId => $aAction) - { - $aActionRules[$sId] = ContextManipulatorHelper::PrepareAndEncodeRulesToken($aAction['rules'], $aItems); - } - - return $aActionRules; - } - - /** - * Takes $aCurrentRow as a flat array and transform it in another flat array (not objects) with only the necessary informations - * - * eg: - * - $aCurrentRow : array('L-1' => ObjectClass1, 'L-1-1' => ObjectClass2, 'L-1-1-1' => ObjectClass3) - * - $aRow will be : array( - * 'L1' => array( - * 'name' => 'Object class 1 name' - * ), - * 'L1-1' => array( - * 'name' => 'Object class 2 name', - * ), - * 'L1-1-1' => array( - * 'name' => 'Object class 3 name', - * ), - * ... - * ) - * - * @param array $aCurrentRow - * @param array $aLevelsProperties - * @param \Silex\Application $oApp - * - * @return array - * - * @throws \Exception - */ - public static function AddToFlatItems(array $aCurrentRow, array &$aLevelsProperties, Application $oApp) - { - $aRow = array(); - - foreach ($aCurrentRow as $key => $value) - { - // Retrieving objects from all levels - $aItems = array_values($aCurrentRow); - - $aRow[$key] = array( - 'level_alias' => $key, - 'id' => $value->GetKey(), - 'name' => $value->Get($aLevelsProperties[$key]['name_att']), - 'class' => get_class($value), - 'action_rules_token' => static::PrepareActionRulesForItems($aItems, $key, $aLevelsProperties) - ); - - // Adding optional attributes if necessary - foreach(static::$aOptionalAttributes as $sOptionalAttribute) - { - if ($aLevelsProperties[$key][$sOptionalAttribute] !== null) - { - $sPropertyName = substr($sOptionalAttribute, 0, -4); - $oAttDef = MetaModel::GetAttributeDef(get_class($value), $aLevelsProperties[$key][$sOptionalAttribute]); - - if($oAttDef instanceof AttributeImage) - { - $tmpAttValue = $value->Get($aLevelsProperties[$key][$sOptionalAttribute]); - if ($sOptionalAttribute === 'image_att') - { - if (is_object($tmpAttValue) && !$tmpAttValue->IsEmpty()) - { - $tmpAttValue = $oApp['url_generator']->generate('p_object_document_display', array( - 'sObjectClass' => get_class($value), - 'sObjectId' => $value->GetKey(), - 'sObjectField' => $aLevelsProperties[$key][$sOptionalAttribute], - 'cache' => 86400 - )); - } - else - { - $tmpAttValue = $oAttDef->Get('default_image'); - } - } - } - else - { - $tmpAttValue = $value->GetAsHTML($aLevelsProperties[$key][$sOptionalAttribute]); - } - - $aRow[$key][$sPropertyName] = $tmpAttValue; - } - } - // Adding fields attributes if necessary - if (!empty($aLevelsProperties[$key]['fields'])) - { - $aRow[$key]['fields'] = array(); - foreach ($aLevelsProperties[$key]['fields'] as $aField) - { - $oAttDef = MetaModel::GetAttributeDef(get_class($value), $aField['code']); - - $sHtmlForFieldValue = ''; - switch (get_class($oAttDef)) - { - case 'AttributeTagSet': - /** @var \ormTagSet $oSetValues */ - $oSetValues = $value->Get($aField['code']); - $aCodes = $oSetValues->GetTags(); - /** @var \AttributeTagSet $oAttDef */ - $sHtmlForFieldValue = $oAttDef->GenerateViewHtmlForValues($aCodes, '', false); - break; - default: - $sHtmlForFieldValue = $oAttDef->GetAsHTML($value->Get($aField['code'])); - break; - } - - $aRow[$key]['fields'][$aField['code']] = $sHtmlForFieldValue; - } - } - } - - return $aRow; - } - - /** - * Takes $aCurrentRow as a flat array to recursvily convert and insert it into a tree array $aItems. - * This is used to build a tree array from a DBObjectSet retrieved with FetchAssoc(). - * - * eg: - * - $aCurrentRow : array('L-1' => ObjectClass1, 'L-1-1' => ObjectClass2, 'L-1-1-1' => ObjectClass3) - * - $aItems will be : array( - * 'L1' => - * 'name' => 'Object class 1 name', - * 'subitems' => array( - * 'L1-1' => array( - * 'name' => 'Object class 2 name', - * 'subitems' => array( - * 'L1-1-1' => array( - * 'name' => 'Object class 3 name', - * 'subitems' => array() - * ), - * ... - * ) - * ), - * ... - * ) - * ), - * ... - * ) - * - * @param array &$aItems Reference to the array to be built - * @param array $aCurrentRow - * @param array $aLevelsProperties - * @param array|null $aCurrentRowObjects - * @param \Silex\Application|null $oApp - * - * @throws \Exception - */ - public static function AddToTreeItems(array &$aItems, array $aCurrentRow, array &$aLevelsProperties, $aCurrentRowObjects = null, Application $oApp = null) - { - $aCurrentRowKeys = array_keys($aCurrentRow); - $aCurrentRowValues = array_values($aCurrentRow); - $sCurrentIndex = $aCurrentRowKeys[0] . '::' . $aCurrentRowValues[0]->GetKey(); - - // We make sure to keep all row objects through levels by copying them when processing the first level. - // Otherwise they will be sliced through levels, one by one. - if($aCurrentRowObjects === null) - { - $aCurrentRowObjects = $aCurrentRowValues; - } - - if (!isset($aItems[$sCurrentIndex])) - { - $aItems[$sCurrentIndex] = array( - 'level_alias' => $aCurrentRowKeys[0], - 'id' => $aCurrentRowValues[0]->GetKey(), - 'name' => $aCurrentRowValues[0]->Get($aLevelsProperties[$aCurrentRowKeys[0]]['name_att']), - 'class' => get_class($aCurrentRowValues[0]), - 'subitems' => array(), - 'action_rules_token' => static::PrepareActionRulesForItems($aCurrentRowObjects, $aCurrentRowKeys[0], $aLevelsProperties) - ); - - // Adding optional attributes if necessary - foreach(static::$aOptionalAttributes as $sOptionalAttribute) - { - if ($aLevelsProperties[$aCurrentRowKeys[0]][$sOptionalAttribute] !== null) - { - $sPropertyName = substr($sOptionalAttribute, 0, -4); - $oAttDef = MetaModel::GetAttributeDef(get_class($aCurrentRowValues[0]), $aLevelsProperties[$aCurrentRowKeys[0]][$sOptionalAttribute]); - - if($oAttDef instanceof AttributeImage) - { - $tmpAttValue = $aCurrentRowValues[0]->Get($aLevelsProperties[$aCurrentRowKeys[0]][$sOptionalAttribute]); - if($sOptionalAttribute === 'image_att') - { - if (is_object($tmpAttValue) && !$tmpAttValue->IsEmpty()) - { - $tmpAttValue = $oApp['url_generator']->generate('p_object_document_display', array('sObjectClass' => get_class($aCurrentRowValues[0]), 'sObjectId' => $aCurrentRowValues[0]->GetKey(), 'sObjectField' => $aLevelsProperties[$aCurrentRowKeys[0]][$sOptionalAttribute], 'cache' => 86400)); - } - else - { - $tmpAttValue = $oAttDef->Get('default_image'); - } - } - } - else - { - $tmpAttValue = $aCurrentRowValues[0]->GetAsHTML($aLevelsProperties[$aCurrentRowKeys[0]][$sOptionalAttribute]); - } - - $aItems[$sCurrentIndex][$sPropertyName] = $tmpAttValue; - } - } - } - - $aCurrentRowSliced = array_slice($aCurrentRow, 1); - if (!empty($aCurrentRowSliced)) - { - static::AddToTreeItems($aItems[$sCurrentIndex]['subitems'], $aCurrentRowSliced, $aLevelsProperties, $aCurrentRowObjects, $oApp); - } - } - -} diff --git a/datamodels/2.x/itop-portal-base/portal/composer.lock b/datamodels/2.x/itop-portal-base/portal/composer.lock new file mode 100644 index 0000000000..341bf027fe --- /dev/null +++ b/datamodels/2.x/itop-portal-base/portal/composer.lock @@ -0,0 +1,1726 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "129fe68235aea6cd5fe73994f74a7962", + "packages": [ + { + "name": "paragonie/random_compat", + "version": "v2.0.18", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "0a58ef6e3146256cc3dc7cc393927bcc7d1b72db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/0a58ef6e3146256cc3dc7cc393927bcc7d1b72db", + "reference": "0a58ef6e3146256cc3dc7cc393927bcc7d1b72db", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "time": "2019-01-03T20:59:08+00:00" + }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "time": "2016-08-06T20:24:11+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/log", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2018-11-20T15:27:04+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "time": "2017-10-23T01:57:42+00:00" + }, + { + "name": "symfony/cache", + "version": "v3.4.29", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "8c6e162f0f7626771edbfa0a0e45b46623bbae1c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/8c6e162f0f7626771edbfa0a0e45b46623bbae1c", + "reference": "8c6e162f0f7626771edbfa0a0e45b46623bbae1c", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/cache": "~1.0", + "psr/log": "~1.0", + "psr/simple-cache": "^1.0", + "symfony/polyfill-apcu": "~1.1" + }, + "conflict": { + "symfony/var-dumper": "<3.3" + }, + "provide": { + "psr/cache-implementation": "1.0", + "psr/simple-cache-implementation": "1.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/cache": "~1.6", + "doctrine/dbal": "~2.4", + "predis/predis": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Cache component with PSR-6, PSR-16, and tags", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "time": "2019-06-17T17:26:15+00:00" + }, + { + "name": "symfony/class-loader", + "version": "v3.4.29", + "source": { + "type": "git", + "url": "https://github.com/symfony/class-loader.git", + "reference": "4459eef5298dedfb69f771186a580062b8516497" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/4459eef5298dedfb69f771186a580062b8516497", + "reference": "4459eef5298dedfb69f771186a580062b8516497", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "require-dev": { + "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/polyfill-apcu": "~1.1" + }, + "suggest": { + "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\ClassLoader\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony ClassLoader Component", + "homepage": "https://symfony.com", + "time": "2019-01-16T09:39:14+00:00" + }, + { + "name": "symfony/config", + "version": "v3.4.29", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "29a33f66194fbe2ed4555981810f15fd8440e4a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/29a33f66194fbe2ed4555981810f15fd8440e4a8", + "reference": "29a33f66194fbe2ed4555981810f15fd8440e4a8", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/filesystem": "~2.8|~3.0|~4.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/dependency-injection": "<3.3", + "symfony/finder": "<3.3" + }, + "require-dev": { + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/event-dispatcher": "~3.3|~4.0", + "symfony/finder": "~3.3|~4.0", + "symfony/yaml": "~3.0|~4.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2019-05-30T15:47:52+00:00" + }, + { + "name": "symfony/console", + "version": "v3.4.29", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "c4d2f3529755ffc0be9fb823583b28d8744eeb3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/c4d2f3529755ffc0be9fb823583b28d8744eeb3d", + "reference": "c4d2f3529755ffc0be9fb823583b28d8744eeb3d", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/debug": "~2.8|~3.0|~4.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.3|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~2.8|~3.0|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.3|~4.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2019-06-05T11:33:52+00:00" + }, + { + "name": "symfony/debug", + "version": "v3.4.29", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "1172dc1abe44dfadd162239153818b074e6e53bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/1172dc1abe44dfadd162239153818b074e6e53bf", + "reference": "1172dc1abe44dfadd162239153818b074e6e53bf", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/http-kernel": "~2.8|~3.0|~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "time": "2019-06-18T21:26:03+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v3.4.29", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "76857ce235ba1866b66a1d5be34c6794c8895435" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/76857ce235ba1866b66a1d5be34c6794c8895435", + "reference": "76857ce235ba1866b66a1d5be34c6794c8895435", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/container": "^1.0" + }, + "conflict": { + "symfony/config": "<3.3.7", + "symfony/finder": "<3.3", + "symfony/proxy-manager-bridge": "<3.4", + "symfony/yaml": "<3.4" + }, + "provide": { + "psr/container-implementation": "1.0" + }, + "require-dev": { + "symfony/config": "~3.3|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DependencyInjection Component", + "homepage": "https://symfony.com", + "time": "2019-05-30T15:47:52+00:00" + }, + { + "name": "symfony/dotenv", + "version": "v3.4.29", + "source": { + "type": "git", + "url": "https://github.com/symfony/dotenv.git", + "reference": "3f4fdfb551bf36f2017d75cd2e6490fbe67f9d2d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/3f4fdfb551bf36f2017d75cd2e6490fbe67f9d2d", + "reference": "3f4fdfb551bf36f2017d75cd2e6490fbe67f9d2d", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "require-dev": { + "symfony/process": "~3.2|~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Dotenv\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Registers environment variables from a .env file", + "homepage": "https://symfony.com", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "time": "2019-06-23T08:10:04+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.4.29", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "f18fdd6cc7006441865e698420cee26bac94741f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f18fdd6cc7006441865e698420cee26bac94741f", + "reference": "f18fdd6cc7006441865e698420cee26bac94741f", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0|~4.0", + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/stopwatch": "~2.8|~3.0|~4.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2019-06-25T07:45:31+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v3.4.29", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "70adda061ef83bb7def63a17953dc41f203308a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/70adda061ef83bb7def63a17953dc41f203308a7", + "reference": "70adda061ef83bb7def63a17953dc41f203308a7", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2019-06-23T09:29:17+00:00" + }, + { + "name": "symfony/finder", + "version": "v3.4.29", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "5f80266a729e30bbcc37f8bf0e62c3d5a38c8208" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/5f80266a729e30bbcc37f8bf0e62c3d5a38c8208", + "reference": "5f80266a729e30bbcc37f8bf0e62c3d5a38c8208", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2019-05-30T15:47:52+00:00" + }, + { + "name": "symfony/framework-bundle", + "version": "v3.4.29", + "source": { + "type": "git", + "url": "https://github.com/symfony/framework-bundle.git", + "reference": "ed26f0f9cef6ff47aa26766349272df653638f31" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/ed26f0f9cef6ff47aa26766349272df653638f31", + "reference": "ed26f0f9cef6ff47aa26766349272df653638f31", + "shasum": "" + }, + "require": { + "ext-xml": "*", + "php": "^5.5.9|>=7.0.8", + "symfony/cache": "~3.4|~4.0", + "symfony/class-loader": "~3.2", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "^3.4.24|^4.2.5", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/filesystem": "~2.8|~3.0|~4.0", + "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/http-foundation": "^3.3.11|~4.0", + "symfony/http-kernel": "~3.4|~4.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/routing": "^3.4.5|^4.0.5" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.0", + "phpdocumentor/type-resolver": "<0.2.1", + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/asset": "<3.3", + "symfony/console": "<3.4", + "symfony/form": "<3.4", + "symfony/property-info": "<3.3", + "symfony/serializer": "<3.3", + "symfony/stopwatch": "<3.4", + "symfony/translation": "<3.4", + "symfony/validator": "<3.4", + "symfony/workflow": "<3.3" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/cache": "~1.0", + "fig/link-util": "^1.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0", + "symfony/asset": "~3.3|~4.0", + "symfony/browser-kit": "~2.8|~3.0|~4.0", + "symfony/console": "~3.4|~4.0", + "symfony/css-selector": "~2.8|~3.0|~4.0", + "symfony/dom-crawler": "~2.8|~3.0|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/form": "^3.4.22|~4.1.11|^4.2.3", + "symfony/lock": "~3.4|~4.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/process": "~2.8|~3.0|~4.0", + "symfony/property-info": "~3.3|~4.0", + "symfony/security-core": "~3.2|~4.0", + "symfony/security-csrf": "^2.8.31|^3.3.13|~4.0", + "symfony/serializer": "~3.3|~4.0", + "symfony/stopwatch": "~3.4|~4.0", + "symfony/templating": "~2.8|~3.0|~4.0", + "symfony/translation": "~3.4|~4.0", + "symfony/validator": "~3.4|~4.0", + "symfony/var-dumper": "~3.3|~4.0", + "symfony/web-link": "~3.3|~4.0", + "symfony/workflow": "~3.3|~4.0", + "symfony/yaml": "~3.2|~4.0", + "twig/twig": "~1.34|~2.4" + }, + "suggest": { + "ext-apcu": "For best performance of the system caches", + "symfony/console": "For using the console commands", + "symfony/form": "For using forms", + "symfony/property-info": "For using the property_info service", + "symfony/serializer": "For using the serializer service", + "symfony/validator": "For using validation", + "symfony/web-link": "For using web links, features such as preloading, prefetching or prerendering", + "symfony/yaml": "For using the debug:config and lint:yaml commands" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bundle\\FrameworkBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony FrameworkBundle", + "homepage": "https://symfony.com", + "time": "2019-06-25T15:43:39+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v3.4.29", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "8cfbf75bb3a72963b12c513a73e9247891df24f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/8cfbf75bb3a72963b12c513a73e9247891df24f8", + "reference": "8cfbf75bb3a72963b12c513a73e9247891df24f8", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php70": "~1.6" + }, + "require-dev": { + "symfony/expression-language": "~2.8|~3.0|~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "https://symfony.com", + "time": "2019-06-22T20:10:25+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v3.4.29", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "abbb38dbab652ddc40a86d0c3b0e14ca52d58ed2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/abbb38dbab652ddc40a86d0c3b0e14ca52d58ed2", + "reference": "abbb38dbab652ddc40a86d0c3b0e14ca52d58ed2", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/log": "~1.0", + "symfony/debug": "^3.3.3|~4.0", + "symfony/event-dispatcher": "~2.8|~3.0|~4.0", + "symfony/http-foundation": "~3.4.12|~4.0.12|^4.1.1", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.4.10|<4.0.10,>=4", + "symfony/var-dumper": "<3.3", + "twig/twig": "<1.34|<2.4,>=2" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/cache": "~1.0", + "symfony/browser-kit": "~2.8|~3.0|~4.0", + "symfony/class-loader": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0|~4.0", + "symfony/console": "~2.8|~3.0|~4.0", + "symfony/css-selector": "~2.8|~3.0|~4.0", + "symfony/dependency-injection": "^3.4.10|^4.0.10", + "symfony/dom-crawler": "~2.8|~3.0|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/process": "~2.8|~3.0|~4.0", + "symfony/routing": "~3.4|~4.0", + "symfony/stopwatch": "~2.8|~3.0|~4.0", + "symfony/templating": "~2.8|~3.0|~4.0", + "symfony/translation": "~2.8|~3.0|~4.0", + "symfony/var-dumper": "~3.3|~4.0" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpKernel Component", + "homepage": "https://symfony.com", + "time": "2019-06-26T13:56:39+00:00" + }, + { + "name": "symfony/polyfill-apcu", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-apcu.git", + "reference": "a502face1da6a53289480166f24de2c3c68e5c3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/a502face1da6a53289480166f24de2c3c68e5c3c", + "reference": "a502face1da6a53289480166f24de2c3c68e5c3c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Apcu\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting apcu_* functions to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "apcu", + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "82ebae02209c21113908c229e9883c419720738a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", + "reference": "82ebae02209c21113908c229e9883c419720738a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "symfony/polyfill-php70", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "bc4858fb611bda58719124ca079baff854149c89" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/bc4858fb611bda58719124ca079baff854149c89", + "reference": "bc4858fb611bda58719124ca079baff854149c89", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "~1.0|~2.0|~9.99", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php70\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "symfony/routing", + "version": "v3.4.29", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "8d804d8a65a26dc9de1aaf2ff3a421e581d050e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/8d804d8a65a26dc9de1aaf2ff3a421e581d050e6", + "reference": "8d804d8a65a26dc9de1aaf2ff3a421e581d050e6", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "conflict": { + "symfony/config": "<3.3.1", + "symfony/dependency-injection": "<3.3", + "symfony/yaml": "<3.4" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "psr/log": "~1.0", + "symfony/config": "^3.3.1|~4.0", + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/http-foundation": "~2.8|~3.0|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Routing Component", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "time": "2019-06-26T11:14:13+00:00" + }, + { + "name": "symfony/twig-bridge", + "version": "v3.4.29", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "c5cda7f836ccfa80a84c27aec10aa16591333829" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/c5cda7f836ccfa80a84c27aec10aa16591333829", + "reference": "c5cda7f836ccfa80a84c27aec10aa16591333829", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "twig/twig": "^1.40|^2.9" + }, + "conflict": { + "symfony/console": "<3.4", + "symfony/form": "<3.4.13|>=4.0,<4.0.13|>=4.1,<4.1.2" + }, + "require-dev": { + "symfony/asset": "~2.8|~3.0|~4.0", + "symfony/console": "~3.4|~4.0", + "symfony/dependency-injection": "~2.8|~3.0|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/form": "^3.4.23|^4.2.4", + "symfony/http-foundation": "^3.3.11|~4.0", + "symfony/http-kernel": "~3.2|~4.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/routing": "~2.8|~3.0|~4.0", + "symfony/security": "^2.8.31|^3.3.13|~4.0", + "symfony/security-acl": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0|~4.0", + "symfony/templating": "~2.8|~3.0|~4.0", + "symfony/translation": "~2.8|~3.0|~4.0", + "symfony/var-dumper": "~2.8.10|~3.1.4|~3.2|~4.0", + "symfony/web-link": "~3.3|~4.0", + "symfony/workflow": "~3.3|~4.0", + "symfony/yaml": "~2.8|~3.0|~4.0" + }, + "suggest": { + "symfony/asset": "For using the AssetExtension", + "symfony/expression-language": "For using the ExpressionExtension", + "symfony/finder": "", + "symfony/form": "For using the FormExtension", + "symfony/http-kernel": "For using the HttpKernelExtension", + "symfony/routing": "For using the RoutingExtension", + "symfony/security": "For using the SecurityExtension", + "symfony/stopwatch": "For using the StopwatchExtension", + "symfony/templating": "For using the TwigEngine", + "symfony/translation": "For using the TranslationExtension", + "symfony/var-dumper": "For using the DumpExtension", + "symfony/web-link": "For using the WebLinkExtension", + "symfony/yaml": "For using the YamlExtension" + }, + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Twig\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Twig Bridge", + "homepage": "https://symfony.com", + "time": "2019-06-13T10:34:15+00:00" + }, + { + "name": "symfony/twig-bundle", + "version": "v3.4.29", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bundle.git", + "reference": "94c558cc915a6ac2e2de7d7f9a6b6e8a739a7c53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/94c558cc915a6ac2e2de7d7f9a6b6e8a739a7c53", + "reference": "94c558cc915a6ac2e2de7d7f9a6b6e8a739a7c53", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/config": "~3.2|~4.0", + "symfony/http-foundation": "~2.8|~3.0|~4.0", + "symfony/http-kernel": "^3.3|~4.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/twig-bridge": "^3.4.3|^4.0.3", + "twig/twig": "~1.40|~2.9" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<3.3.1" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/cache": "~1.0", + "symfony/asset": "~2.8|~3.0|~4.0", + "symfony/dependency-injection": "~3.4.24|^4.2.5", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/form": "~2.8|~3.0|~4.0", + "symfony/framework-bundle": "^3.3.11|~4.0", + "symfony/routing": "~2.8|~3.0|~4.0", + "symfony/stopwatch": "~2.8|~3.0|~4.0", + "symfony/templating": "~2.8|~3.0|~4.0", + "symfony/web-link": "~3.3|~4.0", + "symfony/yaml": "~2.8|~3.0|~4.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bundle\\TwigBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony TwigBundle", + "homepage": "https://symfony.com", + "time": "2019-05-30T15:47:52+00:00" + }, + { + "name": "symfony/yaml", + "version": "v3.4.29", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "212a27b731e5bfb735679d1ffaac82bd6a1dc996" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/212a27b731e5bfb735679d1ffaac82bd6a1dc996", + "reference": "212a27b731e5bfb735679d1ffaac82bd6a1dc996", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2019-03-25T07:48:46+00:00" + }, + { + "name": "twig/twig", + "version": "v1.42.2", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "21707d6ebd05476854805e4f91b836531941bcd4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/21707d6ebd05476854805e4f91b836531941bcd4", + "reference": "21707d6ebd05476854805e4f91b836531941bcd4", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/debug": "^2.7", + "symfony/phpunit-bridge": "^3.4.19|^4.1.8|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.42-dev" + } + }, + "autoload": { + "psr-0": { + "Twig_": "lib/" + }, + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + }, + { + "name": "Twig Team", + "homepage": "https://twig.symfony.com/contributors", + "role": "Contributors" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "time": "2019-06-18T15:35:16+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.6.0", + "ext-ctype": "*", + "ext-iconv": "*" + }, + "platform-dev": [], + "platform-overrides": { + "php": "5.6.0" + } +} diff --git a/datamodels/2.x/itop-portal-base/portal/config/services.yaml b/datamodels/2.x/itop-portal-base/portal/config/services.yaml index cc92c440a4..f957c99e60 100644 --- a/datamodels/2.x/itop-portal-base/portal/config/services.yaml +++ b/datamodels/2.x/itop-portal-base/portal/config/services.yaml @@ -46,8 +46,9 @@ services: # fetching services directly from the container via $container->get() won't work. # The best practice is to be explicit about your dependencies anyway. bind: + $bDebug: '%kernel.debug%' $sPortalCachePath: !php/const PORTAL_CACHE_PATH - $sPortalId: !php/const PORTAL_ID + $sPortalId: !php/const PORTAL_ID $sCombodoPortalInstanceAbsoluteUrl: !php/const COMBODO_PORTAL_INSTANCE_ABSOLUTE_URL # Makes classes in src/ available to be used as services @@ -124,4 +125,7 @@ services: public: true url_generator: alias: router + public: true + browse_brick: + alias: Combodo\iTop\Portal\Helper\BrowseBrickHelper public: true \ No newline at end of file diff --git a/datamodels/2.x/itop-portal-base/portal/src/Controller/.gitignore b/datamodels/2.x/itop-portal-base/portal/src/Controller/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/controllers/brickcontroller.class.inc.php b/datamodels/2.x/itop-portal-base/portal/src/Controller/BrickController.php similarity index 100% rename from datamodels/2.x/itop-portal-base/portal-silex/src/controllers/brickcontroller.class.inc.php rename to datamodels/2.x/itop-portal-base/portal/src/Controller/BrickController.php diff --git a/datamodels/2.x/itop-portal-base/portal/src/Controller/BrowseBrickController.php b/datamodels/2.x/itop-portal-base/portal/src/Controller/BrowseBrickController.php new file mode 100644 index 0000000000..875de55375 --- /dev/null +++ b/datamodels/2.x/itop-portal-base/portal/src/Controller/BrowseBrickController.php @@ -0,0 +1,412 @@ + + * @since 2.3.0 + */ +class BrowseBrickController extends BrickController +{ + /** + * @param \Symfony\Component\HttpFoundation\Request $oRequest + * @param string $sBrickId + * @param string $sBrowseMode + * @param string $sDataLoading + * + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \DictExceptionMissingString + * @throws \MissingQueryArgument + * @throws \MySQLException + * @throws \MySQLHasGoneAwayException + * @throws \OQLException + */ + public function DisplayAction(Request $oRequest, $sBrickId, $sBrowseMode = null, $sDataLoading = null) + { + /** @var \Combodo\iTop\Portal\Helper\BrowseBrickHelper $oBrowseBrickHelper */ + $oBrowseBrickHelper = $this->get('browse_brick'); + /** @var \Combodo\iTop\Portal\Helper\RequestManipulatorHelper $oRequestManipulator */ + $oRequestManipulator = $this->get('request_manipulator'); + + /** @var \Combodo\iTop\Portal\Brick\BrowseBrick $oBrick */ + $oBrick = $this->get('brick_collection')->getBrickById($sBrickId); + + // Getting availables browse modes + $aBrowseModes = $oBrick->GetAvailablesBrowseModes(); + $aBrowseButtons = array_keys($aBrowseModes); + // Getting current browse mode (First from router pamater, then default brick value) + $sBrowseMode = (!empty($sBrowseMode)) ? $sBrowseMode : $oBrick->GetDefaultBrowseMode(); + // Getting current dataloading mode (First from router parameter, then query parameter, then default brick value) + $sDataLoading = ($sDataLoading !== null) ? $sDataLoading : $oRequestManipulator->ReadParam('sDataLoading', $oBrick->GetDataLoading()); + // Getting search value + $sSearchValue = $oRequestManipulator->ReadParam('sSearchValue', ''); + if (!empty($sSearchValue)) + { + $sDataLoading = AbstractBrick::ENUM_DATA_LOADING_LAZY; + } + + $aData = array(); + $aLevelsProperties = array(); + $aLevelsClasses = array(); + $oBrowseBrickHelper->TreeToFlatLevelsProperties($oBrick->GetLevels(), $aLevelsProperties); + + // Consistency checks + if (!in_array($sBrowseMode, array_keys($aBrowseModes))) + { + throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR, 'Browse brick "' . $sBrickId . '" : Unknown browse mode "' . $sBrowseMode . '", availables are ' . implode(' / ', array_keys($aBrowseModes))); + } + if (empty($aLevelsProperties)) + { + throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR, 'Browse brick "' . $sBrickId . '" : No levels to display.'); + } + + // Building DBObjectSearch + $oQuery = null; + // ... In this case only we have to build a specific query for the current level only + if (in_array($sBrowseMode, array(BrowseBrick::ENUM_BROWSE_MODE_TREE, BrowseBrick::ENUM_BROWSE_MODE_MOSAIC)) && ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_LAZY)) + { + // Will be handled later in the pagination part + } + // .. Otherwise + else + { + // We iterate (in reverse mode /!\) over the levels to build the whole query, starting from the bottom + $aLevelsPropertiesKeys = array_keys($aLevelsProperties); + $iLoopMax = count($aLevelsPropertiesKeys) - 1; + $oFullBinExpr = null; + for ($i = $iLoopMax; $i >= 0; $i--) + { + // Retrieving class alias for all depth + array_unshift($aLevelsClasses, $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->GetClassAlias()); + + // Joining queries from bottom-up + if ($i < $iLoopMax) + { + $aRealiasingMap = array(); + $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search'] = $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->Join($aLevelsProperties[$aLevelsPropertiesKeys[$i + 1]]['search'], DBSearch::JOIN_REFERENCED_BY, $aLevelsProperties[$aLevelsPropertiesKeys[$i + 1]]['parent_att'], TREE_OPERATOR_EQUALS, $aRealiasingMap); + foreach ($aLevelsPropertiesKeys as $sLevelAlias) + { + if (array_key_exists($sLevelAlias, $aRealiasingMap)) + { + $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->RenameAlias($aRealiasingMap[$sLevelAlias], $sLevelAlias); + } + } + } + + // Adding search clause + // Note : For know the search is naive and looks only for the exact match. It doesn't search for words separately + if (!empty($sSearchValue)) + { + // - Cleaning the search value by exploding and trimming spaces + $aSearchValues = explode(' ', $sSearchValue); + array_walk($aSearchValues, function (&$sSearchValue /*, $sKey*/) { + trim($sSearchValue); + }); + + // - Retrieving fields to search + $aSearchFields = array($aLevelsProperties[$aLevelsPropertiesKeys[$i]]['name_att']); + if (!empty($aLevelsProperties[$aLevelsPropertiesKeys[$i]]['fields'])) + { + foreach ($aLevelsProperties[$aLevelsPropertiesKeys[$i]]['fields'] as $aTmpField) + { + $aSearchFields[] = $aTmpField['code']; + } + } + // - Building query for the search values parts + $oLevelBinExpr = null; + $iFieldLoopMax = count($aSearchFields) - 1; + $iSearchLoopMax = count($aSearchValues) - 1; + for ($j = 0; $j <= $iFieldLoopMax; $j++) + { + $sTmpFieldAttCode = $aSearchFields[$j]; + $oFieldBinExpr = null; + //$oFieldBinExpr = new BinaryExpression(new FieldExpression($aSearchFields[$j], $aLevelsPropertiesKeys[$i]), ) + + for ($k = 0; $k <= $iSearchLoopMax; $k++) + { + $oSearchBinExpr = new BinaryExpression(new FieldExpression($sTmpFieldAttCode, $aLevelsPropertiesKeys[$i]), 'LIKE', new VariableExpression('search_value_' . $k)); + if ($k === 0) + { + $oFieldBinExpr = $oSearchBinExpr; + } + else + { + $oFieldBinExpr = new BinaryExpression($oFieldBinExpr, 'AND', $oSearchBinExpr); + } + } + + if ($j === 0) + { + $oLevelBinExpr = $oFieldBinExpr; + } + else + { + $oLevelBinExpr = new BinaryExpression($oLevelBinExpr, 'OR', $oFieldBinExpr); + } + } + + // - Building query for the level + if ($i === $iLoopMax) + { + $oFullBinExpr = $oLevelBinExpr; + } + else + { + $oFullBinExpr = new BinaryExpression($oFullBinExpr, 'OR', $oLevelBinExpr); + } + + // - Adding it to the query when complete + if ($i === 0) + { + $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->AddConditionExpression($oFullBinExpr); + } + } + + // Setting selected classes and binding parameters + if ($i === 0) + { + $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->SetSelectedClasses($aLevelsClasses); + + if (!empty($sSearchValue)) + { + // Note : This could be way more simpler if we had a SetInternalParam($sParam, $value) verb + $aQueryParams = $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->GetInternalParams(); + // Note : $iSearchloopMax was initialized on the previous loop + for ($j = 0; $j <= $iSearchLoopMax; $j++) + { + $aQueryParams['search_value_' . $j] = '%' . $aSearchValues[$j] . '%'; + } + $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->SetInternalParams($aQueryParams); + } + } + } + $oQuery = $aLevelsProperties[$aLevelsPropertiesKeys[0]]['search']; + + // Testing appropriate data loading mode if we are in auto + if ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_AUTO) + { + // - Check how many records there is. + // - Update $sDataLoading with its new value regarding the number of record and the threshold + $oCountSet = new DBObjectSet($oQuery); + $fThreshold = (float) MetaModel::GetModuleSetting($this->getParameter('combodo.portal.instance.id'), 'lazy_loading_threshold'); + $sDataLoading = ($oCountSet->Count() > $fThreshold) ? AbstractBrick::ENUM_DATA_LOADING_LAZY : AbstractBrick::ENUM_DATA_LOADING_FULL; + unset($oCountSet); + } + } + + // Setting query pagination if needed + if ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_LAZY) + { + switch ($sBrowseMode) + { + case BrowseBrick::ENUM_BROWSE_MODE_LIST: + // Retrieving parameters + $iPageNumber = (int) $oRequestManipulator->ReadParam('iPageNumber', 1, FILTER_SANITIZE_NUMBER_INT); + $iListLength = (int) $oRequestManipulator->ReadParam('iListLength', BrowseBrick::DEFAULT_LIST_LENGTH, FILTER_SANITIZE_NUMBER_INT); + + // Getting total records number + $oCountSet = new DBObjectSet($oQuery); + $aData['recordsTotal'] = $oCountSet->Count(); + $aData['recordsFiltered'] = $oCountSet->Count(); + unset($oCountSet); + + $oSet = new DBObjectSet($oQuery); + $oSet->SetLimit($iListLength, $iListLength * ($iPageNumber - 1)); + + break; + case BrowseBrick::ENUM_BROWSE_MODE_TREE: + case BrowseBrick::ENUM_BROWSE_MODE_MOSAIC: + // Retrieving parameters + $sLevelAlias = $oRequestManipulator->ReadParam('sLevelAlias', ''); + $sNodeId = $oRequestManipulator->ReadParam('sNodeId', ''); + + // If no values for those parameters, we might be loading page in lazy mode for the first time, therefore the URL doesn't have those informations. + if (empty($sLevelAlias)) + { + reset($aLevelsProperties); + $oQuery = $aLevelsProperties[key($aLevelsProperties)]['search']; + if (!empty($sNodeId)) + { + $oQuery->AddCondition('id', $sNodeId); + } + } + // Else we need to find the OQL for that particular level + else + { + $bFoundLevel = false; + foreach ($aLevelsProperties as $aLevelProperties) + { + if ($aLevelProperties['alias'] === $sLevelAlias) + { + if (isset($aLevelProperties['levels']) && !empty($aLevelProperties['levels']) && isset($aLevelsProperties[$aLevelProperties['levels'][0]])) + { + $oQuery = $aLevelsProperties[$aLevelProperties['levels'][0]]['search']; + if (!empty($sNodeId)) + { + $oQuery->AddCondition($aLevelsProperties[$aLevelProperties['levels'][0]]['parent_att'], $sNodeId); + } + $bFoundLevel = true; + break; + } + } + } + + if (!$bFoundLevel) + { + throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR, 'Browse brick "' . $sBrickId . '" : Level alias "' . $sLevelAlias . '" is not defined for that brick.'); + } + } + + $oSet = new DBObjectSet($oQuery); + break; + + default: + // We should never be there. If there is an other browse mode for that brick : + // - If it's from a custom brick extension, it should be handle by the extension router/controller + // - If it's from a base brick, it should be handle in a case above this one + // - If none of the previous statements was done, this fail safe will load all data as it's not able to know how to handle the pagination + $oSet = new DBObjectSet($oQuery); + break; + } + } + else + { + $oSet = new DBObjectSet($oQuery); + } + + // Optimizing the ObjectSet to retrieve only necessary columns + $aColumnAttrs = array(); + foreach ($oSet->GetFilter()->GetSelectedClasses() as $sTmpClassAlias => $sTmpClassName) + { + if (isset($aLevelsProperties[$sTmpClassAlias])) + { + $aTmpLevelProperties = $aLevelsProperties[$sTmpClassAlias]; + // Mandatory main attribute + $aTmpColumnAttrs = array($aTmpLevelProperties['name_att']); + // Optional attributes, only if in list mode + if ($sBrowseMode === BrowseBrick::ENUM_BROWSE_MODE_LIST) + { + foreach ($aTmpLevelProperties['fields'] as $aTmpField) + { + $aTmpColumnAttrs[] = $aTmpField['code']; + } + } + // Optional attributes + foreach (BrowseBrickHelper::OPTIONAL_ATTRIBUTES as $sOptionalAttribute) + { + if ($aTmpLevelProperties[$sOptionalAttribute] !== null) + { + $aTmpColumnAttrs[] = $aTmpLevelProperties[$sOptionalAttribute]; + } + } + + $aColumnAttrs[$sTmpClassAlias] = $aTmpColumnAttrs; + } + } + $oSet->OptimizeColumnLoad($aColumnAttrs); + + // Sorting objects through defined order (in DM) + $oSet->SetOrderByClasses(); + + // Retrieving results and organizing them for templating + $aItems = array(); + while ($aCurrentRow = $oSet->FetchAssoc()) + { + switch ($sBrowseMode) + { + case BrowseBrick::ENUM_BROWSE_MODE_TREE: + case BrowseBrick::ENUM_BROWSE_MODE_MOSAIC: + $oBrowseBrickHelper->AddToTreeItems($aItems, $aCurrentRow, $aLevelsProperties, null); + break; + + case BrowseBrick::ENUM_BROWSE_MODE_LIST: + default: + $aItems[] = $oBrowseBrickHelper->AddToFlatItems($aCurrentRow, $aLevelsProperties); + break; + } + } + + // Preparing response + if ($oRequest->isXmlHttpRequest()) + { + $aData = $aData + array( + 'data' => $aItems, + 'levelsProperties' => $aLevelsProperties, + ); + $oResponse = new JsonResponse($aData); + } + else + { + $aData = $aData + array( + 'oBrick' => $oBrick, + 'sBrickId' => $sBrickId, + 'sBrowseMode' => $sBrowseMode, + 'aBrowseButtons' => $aBrowseButtons, + 'sSearchValue' => $sSearchValue, + 'sDataLoading' => $sDataLoading, + 'aItems' => json_encode($aItems), + 'iItemsCount' => count($aItems), + 'aLevelsProperties' => json_encode($aLevelsProperties), + ); + + // Note : To extend this brick's template, depending on what you want to do : + // a) Modify the whole template : + // - Create a template and specify it in the brick configuration + // b) Add a new browse mode : + // - Create a template for that browse mode, + // - Add the mode to those availables in the brick configuration, + // - Create a router and add a route for the new browse mode + if ($oBrick->GetPageTemplatePath() !== null) + { + $sTemplatePath = $oBrick->GetPageTemplatePath(); + } + else + { + $sTemplatePath = $aBrowseModes[$sBrowseMode]['template']; + } + $oResponse = $this->render($sTemplatePath, $aData); + } + + return $oResponse; + } +} diff --git a/datamodels/2.x/itop-portal-base/portal/src/Helper/BrowseBrickHelper.php b/datamodels/2.x/itop-portal-base/portal/src/Helper/BrowseBrickHelper.php new file mode 100644 index 0000000000..b8b44d19e1 --- /dev/null +++ b/datamodels/2.x/itop-portal-base/portal/src/Helper/BrowseBrickHelper.php @@ -0,0 +1,482 @@ +oSecurityHelper = $oSecurityHelper; + $this->oScopeValidator = $oScopeValidator; + $this->oUrlGenerator = $oUrlGenerator; + } + + /** + * Flattens the $aLevels into $aLevelsProperties in order to be able to build an OQL query from multiple single queries related to each + * others. As of now it only keeps search / parent_att / name_att properties. + * + * Note : This is not in the BrowseBrick class because the classes should not rely on DBObjectSearch. + * + * @param array $aLevels Levels from a BrowseBrick class + * @param array $aLevelsProperties Reference to an array that will contain the flattened levels + * @param string $sLevelAliasPrefix String that will be prefixed to the level ID as an unique path identifier + * + * @throws \CoreException + * @throws \DictExceptionMissingString + * @throws \MissingQueryArgument + * @throws \MySQLException + * @throws \MySQLHasGoneAwayException + * @throws \OQLException + */ + public function TreeToFlatLevelsProperties(array $aLevels, array &$aLevelsProperties, $sLevelAliasPrefix = 'L') + { + foreach ($aLevels as $aLevel) + { + $sCurrentLevelAlias = $sLevelAliasPrefix . static::LEVEL_SEPARATOR . $aLevel['id']; + $oSearch = DBSearch::CloneWithAlias(DBSearch::FromOQL($aLevel['oql']), $sCurrentLevelAlias); + + // Restricting to the allowed scope + $oScopeSearch = $this->oScopeValidator->GetScopeFilterForProfiles(UserRights::ListProfiles(), $oSearch->GetClass(), UR_ACTION_READ); + $oSearch = ($oScopeSearch !== null) ? $oSearch->Intersect($oScopeSearch) : null; + // - Allowing all data if necessary + if ($oScopeSearch !== null && $oScopeSearch->IsAllDataAllowed()) + { + $oSearch->AllowAllData(); + } + + if ($oSearch !== null) + { + $aLevelsProperties[$sCurrentLevelAlias] = array( + 'alias' => $sCurrentLevelAlias, + 'title' => ($aLevel['title'] !== null) ? Dict::S($aLevel['title']) : MetaModel::GetName($oSearch->GetClass()), + 'parent_att' => $aLevel['parent_att'], + 'name_att' => $aLevel['name_att'], + 'tooltip_att' => $aLevel['tooltip_att'], + 'description_att' => $aLevel['description_att'], + 'image_att' => $aLevel['image_att'], + 'search' => $oSearch, + 'fields' => array(), + 'actions' => array() + ); + + // Adding current level's fields + if (isset($aLevel['fields'])) + { + $aLevelsProperties[$sCurrentLevelAlias]['fields'] = array(); + + foreach ($aLevel['fields'] as $sFieldAttCode => $aFieldProperties) + { + $aLevelsProperties[$sCurrentLevelAlias]['fields'][] = array( + 'code' => $sFieldAttCode, + 'label' => MetaModel::GetAttributeDef($oSearch->GetClass(), $sFieldAttCode)->GetLabel(), + 'hidden' => $aFieldProperties['hidden'] + ); + } + } + + // Flattening and adding sublevels + if (isset($aLevel['levels'])) + { + foreach ($aLevel['levels'] as $aChildLevel) + { + // Checking if the sublevel if allowed + $oChildSearch = DBSearch::FromOQL($aChildLevel['oql']); + if ($this->oSecurityHelper->IsActionAllowed(UR_ACTION_READ, $oChildSearch->GetClass())) + { + // Adding the sublevel to this one + $aLevelsProperties[$sCurrentLevelAlias]['levels'][] = $sCurrentLevelAlias . static::LEVEL_SEPARATOR . $aChildLevel['id']; + + // Adding drilldown action if necessary + foreach ($aLevel['actions'] as $sId => $aAction) + { + if ($aAction['type'] === BrowseBrick::ENUM_ACTION_DRILLDOWN) + { + $aLevelsProperties[$sCurrentLevelAlias]['actions'][$sId] = $aAction; + break; + } + } + } + unset($oChildSearch); + } + $this->TreeToFlatLevelsProperties($aLevel['levels'], $aLevelsProperties, $sCurrentLevelAlias); + } + + // Adding actions to the level + foreach ($aLevel['actions'] as $sId => $aAction) + { + // ... Only if it's not already there (eg. the drilldown added with the sublevels) + if (!array_key_exists($sId, $aLevelsProperties[$sCurrentLevelAlias]['actions'])) + { + // Adding action only if allowed + if (($aAction['type'] === BrowseBrick::ENUM_ACTION_VIEW) && !$this->oSecurityHelper->IsActionAllowed(UR_ACTION_READ, $oSearch->GetClass())) + { + continue; + } + elseif (($aAction['type'] === BrowseBrick::ENUM_ACTION_EDIT) && !$this->oSecurityHelper->IsActionAllowed(UR_ACTION_MODIFY, $oSearch->GetClass())) + { + continue; + } + elseif ($aAction['type'] === BrowseBrick::ENUM_ACTION_DRILLDOWN) + { + continue; + } + + // Setting action title + if (isset($aAction['title'])) + { + // Note : There could be an enhancement here, by checking if the string code has the '%1' needle and use Dict::S or Dict::Format accordingly. + // But it would require to benchmark a potential performance drop as it will be done for all items + $aAction['title'] = Dict::S($aAction['title']); + } + else + { + switch ($aAction['type']) + { + case BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS: + // We can only make translate a dictionnary entry with a class placeholder when the action has a class tag. if it has a factory method, we don't know yet what class is going to be created + if ($aAction['factory']['type'] === BrowseBrick::ENUM_FACTORY_TYPE_CLASS) + { + $aAction['title'] = Dict::Format('Brick:Portal:Browse:Action:CreateObjectFromThis', MetaModel::GetName($aAction['factory']['value'])); + $aAction['url'] = $this->oUrlGenerator->generate('p_object_create', array('sObjectClass' => $aAction['factory']['value'])); + } + else + { + $aAction['title'] = Dict::S('Brick:Portal:Browse:Action:Create'); + } + break; + case BrowseBrick::ENUM_ACTION_VIEW: + $aAction['title'] = Dict::S('Brick:Portal:Browse:Action:View'); + break; + case BrowseBrick::ENUM_ACTION_EDIT: + $aAction['title'] = Dict::S('Brick:Portal:Browse:Action:Edit'); + break; + case BrowseBrick::ENUM_ACTION_DRILLDOWN: + $aAction['title'] = Dict::S('Brick:Portal:Browse:Action:Drilldown'); + break; + } + } + + // Setting action icon class + if (!isset($aAction['icon_class'])) + { + switch ($aAction['type']) + { + case BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS: + $aAction['icon_class'] = BrowseBrick::ENUM_ACTION_ICON_CLASS_CREATE_FROM_THIS; + break; + case BrowseBrick::ENUM_ACTION_VIEW: + $aAction['icon_class'] = BrowseBrick::ENUM_ACTION_ICON_CLASS_VIEW; + break; + case BrowseBrick::ENUM_ACTION_EDIT: + $aAction['icon_class'] = BrowseBrick::ENUM_ACTION_ICON_CLASS_EDIT; + break; + case BrowseBrick::ENUM_ACTION_DRILLDOWN: + $aAction['icon_class'] = BrowseBrick::ENUM_ACTION_ICON_CLASS_DRILLDOWN; + break; + } + } + + // Setting action url + switch ($aAction['type']) + { + case BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS: + if ($aAction['factory']['type'] === BrowseBrick::ENUM_FACTORY_TYPE_CLASS) + { + $aAction['url'] = $this->oUrlGenerator->generate('p_object_create', array('sObjectClass' => $aAction['factory']['value'])); + } + else + { + $aAction['url'] = $this->oUrlGenerator->generate('p_object_create_from_factory', array('sEncodedMethodName' => base64_encode($aAction['factory']['value']), 'sObjectClass' => '-objectClass-', 'sObjectId' => '-objectId-')); + } + break; + } + + $aLevelsProperties[$sCurrentLevelAlias]['actions'][$sId] = $aAction; + } + } + } + } + } + + /** + * Prepares the action rules for an array of DBObject items. + * + * @param array $aItems + * @param string $sLevelsAlias + * @param array $aLevelsProperties + * + * @return array + */ + public function PrepareActionRulesForItems(array $aItems, $sLevelsAlias, array &$aLevelsProperties) + { + $aActionRules = array(); + + foreach ($aLevelsProperties[$sLevelsAlias]['actions'] as $sId => $aAction) + { + $aActionRules[$sId] = ContextManipulatorHelper::PrepareAndEncodeRulesToken($aAction['rules'], $aItems); + } + + return $aActionRules; + } + + /** + * Takes $aCurrentRow as a flat array and transform it in another flat array (not objects) with only the necessary informations + * + * eg: + * - $aCurrentRow : array('L-1' => ObjectClass1, 'L-1-1' => ObjectClass2, 'L-1-1-1' => ObjectClass3) + * - $aRow will be : array( + * 'L1' => array( + * 'name' => 'Object class 1 name' + * ), + * 'L1-1' => array( + * 'name' => 'Object class 2 name', + * ), + * 'L1-1-1' => array( + * 'name' => 'Object class 3 name', + * ), + * ... + * ) + * + * @param array $aCurrentRow + * @param array $aLevelsProperties + * + * @return array + * + * @throws \CoreException + * @throws \OQLException + */ + public function AddToFlatItems(array $aCurrentRow, array &$aLevelsProperties) + { + $aRow = array(); + + foreach ($aCurrentRow as $key => $value) + { + // Retrieving objects from all levels + $aItems = array_values($aCurrentRow); + + $aRow[$key] = array( + 'level_alias' => $key, + 'id' => $value->GetKey(), + 'name' => $value->Get($aLevelsProperties[$key]['name_att']), + 'class' => get_class($value), + 'action_rules_token' => $this->PrepareActionRulesForItems($aItems, $key, $aLevelsProperties) + ); + + // Adding optional attributes if necessary + foreach(static::OPTIONAL_ATTRIBUTES as $sOptionalAttribute) + { + if ($aLevelsProperties[$key][$sOptionalAttribute] !== null) + { + $sPropertyName = substr($sOptionalAttribute, 0, -4); + $oAttDef = MetaModel::GetAttributeDef(get_class($value), $aLevelsProperties[$key][$sOptionalAttribute]); + + if($oAttDef instanceof AttributeImage) + { + $tmpAttValue = $value->Get($aLevelsProperties[$key][$sOptionalAttribute]); + if ($sOptionalAttribute === 'image_att') + { + if (is_object($tmpAttValue) && !$tmpAttValue->IsEmpty()) + { + $tmpAttValue = $this->oUrlGenerator->generate('p_object_document_display', array( + 'sObjectClass' => get_class($value), + 'sObjectId' => $value->GetKey(), + 'sObjectField' => $aLevelsProperties[$key][$sOptionalAttribute], + 'cache' => 86400 + )); + } + else + { + $tmpAttValue = $oAttDef->Get('default_image'); + } + } + } + else + { + $tmpAttValue = $value->GetAsHTML($aLevelsProperties[$key][$sOptionalAttribute]); + } + + $aRow[$key][$sPropertyName] = $tmpAttValue; + } + } + // Adding fields attributes if necessary + if (!empty($aLevelsProperties[$key]['fields'])) + { + $aRow[$key]['fields'] = array(); + foreach ($aLevelsProperties[$key]['fields'] as $aField) + { + $oAttDef = MetaModel::GetAttributeDef(get_class($value), $aField['code']); + + $sHtmlForFieldValue = ''; + switch (get_class($oAttDef)) + { + case 'AttributeTagSet': + /** @var \ormTagSet $oSetValues */ + $oSetValues = $value->Get($aField['code']); + $aCodes = $oSetValues->GetTags(); + /** @var \AttributeTagSet $oAttDef */ + $sHtmlForFieldValue = $oAttDef->GenerateViewHtmlForValues($aCodes, '', false); + break; + default: + $sHtmlForFieldValue = $oAttDef->GetAsHTML($value->Get($aField['code'])); + break; + } + + $aRow[$key]['fields'][$aField['code']] = $sHtmlForFieldValue; + } + } + } + + return $aRow; + } + + /** + * Takes $aCurrentRow as a flat array to recursvily convert and insert it into a tree array $aItems. + * This is used to build a tree array from a DBObjectSet retrieved with FetchAssoc(). + * + * eg: + * - $aCurrentRow : array('L-1' => ObjectClass1, 'L-1-1' => ObjectClass2, 'L-1-1-1' => ObjectClass3) + * - $aItems will be : array( + * 'L1' => + * 'name' => 'Object class 1 name', + * 'subitems' => array( + * 'L1-1' => array( + * 'name' => 'Object class 2 name', + * 'subitems' => array( + * 'L1-1-1' => array( + * 'name' => 'Object class 3 name', + * 'subitems' => array() + * ), + * ... + * ) + * ), + * ... + * ) + * ), + * ... + * ) + * + * @param array & $aItems Reference to the array to be built + * @param array $aCurrentRow + * @param array $aLevelsProperties + * @param array|null $aCurrentRowObjects + * + * @throws \Exception + */ + public function AddToTreeItems(array &$aItems, array $aCurrentRow, array &$aLevelsProperties, $aCurrentRowObjects = null) + { + $aCurrentRowKeys = array_keys($aCurrentRow); + $aCurrentRowValues = array_values($aCurrentRow); + $sCurrentIndex = $aCurrentRowKeys[0] . '::' . $aCurrentRowValues[0]->GetKey(); + + // We make sure to keep all row objects through levels by copying them when processing the first level. + // Otherwise they will be sliced through levels, one by one. + if($aCurrentRowObjects === null) + { + $aCurrentRowObjects = $aCurrentRowValues; + } + + if (!isset($aItems[$sCurrentIndex])) + { + $aItems[$sCurrentIndex] = array( + 'level_alias' => $aCurrentRowKeys[0], + 'id' => $aCurrentRowValues[0]->GetKey(), + 'name' => $aCurrentRowValues[0]->Get($aLevelsProperties[$aCurrentRowKeys[0]]['name_att']), + 'class' => get_class($aCurrentRowValues[0]), + 'subitems' => array(), + 'action_rules_token' => $this->PrepareActionRulesForItems($aCurrentRowObjects, $aCurrentRowKeys[0], $aLevelsProperties) + ); + + // Adding optional attributes if necessary + foreach(static::OPTIONAL_ATTRIBUTES as $sOptionalAttribute) + { + if ($aLevelsProperties[$aCurrentRowKeys[0]][$sOptionalAttribute] !== null) + { + $sPropertyName = substr($sOptionalAttribute, 0, -4); + $oAttDef = MetaModel::GetAttributeDef(get_class($aCurrentRowValues[0]), $aLevelsProperties[$aCurrentRowKeys[0]][$sOptionalAttribute]); + + if($oAttDef instanceof AttributeImage) + { + $tmpAttValue = $aCurrentRowValues[0]->Get($aLevelsProperties[$aCurrentRowKeys[0]][$sOptionalAttribute]); + if($sOptionalAttribute === 'image_att') + { + if (is_object($tmpAttValue) && !$tmpAttValue->IsEmpty()) + { + $tmpAttValue = $this->oUrlGenerator->generate('p_object_document_display', array('sObjectClass' => get_class($aCurrentRowValues[0]), 'sObjectId' => $aCurrentRowValues[0]->GetKey(), 'sObjectField' => $aLevelsProperties[$aCurrentRowKeys[0]][$sOptionalAttribute], 'cache' => 86400)); + } + else + { + $tmpAttValue = $oAttDef->Get('default_image'); + } + } + } + else + { + $tmpAttValue = $aCurrentRowValues[0]->GetAsHTML($aLevelsProperties[$aCurrentRowKeys[0]][$sOptionalAttribute]); + } + + $aItems[$sCurrentIndex][$sPropertyName] = $tmpAttValue; + } + } + } + + $aCurrentRowSliced = array_slice($aCurrentRow, 1); + if (!empty($aCurrentRowSliced)) + { + $this->AddToTreeItems($aItems[$sCurrentIndex]['subitems'], $aCurrentRowSliced, $aLevelsProperties, $aCurrentRowObjects); + } + } +} \ No newline at end of file diff --git a/datamodels/2.x/itop-portal-base/portal/src/Helper/SecurityHelper.php b/datamodels/2.x/itop-portal-base/portal/src/Helper/SecurityHelper.php index b54724f8be..b67ad46eb7 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Helper/SecurityHelper.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Helper/SecurityHelper.php @@ -4,7 +4,7 @@ // // This file is part of iTop. // -// iTop is free software; you can redistribute it and/or modify +// iTop is free software; you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. @@ -44,35 +44,56 @@ class SecurityHelper UR_ACTION_MODIFY => array(), ); - /** + /** + * @var \Combodo\iTop\Portal\Helper\ScopeValidatorHelper + */ + private $oScopeValidator; + /** + * @var \Combodo\iTop\Portal\Helper\LifecycleValidatorHelper + */ + private $oLifecycleValidator; + /** @var bool $bDebug */ + private $bDebug; + + /** + * SecurityHelper constructor. + * + * @param \Combodo\iTop\Portal\Helper\ScopeValidatorHelper $oScopeValidator + * @param \Combodo\iTop\Portal\Helper\LifecycleValidatorHelper $oLifecycleValidator + * @param $bDebug + */ + public function __construct(ScopeValidatorHelper $oScopeValidator, LifecycleValidatorHelper $oLifecycleValidator, $bDebug) + { + $this->oScopeValidator = $oScopeValidator; + $this->oLifecycleValidator = $oLifecycleValidator; + $this->bDebug = $bDebug; + } + + + /** * Returns true if the current user is allowed to do the $sAction on an $sObjectClass object (with optionnal $sObjectId id) * Checks are: * - Has a scope query for the $sObjectClass / $sAction * - Optionally, if $sObjectId provided: Is object within scope for $sObjectClass / $sObjectId / $sAction * - Is allowed by datamodel for $sObjectClass / $sAction * - * @param ScopeValidatorHelper $scopeValidator - * @param boolean $isDebugEnabled - * @param string $sAction Must be in UR_ACTION_READ|UR_ACTION_MODIFY|UR_ACTION_CREATE - * @param string $sObjectClass - * @param string $sObjectId + * @param \Silex\Application $oApp + * @param string $sAction Must be in UR_ACTION_READ|UR_ACTION_MODIFY|UR_ACTION_CREATE + * @param string $sObjectClass + * @param string $sObjectId * * @return boolean * * @throws \CoreException - * @throws \MissingQueryArgument - * @throws \MySQLException - * @throws \MySQLHasGoneAwayException - * @throws \OQLException */ - public static function IsActionAllowed(ScopeValidatorHelper $scopeValidator, $isDebugEnabled, $sAction, $sObjectClass, $sObjectId = null) + public function IsActionAllowed($sAction, $sObjectClass, $sObjectId = null) { $sDebugTracePrefix = __CLASS__ . ' / ' . __METHOD__ . ' : Returned false for action ' . $sAction . ' on ' . $sObjectClass . '::' . $sObjectId; // Checking action type if (!in_array($sAction, array(UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_CREATE))) { - if ($isDebugEnabled) + if ($this->bDebug) { IssueLog::Info($sDebugTracePrefix . ' as the action value could not be understood (' . UR_ACTION_READ . '/' . UR_ACTION_MODIFY . '/' . UR_ACTION_CREATE . ' expected'); } @@ -83,10 +104,10 @@ class SecurityHelper // - Transforming scope action as there is only 2 values $sScopeAction = ($sAction === UR_ACTION_READ) ? UR_ACTION_READ : UR_ACTION_MODIFY; // - Retrieving the query. If user has no scope, it can't access that kind of objects - $oScopeQuery = $scopeValidator->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sObjectClass, $sScopeAction); + $oScopeQuery = $this->oScopeValidator->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sObjectClass, $sScopeAction); if ($oScopeQuery === null) { - if ($isDebugEnabled) + if ($this->bDebug) { IssueLog::Info($sDebugTracePrefix . ' as there was no scope defined for action ' . $sScopeAction . ' and profiles ' . implode('/', UserRights::ListProfiles())); } @@ -103,7 +124,7 @@ class SecurityHelper { if(static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass][$sObjectId] === false) { - if ($isDebugEnabled) + if ($this->bDebug) { IssueLog::Info($sDebugTracePrefix . ' as it was denied in the scope objects cache'); } @@ -131,7 +152,7 @@ class SecurityHelper // Updating cache static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass][$sObjectId] = false; - if ($isDebugEnabled) + if ($this->bDebug) { IssueLog::Info($sDebugTracePrefix . ' as there was no result for the following scope query : ' . $oScopeQuery->ToOQL(true)); } @@ -149,7 +170,7 @@ class SecurityHelper { // For security reasons, we don't want to give the user too many informations on why he cannot access the object. //throw new SecurityException('User not allowed to view this object', array('class' => $sObjectClass, 'id' => $sObjectId)); - if ($isDebugEnabled) + if ($this->bDebug) { IssueLog::Info($sDebugTracePrefix . ' as the user is not allowed to access this object according to the datamodel security (cf. Console settings)'); } @@ -159,16 +180,7 @@ class SecurityHelper return true; } - /** - * @param LifecycleValidatorHelper $lifecycleValidator - * @param $sStimulusCode - * @param $sObjectClass - * @param null $oInstanceSet - * - * @return bool - * @throws \Exception - */ - public static function IsStimulusAllowed(LifecycleValidatorHelper $lifecycleValidator, $sStimulusCode, $sObjectClass, $oInstanceSet = null) + public function IsStimulusAllowed($sStimulusCode, $sObjectClass, $oInstanceSet = null) { // Checking DataModel layer $aStimuliFromDatamodel = Metamodel::EnumStimuli($sObjectClass); @@ -179,7 +191,7 @@ class SecurityHelper } // Checking portal security layer - $aStimuliFromPortal = $lifecycleValidator->GetStimuliForProfiles(UserRights::ListProfiles(), $sObjectClass); + $aStimuliFromPortal = $this->oLifecycleValidator->GetStimuliForProfiles(UserRights::ListProfiles(), $sObjectClass); if(!in_array($sStimulusCode, $aStimuliFromPortal)) { return false; @@ -188,19 +200,18 @@ class SecurityHelper return true; } - /** - * Preloads scope objects cache with objects from $oQuery - * - * @param ScopeValidatorHelper $scopeValidator - * @param \DBSearch $oSearch - * @param array $aExtKeysToPreload - * - * @throws \CoreException - * @throws \CoreUnexpectedValue - * @throws \MySQLException - * @throws \OQLException - */ - public static function PreloadForCache(ScopeValidatorHelper $scopeValidator, DBSearch $oSearch, $aExtKeysToPreload = null) + /** + * Preloads scope objects cache with objects from $oQuery + * + * @param \DBSearch $oSearch + * @param array $aExtKeysToPreload + * + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \MySQLException + * @throws \OQLException + */ + public function PreloadForCache(DBSearch $oSearch, $aExtKeysToPreload = null) { $sObjectClass = $oSearch->GetClass(); $aObjectIds = array(); @@ -250,7 +261,7 @@ class SecurityHelper { // Retrieving scope query /** @var DBSearch $oScopeQuery */ - $oScopeQuery = $scopeValidator->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sObjectClass, $sScopeAction); + $oScopeQuery = $this->oScopeValidator->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sObjectClass, $sScopeAction); if($oScopeQuery !== null) { // Restricting scope if specified @@ -290,7 +301,7 @@ class SecurityHelper $oTargetSearch = new DBObjectSearch($sTargetClass); $oTargetSearch->AddCondition('id', $aTargetIds, 'IN'); - static::PreloadForCache($scopeValidator, $oTargetSearch); + static::PreloadForCache($oTargetSearch); } } }