mirror of
https://github.com/Combodo/iTop.git
synced 2026-05-19 15:22:17 +02:00
N°2060 [WIP] Initialisation of the portal application:
- Refactor SecurityHelper into SF service (DI) - Make BrowseBrick work (again!) - Extract methods from BrowseBrickController to a dedicated service (BrowseBrickHelper)
This commit is contained in:
@@ -1,821 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
// Copyright (C) 2010-2017 Combodo SARL
|
|
||||||
//
|
|
||||||
// This file is part of iTop.
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
//
|
|
||||||
// iTop is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
|
|
||||||
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 <guillaume.lajarige@combodo.com>
|
|
||||||
* @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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
1726
datamodels/2.x/itop-portal-base/portal/composer.lock
generated
Normal file
1726
datamodels/2.x/itop-portal-base/portal/composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -46,8 +46,9 @@ services:
|
|||||||
# fetching services directly from the container via $container->get() won't work.
|
# fetching services directly from the container via $container->get() won't work.
|
||||||
# The best practice is to be explicit about your dependencies anyway.
|
# The best practice is to be explicit about your dependencies anyway.
|
||||||
bind:
|
bind:
|
||||||
|
$bDebug: '%kernel.debug%'
|
||||||
$sPortalCachePath: !php/const PORTAL_CACHE_PATH
|
$sPortalCachePath: !php/const PORTAL_CACHE_PATH
|
||||||
$sPortalId: !php/const PORTAL_ID
|
$sPortalId: !php/const PORTAL_ID
|
||||||
$sCombodoPortalInstanceAbsoluteUrl: !php/const COMBODO_PORTAL_INSTANCE_ABSOLUTE_URL
|
$sCombodoPortalInstanceAbsoluteUrl: !php/const COMBODO_PORTAL_INSTANCE_ABSOLUTE_URL
|
||||||
|
|
||||||
# Makes classes in src/ available to be used as services
|
# Makes classes in src/ available to be used as services
|
||||||
@@ -125,3 +126,6 @@ services:
|
|||||||
url_generator:
|
url_generator:
|
||||||
alias: router
|
alias: router
|
||||||
public: true
|
public: true
|
||||||
|
browse_brick:
|
||||||
|
alias: Combodo\iTop\Portal\Helper\BrowseBrickHelper
|
||||||
|
public: true
|
||||||
@@ -0,0 +1,412 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2013-2019 Combodo SARL
|
||||||
|
*
|
||||||
|
* This file is part of iTop.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* iTop is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Combodo\iTop\Portal\Controller;
|
||||||
|
|
||||||
|
use Combodo\iTop\Portal\Helper\BrowseBrickHelper;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||||
|
use MetaModel;
|
||||||
|
use DBSearch;
|
||||||
|
use DBObjectSet;
|
||||||
|
use BinaryExpression;
|
||||||
|
use FieldExpression;
|
||||||
|
use VariableExpression;
|
||||||
|
use Combodo\iTop\Portal\Brick\AbstractBrick;
|
||||||
|
use Combodo\iTop\Portal\Brick\BrowseBrick;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class BrowseBrickController
|
||||||
|
*
|
||||||
|
* @package Combodo\iTop\Portal\Controller
|
||||||
|
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,482 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2013-2019 Combodo SARL
|
||||||
|
*
|
||||||
|
* This file is part of iTop.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* iTop is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
namespace Combodo\iTop\Portal\Helper;
|
||||||
|
|
||||||
|
|
||||||
|
use AttributeImage;
|
||||||
|
use Combodo\iTop\Portal\Brick\BrowseBrick;
|
||||||
|
use DBSearch;
|
||||||
|
use Dict;
|
||||||
|
use MetaModel;
|
||||||
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
|
use UserRights;
|
||||||
|
use Combodo\iTop\Portal\Routing\UrlGenerator;
|
||||||
|
|
||||||
|
class BrowseBrickHelper
|
||||||
|
{
|
||||||
|
const LEVEL_SEPARATOR = '-';
|
||||||
|
const OPTIONAL_ATTRIBUTES = array('tooltip_att', 'description_att', 'image_att');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Combodo\iTop\Portal\Helper\SecurityHelper
|
||||||
|
*/
|
||||||
|
private $oSecurityHelper;
|
||||||
|
/**
|
||||||
|
* @var \Combodo\iTop\Portal\Helper\ScopeValidatorHelper
|
||||||
|
*/
|
||||||
|
private $oScopeValidator;
|
||||||
|
/**
|
||||||
|
* @var \Combodo\iTop\Portal\Routing\UrlGenerator
|
||||||
|
*/
|
||||||
|
private $oUrlGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BrowseBrickHelper constructor.
|
||||||
|
*
|
||||||
|
* @param \Combodo\iTop\Portal\Helper\SecurityHelper $oSecurityHelper
|
||||||
|
* @param \Combodo\iTop\Portal\Helper\ScopeValidatorHelper $oScopeValidator
|
||||||
|
* @param \Symfony\Component\Routing\Generator\UrlGeneratorInterface $oUrlGenerator
|
||||||
|
*/
|
||||||
|
public function __construct(SecurityHelper $oSecurityHelper, ScopeValidatorHelper $oScopeValidator, UrlGeneratorInterface $oUrlGenerator)
|
||||||
|
{
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,35 +44,56 @@ class SecurityHelper
|
|||||||
UR_ACTION_MODIFY => array(),
|
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)
|
* Returns true if the current user is allowed to do the $sAction on an $sObjectClass object (with optionnal $sObjectId id)
|
||||||
* Checks are:
|
* Checks are:
|
||||||
* - Has a scope query for the $sObjectClass / $sAction
|
* - Has a scope query for the $sObjectClass / $sAction
|
||||||
* - Optionally, if $sObjectId provided: Is object within scope for $sObjectClass / $sObjectId / $sAction
|
* - Optionally, if $sObjectId provided: Is object within scope for $sObjectClass / $sObjectId / $sAction
|
||||||
* - Is allowed by datamodel for $sObjectClass / $sAction
|
* - Is allowed by datamodel for $sObjectClass / $sAction
|
||||||
*
|
*
|
||||||
* @param ScopeValidatorHelper $scopeValidator
|
* @param \Silex\Application $oApp
|
||||||
* @param boolean $isDebugEnabled
|
* @param string $sAction Must be in UR_ACTION_READ|UR_ACTION_MODIFY|UR_ACTION_CREATE
|
||||||
* @param string $sAction Must be in UR_ACTION_READ|UR_ACTION_MODIFY|UR_ACTION_CREATE
|
* @param string $sObjectClass
|
||||||
* @param string $sObjectClass
|
* @param string $sObjectId
|
||||||
* @param string $sObjectId
|
|
||||||
*
|
*
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*
|
*
|
||||||
* @throws \CoreException
|
* @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;
|
$sDebugTracePrefix = __CLASS__ . ' / ' . __METHOD__ . ' : Returned false for action ' . $sAction . ' on ' . $sObjectClass . '::' . $sObjectId;
|
||||||
|
|
||||||
// Checking action type
|
// Checking action type
|
||||||
if (!in_array($sAction, array(UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_CREATE)))
|
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');
|
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
|
// - Transforming scope action as there is only 2 values
|
||||||
$sScopeAction = ($sAction === UR_ACTION_READ) ? UR_ACTION_READ : UR_ACTION_MODIFY;
|
$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
|
// - 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 ($oScopeQuery === null)
|
||||||
{
|
{
|
||||||
if ($isDebugEnabled)
|
if ($this->bDebug)
|
||||||
{
|
{
|
||||||
IssueLog::Info($sDebugTracePrefix . ' as there was no scope defined for action ' . $sScopeAction . ' and profiles ' . implode('/', UserRights::ListProfiles()));
|
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(static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass][$sObjectId] === false)
|
||||||
{
|
{
|
||||||
if ($isDebugEnabled)
|
if ($this->bDebug)
|
||||||
{
|
{
|
||||||
IssueLog::Info($sDebugTracePrefix . ' as it was denied in the scope objects cache');
|
IssueLog::Info($sDebugTracePrefix . ' as it was denied in the scope objects cache');
|
||||||
}
|
}
|
||||||
@@ -131,7 +152,7 @@ class SecurityHelper
|
|||||||
// Updating cache
|
// Updating cache
|
||||||
static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass][$sObjectId] = false;
|
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));
|
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.
|
// 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));
|
//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)');
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function IsStimulusAllowed($sStimulusCode, $sObjectClass, $oInstanceSet = null)
|
||||||
* @param LifecycleValidatorHelper $lifecycleValidator
|
|
||||||
* @param $sStimulusCode
|
|
||||||
* @param $sObjectClass
|
|
||||||
* @param null $oInstanceSet
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
public static function IsStimulusAllowed(LifecycleValidatorHelper $lifecycleValidator, $sStimulusCode, $sObjectClass, $oInstanceSet = null)
|
|
||||||
{
|
{
|
||||||
// Checking DataModel layer
|
// Checking DataModel layer
|
||||||
$aStimuliFromDatamodel = Metamodel::EnumStimuli($sObjectClass);
|
$aStimuliFromDatamodel = Metamodel::EnumStimuli($sObjectClass);
|
||||||
@@ -179,7 +191,7 @@ class SecurityHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Checking portal security layer
|
// Checking portal security layer
|
||||||
$aStimuliFromPortal = $lifecycleValidator->GetStimuliForProfiles(UserRights::ListProfiles(), $sObjectClass);
|
$aStimuliFromPortal = $this->oLifecycleValidator->GetStimuliForProfiles(UserRights::ListProfiles(), $sObjectClass);
|
||||||
if(!in_array($sStimulusCode, $aStimuliFromPortal))
|
if(!in_array($sStimulusCode, $aStimuliFromPortal))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -188,19 +200,18 @@ class SecurityHelper
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preloads scope objects cache with objects from $oQuery
|
* Preloads scope objects cache with objects from $oQuery
|
||||||
*
|
*
|
||||||
* @param ScopeValidatorHelper $scopeValidator
|
* @param \DBSearch $oSearch
|
||||||
* @param \DBSearch $oSearch
|
* @param array $aExtKeysToPreload
|
||||||
* @param array $aExtKeysToPreload
|
*
|
||||||
*
|
* @throws \CoreException
|
||||||
* @throws \CoreException
|
* @throws \CoreUnexpectedValue
|
||||||
* @throws \CoreUnexpectedValue
|
* @throws \MySQLException
|
||||||
* @throws \MySQLException
|
* @throws \OQLException
|
||||||
* @throws \OQLException
|
*/
|
||||||
*/
|
public function PreloadForCache(DBSearch $oSearch, $aExtKeysToPreload = null)
|
||||||
public static function PreloadForCache(ScopeValidatorHelper $scopeValidator, DBSearch $oSearch, $aExtKeysToPreload = null)
|
|
||||||
{
|
{
|
||||||
$sObjectClass = $oSearch->GetClass();
|
$sObjectClass = $oSearch->GetClass();
|
||||||
$aObjectIds = array();
|
$aObjectIds = array();
|
||||||
@@ -250,7 +261,7 @@ class SecurityHelper
|
|||||||
{
|
{
|
||||||
// Retrieving scope query
|
// Retrieving scope query
|
||||||
/** @var DBSearch $oScopeQuery */
|
/** @var DBSearch $oScopeQuery */
|
||||||
$oScopeQuery = $scopeValidator->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sObjectClass, $sScopeAction);
|
$oScopeQuery = $this->oScopeValidator->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sObjectClass, $sScopeAction);
|
||||||
if($oScopeQuery !== null)
|
if($oScopeQuery !== null)
|
||||||
{
|
{
|
||||||
// Restricting scope if specified
|
// Restricting scope if specified
|
||||||
@@ -290,7 +301,7 @@ class SecurityHelper
|
|||||||
$oTargetSearch = new DBObjectSearch($sTargetClass);
|
$oTargetSearch = new DBObjectSearch($sTargetClass);
|
||||||
$oTargetSearch->AddCondition('id', $aTargetIds, 'IN');
|
$oTargetSearch->AddCondition('id', $aTargetIds, 'IN');
|
||||||
|
|
||||||
static::PreloadForCache($scopeValidator, $oTargetSearch);
|
static::PreloadForCache($oTargetSearch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user