123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567 |
- <?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 Combodo\iTop\Portal\Helper\ScopeValidatorHelper;
- use \Silex\Application;
- use \Symfony\Component\HttpFoundation\Request;
- use \UserRights;
- use \CMDBSource;
- use \IssueLog;
- use \MetaModel;
- use \AttributeDefinition;
- use \AttributeDate;
- use \AttributeDateTime;
- use \AttributeDuration;
- use \AttributeSubItem;
- use \DBSearch;
- use \DBObjectSearch;
- use \DBObjectSet;
- use \DBObject;
- use \FieldExpression;
- use \BinaryExpression;
- use \VariableExpression;
- use \SQLExpression;
- use \UnaryExpression;
- use \Dict;
- use \iPopupMenuExtension;
- use \URLButtonItem;
- use \JSButtonItem;
- use \Combodo\iTop\Portal\Helper\ApplicationHelper;
- use \Combodo\iTop\Portal\Helper\SecurityHelper;
- use \Combodo\iTop\Portal\Brick\AbstractBrick;
- use \Combodo\iTop\Portal\Brick\ManageBrick;
- class ManageBrickController extends BrickController
- {
- public function DisplayAction(Request $oRequest, Application $oApp, $sBrickId, $sGroupingTab, $sDataLoading = null)
- {
- /** @var \Combodo\iTop\Portal\Brick\ManageBrick $oBrick */
- $oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
- $aData = array();
- $aGroupingTabsValues = array();
- $aGroupingAreasValues = array();
- $aQueries = array();
- // Getting current dataloading mode (First from router parameter, then query parameter, then default brick value)
- $sDataLoading = ($sDataLoading !== null) ? $sDataLoading : ( ($oRequest->get('sDataLoading') !== null) ? $oRequest->get('sDataLoading') : $oBrick->GetDataLoading() );
- // Getting search value
- $sSearchValue = $oRequest->get('sSearchValue', null);
- // Getting area columns properties
- $aColumnsAttrs = $oBrick->GetFields();
- // Adding friendlyname attribute to the list is not already in it
- $sTitleAttrCode = 'friendlyname';
- if (($sTitleAttrCode !== null) && !in_array($sTitleAttrCode, $aColumnsAttrs))
- {
- $aColumnsAttrs = array_merge(array($sTitleAttrCode), $aColumnsAttrs);
- }
- // Starting to build query
- $oQuery = DBSearch::FromOQL($oBrick->GetOql());
- // - Adding search clause if necessary
- // Note : This is a very naive search at the moment
- if ($sSearchValue !== null)
- {
- $aSearchListItems = MetaModel::GetZListItems($oQuery->GetClass(), 'standard_search');
- $oFullBinExpr = null;
- for ($i = 0; $i < count($aSearchListItems); $i++)
- {
- $sSearchItemAttr = $aSearchListItems[$i];
- $oBinExpr = new BinaryExpression(new FieldExpression($sSearchItemAttr, $oQuery->GetClassAlias()), 'LIKE', new VariableExpression('search_value'));
- // At each iteration we build the complete expression for the search like ( (field1 LIKE %search%) OR (field2 LIKE %search%) OR (field3 LIKE %search%) ...)
- if ($i === 0)
- {
- $oFullBinExpr = $oBinExpr;
- }
- else
- {
- $oFullBinExpr = new BinaryExpression($oFullBinExpr, 'OR', $oBinExpr);
- }
- // Then on the last iteration we add the complete expression to the query
- // Note : We don't do it after the loop as there could be an empty search ZList
- if ($i === (count($aSearchListItems) - 1))
- {
- // - Adding expression to the query
- $oQuery->AddConditionExpression($oFullBinExpr);
- // - Setting expression parameters
- // Note : This could be way more simpler if we had a SetInternalParam($sParam, $value) verb
- $aQueryParams = $oQuery->GetInternalParams();
- $aQueryParams['search_value'] = '%' . $sSearchValue . '%';
- $oQuery->SetInternalParams($aQueryParams);
- }
- }
- }
- // Preparing tabs
- // - We need to retrieve distinct values for the grouping attribute
- if ($oBrick->HasGroupingTabs())
- {
- $aGroupingTabs = $oBrick->GetGroupingTabs();
- // If tabs are made of the distinct values of an attribute, we have a find them via a query
- if ($oBrick->IsGroupingTabsByDistinctValues())
- {
- $sGroupingTabAttCode = $aGroupingTabs['attribute'];
- $oDistinctQuery = DBSearch::FromOQL($oBrick->GetOql());
- // - Restricting query to scope
- $oScopeQuery = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $oDistinctQuery->GetClass(), UR_ACTION_READ);
- if ($oScopeQuery !== null)
- {
- $oDistinctQuery = $oDistinctQuery->Intersect($oScopeQuery);
- // - Allowing all data if necessary
- if ($oScopeQuery->IsAllDataAllowed())
- {
- $oDistinctQuery->AllowAllData();
- }
- }
- // - Adding field condition
- $oFieldExp = new FieldExpression($sGroupingTabAttCode, $oDistinctQuery->GetClassAlias());
- $sDistinctSql = $oDistinctQuery->MakeGroupByQuery(array(), array('grouped_by_1' => $oFieldExp), true);
- $aDistinctResults = CMDBSource::QueryToArray($sDistinctSql);
- if (!empty($aDistinctResults))
- {
- foreach ($aDistinctResults as $aDistinctResult)
- {
- $oConditionQuery = DBSearch::CloneWithAlias($oQuery, 'GTAB');
- $oExpression = new BinaryExpression(new FieldExpression($sGroupingTabAttCode, $oDistinctQuery->GetClassAlias()), '=', new UnaryExpression($aDistinctResult['grouped_by_1']));
- $oConditionQuery->AddConditionExpression($oExpression);
- $aGroupingTabsValues[$aDistinctResult['grouped_by_1']] = array(
- 'value' => $aDistinctResult['grouped_by_1'],
- 'label' => strip_tags($oFieldExp->MakeValueLabel($oDistinctQuery, $aDistinctResult['grouped_by_1'], '')),
- 'condition' => $oConditionQuery,
- 'count' => $aDistinctResult['_itop_count_'],
- );
- unset($oConditionQuery);
- }
- unset($aDistinctResults);
- }
- else
- {
- $aGroupingTabsValues['undefined'] = array(
- 'value' => 'undefined',
- 'label' => '',
- 'condition' => null,
- 'count' => null,
- );
- }
- }
- // Otherwise we create the tabs from the SQL expressions
- else
- {
- foreach ($aGroupingTabs['groups'] as $aGroup)
- {
- $oConditionQuery = $oQuery->Intersect( DBSearch::FromOQL($aGroup['condition']) );
- // - Restricting query to scope
- $oScopeQuery = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $oConditionQuery->GetClass(), UR_ACTION_READ);
- if ($oScopeQuery !== null)
- {
- $oConditionQuery = $oConditionQuery->Intersect($oScopeQuery);
- // - Allowing all data if necessary
- if ($oScopeQuery->IsAllDataAllowed())
- {
- $oConditionQuery->AllowAllData();
- }
- }
- // - Building ObjectSet
- $oConditionSet = new DBObjectSet($oConditionQuery);
- $aGroupingTabsValues[$aGroup['id']] = array(
- 'value' => $aGroup['id'],
- 'label' => Dict::S($aGroup['title']),
- 'condition' => $oConditionQuery,
- 'count' => $oConditionSet->Count(),
- );
- }
- }
- }
- // - Retrieving the current grouping tab to display and altering the query to do so
- if ($sGroupingTab === null)
- {
- if ($oBrick->HasGroupingTabs())
- {
- reset($aGroupingTabsValues);
- $sGroupingTab = key($aGroupingTabsValues);
- if ($aGroupingTabsValues[$sGroupingTab]['condition'] !== null)
- {
- $oQuery = $oQuery->Intersect($aGroupingTabsValues[$sGroupingTab]['condition']);
- }
- }
- else
- {
- // Do not group by tabs, display all in the same page
- }
- }
- else
- {
- if ($aGroupingTabsValues[$sGroupingTab]['condition'] !== null)
- {
- $oQuery = $oQuery->Intersect($aGroupingTabsValues[$sGroupingTab]['condition']);
- }
- }
- // Preparing areas
- // - We need to retrieve distinct values for the grouping attribute
- // Note : Will have to be changed when we consider grouping on something else than the finalclass
- $sParentAlias = $oQuery->GetClassAlias();
- if (true)
- {
- $sGroupingAreaAttCode = 'finalclass';
- // For root classes
- if (MetaModel::IsValidAttCode($oQuery->GetClass(), $sGroupingAreaAttCode))
- {
- $oDistinctQuery = DBSearch::FromOQL($oBrick->GetOql());
- // Checking if there is a scope to apply
- $oDistinctScopeQuery = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $oQuery->GetClass(), UR_ACTION_READ);
- if ($oDistinctScopeQuery != null)
- {
- $oDistinctQuery = $oDistinctQuery->Intersect($oDistinctScopeQuery);
- // - Allowing all data if necessary
- if ($oDistinctScopeQuery->IsAllDataAllowed())
- {
- $oDistinctQuery->AllowAllData();
- }
- }
- // Adding grouping conditions
- $oFieldExp = new FieldExpression($sGroupingAreaAttCode, $sParentAlias);
- $sDistinctSql = $oDistinctQuery->MakeGroupByQuery(array(), array('grouped_by_1' => $oFieldExp), true);
- $aDistinctResults = CMDBSource::QueryToArray($sDistinctSql);
- foreach ($aDistinctResults as $aDistinctResult)
- {
- $oConditionQuery = DBSearch::CloneWithAlias($oQuery, 'GARE');
- $oExpression = new BinaryExpression(new FieldExpression($sGroupingAreaAttCode, 'GARE'), '=', new UnaryExpression($aDistinctResult['grouped_by_1']));
- $oConditionQuery->AddConditionExpression($oExpression);
- $aGroupingAreasValues[$aDistinctResult['grouped_by_1']] = array(
- 'value' => $aDistinctResult['grouped_by_1'],
- 'label' => MetaModel::GetName($aDistinctResult['grouped_by_1']), // Caution : This works only because we froze the grouping areas on the finalclass attribute.
- 'condition' => $oConditionQuery,
- 'count' => $aDistinctResult['_itop_count_']
- );
- unset($oConditionQuery);
- }
- unset($aDistinctResults);
- }
- // For leaf classes
- else
- {
- $aGroupingAreasValues[$oQuery->GetClass()] = array(
- 'value' => $oQuery->GetClass(),
- 'label' => MetaModel::GetName($oQuery->GetClass()), // Caution : This works only because we froze the grouping areas on the finalclass attribute.
- 'condition' => null,
- 'count' => 0
- );
- }
- }
- // - Retrieving the grouping areas to display
- $sGroupingArea = $oRequest->get('sGroupingArea');
- // - If specified or lazy loading, we trunc the $aGroupingAreasValues to keep only this one
- if ($sGroupingArea !== null)
- {
- $aGroupingAreasValues = array($sGroupingArea => $aGroupingAreasValues[$sGroupingArea]);
- }
- // - Preapring the queries
- foreach ($aGroupingAreasValues as $sKey => $aGroupingAreasValue)
- {
- $oAreaQuery = DBSearch::CloneWithAlias($oQuery, $sParentAlias);
- if ($aGroupingAreasValue['condition'] !== null)
- {
- //$oAreaQuery->AddConditionExpression($aGroupingAreasValue['condition']);
- $oAreaQuery = $oAreaQuery->Intersect($aGroupingAreasValue['condition']);
- }
- // Restricting query to allowed scope on each classes
- // Note: Will need to moved the scope restriction on queries elsewhere when we consider grouping on something else than finalclass
- // Note: We now get view scope instead of edit scope as we allowed users to view/edit objects in the brick regarding their rights
- $oScopeQuery = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $aGroupingAreasValue['value'], UR_ACTION_READ);
- if ($oScopeQuery !== null)
- {
- $oAreaQuery = $oAreaQuery->Intersect($oScopeQuery);
- // - Allowing all data if necessary
- if ($oScopeQuery->IsAllDataAllowed())
- {
- $oAreaQuery->AllowAllData();
- }
- }
- else
- {
- $oAreaQuery = null;
- }
- $aQueries[$sKey] = $oAreaQuery;
- }
- // Testing appropriate data loading mode if we are in auto
- // - For all (html) tables, this doesn't care for the grouping ares (finalclass)
- 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);
- $oCountSet->OptimizeColumnLoad(array());
- $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);
- }
- // Preparing data sets
- $aSets = array();
- /** @var DBSearch $oQuery */
- foreach ($aQueries as $sKey => $oQuery)
- {
- // Checking if we have a valid query
- if ($oQuery !== null)
- {
- // Setting query pagination if needed
- if ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_LAZY)
- {
- // Retrieving parameters
- $iPageNumber = (int) $oRequest->get('iPageNumber', 1);
- $iListLength = (int) $oRequest->get('iListLength', ManageBrick::DEFAULT_LIST_LENGTH);
- // Getting total records number
- $oCountSet = new DBObjectSet($oQuery);
- $oCountSet->OptimizeColumnLoad(array($oQuery->GetClassAlias() => $aColumnsAttrs));
- $aData['recordsTotal'] = $oCountSet->Count();
- $aData['recordsFiltered'] = $oCountSet->Count();
- unset($oCountSet);
- $oSet = new DBObjectSet($oQuery);
- $oSet->SetLimit($iListLength, $iListLength * ($iPageNumber - 1));
- }
- else
- {
- $oSet = new DBObjectSet($oQuery);
- }
- // Adding always_in_tables attributes
- $aColumnsToLoad = array($oQuery->GetClassAlias() => $aColumnsAttrs);
- foreach($oQuery->GetSelectedClasses() as $sAlias => $sClass)
- {
- /** @var AttributeDefinition $oAttDef */
- foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
- {
- if($oAttDef->AlwaysLoadInTables())
- {
- $aColumnsToLoad[$sAlias][] = $sAttCode;
- }
- }
- }
- $oSet->OptimizeColumnLoad($aColumnsToLoad);
- $oSet->SetOrderByClasses();
- SecurityHelper::PreloadForCache($oApp, $oSet->GetFilter(), $aColumnsToLoad[$oQuery->GetClassAlias()] /* preloading only extkeys from the main class */);
- $aSets[$sKey] = $oSet;
- }
- }
- // Retrieving and preparing data for rendering
- $aGroupingAreasData = array();
- $bHasObjectListItemExtension = false;
- foreach ($aSets as $sKey => $oSet)
- {
- // Set properties
- $sCurrentClass = $sKey;
-
- // Defining which attribute will open the edition form)
- $sMainActionAttrCode = $aColumnsAttrs[0];
- // Loading columns definition
- $aColumnsDefinition = array();
- foreach ($aColumnsAttrs as $sColumnAttr)
- {
- $oAttDef = MetaModel::GetAttributeDef($sKey, $sColumnAttr);
- $aColumnsDefinition[$sColumnAttr] = array(
- 'title' => $oAttDef->GetLabel(),
- 'type' => ($oAttDef instanceof AttributeDateTime) ? 'moment-'.$oAttDef->GetFormat()->ToMomentJS() : 'html', // Special sorting for Date & Time
- );
- }
- // Getting items
- $aItems = array();
- // ... For each item
- /** @var DBObject $oCurrentRow */
- while ($oCurrentRow = $oSet->Fetch())
- {
- // ... Retrieving item's attributes values
- $aItemAttrs = array();
- foreach ($aColumnsAttrs as $sItemAttr)
- {
- $aActions = array();
- // Set the edit action to the main (first) attribute only
- //if ($sItemAttr === $sTitleAttrCode)
- if ($sItemAttr === $sMainActionAttrCode)
- {
- // Checking if we can edit the object
- if (($oBrick->GetOpeningMode() === ManageBrick::ENUM_ACTION_EDIT) && SecurityHelper::IsActionAllowed($oApp, UR_ACTION_MODIFY, $sCurrentClass, $oCurrentRow->GetKey()))
- {
- $sActionType = ManageBrick::ENUM_ACTION_EDIT;
- }
- // - Otherwise, check if view is allowed
- elseif (SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sCurrentClass, $oCurrentRow->GetKey()))
- {
- $sActionType = ManageBrick::ENUM_ACTION_VIEW;
- }
- else
- {
- $sActionType = null;
- }
- // - Then set allowed action
- if ($sActionType !== null)
- {
- $aActions[] = array(
- 'type' => $sActionType,
- 'class' => $sCurrentClass,
- 'id' => $oCurrentRow->GetKey(),
- 'opening_target' => $oBrick->GetOpeningTarget(),
- );
- }
- }
- /** @var AttributeDefinition $oAttDef */
- $oAttDef = MetaModel::GetAttributeDef($sCurrentClass, $sItemAttr);
- if ($oAttDef->IsExternalKey())
- {
- $sValue = $oCurrentRow->Get($sItemAttr . '_friendlyname');
- // Adding a view action on the external keys
- if ($oCurrentRow->Get($sItemAttr) !== $oAttDef->GetNullValue())
- {
- // Checking if we can view the object
- if ((SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $oAttDef->GetTargetClass(), $oCurrentRow->Get($sItemAttr))))
- {
- $aActions[] = array(
- 'type' => ManageBrick::ENUM_ACTION_VIEW,
- 'class' => $oAttDef->GetTargetClass(),
- 'id' => $oCurrentRow->Get($sItemAttr),
- 'opening_target' => $oBrick->GetOpeningTarget(),
- );
- }
- }
- }
- elseif ($oAttDef instanceof AttributeSubItem || $oAttDef instanceof AttributeDuration)
- {
- $sValue = $oAttDef->GetAsHTML($oCurrentRow->Get($sItemAttr));
- }
- else
- {
- $sValue = $oAttDef->GetValueLabel($oCurrentRow->Get($sItemAttr));
- }
- unset($oAttDef);
- $aItemAttrs[$sItemAttr] = array(
- 'att_code' => $sItemAttr,
- 'value' => $sValue,
- 'actions' => $aActions
- );
- }
- // ... Checking menu extensions
- $aItemButtons = array();
- foreach (MetaModel::EnumPlugins('iPopupMenuExtension') as $oExtensionInstance)
- {
- foreach($oExtensionInstance->EnumItems(iPopupMenuExtension::PORTAL_OBJLISTITEM_ACTIONS, array('portal_id' => $oApp['combodo.portal.instance.id'], 'object' => $oCurrentRow)) as $oMenuItem)
- {
- if (is_object($oMenuItem))
- {
- if($oMenuItem instanceof JSButtonItem)
- {
- $aItemButtons[] = $oMenuItem->GetMenuItem() + array('js_files' => $oMenuItem->GetLinkedScripts(), 'type' => 'button');
- }
- elseif($oMenuItem instanceof URLButtonItem)
- {
- $aItemButtons[] = $oMenuItem->GetMenuItem() + array('type' => 'link');
- }
- }
- }
- }
-
- // ... And item's properties
- $aItems[] = array(
- 'id' => $oCurrentRow->GetKey(),
- 'class' => $sCurrentClass,
- 'attributes' => $aItemAttrs,
- 'highlight_class' => $oCurrentRow->GetHilightClass(),
- 'actions' => $aItemButtons,
- );
- if(!empty($aItemButtons))
- {
- $bHasObjectListItemExtension = true;
- }
- }
- // Adding an extra column for object list item extensions
- if($bHasObjectListItemExtension === true)
- {
- $aColumnsDefinition['_ui_extensions'] = array(
- 'title' => Dict::S('Brick:Portal:Manage:Table:ItemActions'),
- 'type' => 'html',
- );
- }
- $aGroupingAreasData[$sKey] = array(
- 'sId' => $sKey,
- 'sTitle' => $aGroupingAreasValues[$sKey]['label'],
- 'aItems' => $aItems,
- 'iItemsCount' => $oSet->Count(),
- 'aColumnsDefinition' => $aColumnsDefinition
- );
- }
- // Preparing response
- if ($oRequest->isXmlHttpRequest())
- {
- $aData = $aData + array(
- 'data' => $aGroupingAreasData[$sGroupingArea]['aItems']
- );
- $oResponse = $oApp->json($aData);
- }
- else
- {
- $aData = $aData + array(
- 'oBrick' => $oBrick,
- 'sBrickId' => $sBrickId,
- 'sGroupingTab' => $sGroupingTab,
- 'aGroupingTabsValues' => $aGroupingTabsValues,
- 'sDataLoading' => $sDataLoading,
- 'aGroupingAreasData' => $aGroupingAreasData,
- 'sDateFormat' => AttributeDate::GetFormat()->ToMomentJS(),
- 'sDateTimeFormat' => AttributeDateTime::GetFormat()->ToMomentJS(),
- 'sSearchValue' => $sSearchValue,
- );
- $oResponse = $oApp['twig']->render($oBrick->GetPageTemplatePath(), $aData);
- }
- return $oResponse;
- }
- }
|