Pārlūkot izejas kodu

Portal: Performance optimization on ManageBrick

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@4718 a333f486-631f-4898-b8df-5754b55c2be0
glajarige 8 gadi atpakaļ
vecāks
revīzija
40687a2ecf

+ 17 - 4
datamodels/2.x/itop-portal-base/portal/src/controllers/managebrickcontroller.class.inc.php

@@ -19,6 +19,7 @@
 
 namespace Combodo\iTop\Portal\Controller;
 
+use Combodo\iTop\Portal\Helper\ScopeValidatorHelper;
 use \Silex\Application;
 use \Symfony\Component\HttpFoundation\Request;
 use \UserRights;
@@ -267,8 +268,8 @@ class ManageBrickController extends BrickController
 			}
 
 			// 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
+			// 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)
 			{
@@ -284,7 +285,7 @@ class ManageBrickController extends BrickController
 				$oAreaQuery = null;
 			}
 
-			$aQueries[$sKey] = $oAreaQuery;
+            $aQueries[$sKey] = $oAreaQuery;
 		}
 
 		// Testing appropriate data loading mode if we are in auto
@@ -346,11 +347,12 @@ class ManageBrickController extends BrickController
 
 				$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 datas for rendering
+		// Retrieving and preparing data for rendering
 		$aGroupingAreasData = array();
 		foreach ($aSets as $sKey => $oSet)
 		{
@@ -373,6 +375,7 @@ class ManageBrickController extends BrickController
 
 			// Getting items
 			$aItems = array();
+			$aItemsIds = array();
 			// ... For each item
             /** @var DBObject $oCurrentRow */
 			while ($oCurrentRow = $oSet->Fetch())
@@ -457,8 +460,18 @@ class ManageBrickController extends BrickController
 					'attributes' => $aItemAttrs,
 					'highlight_class' => $oCurrentRow->GetHilightClass()
 				);
+				$aItemsIds = $oCurrentRow->GetKey();
 			}
 
+			// Now that we retrieved items, we check which can be edited, which can be view and which cannot be opened
+            //
+            // Note: Now that we do checks here and not through the SecurityHelper while fetching objects, we might bypass datamodel security regarding the object class!
+//            $oScopeQuery = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sCurrentClass, UR_ACTION_MODIFY);
+//			if($oSearchEditableItems !== null)
+//            {
+//                $oSearchEditableItems->A
+//            }
+
 			$aGroupingAreasData[$sKey] = array(
 				'sId' => $sKey,
 				'sTitle' => $aGroupingAreasValues[$sKey]['label'],

+ 107 - 0
datamodels/2.x/itop-portal-base/portal/src/helpers/securityhelper.class.inc.php

@@ -26,6 +26,8 @@ use \UserRights;
 use \Dict;
 use \IssueLog;
 use \MetaModel;
+use \DBSearch;
+use \DBObjectSearch;
 use \DBObjectSet;
 use \FieldExpression;
 use \VariableExpression;
@@ -48,6 +50,10 @@ class SecurityHelper
 
 	/**
 	 * Returns true if the current user is allowed to do the $sAction on an $sObjectClass object (with optionnal $sObjectId id)
+     * Checks are:
+     * - Has a scope query for the $sObjectClass / $sAction
+     * - Optionally, if $sObjectId provided: Is object within scope for $sObjectClass / $sObjectId / $sAction
+     * - Is allowed by datamodel for $sObjectClass / $sAction
 	 *
 	 * @param Silex\Application $oApp
 	 * @param string $sAction Must be in UR_ACTION_READ|UR_ACTION_MODIFY|UR_ACTION_CREATE
@@ -155,4 +161,105 @@ class SecurityHelper
 		$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sObjectClass, $sStimulusCode, $oInstanceSet) : UR_ALLOWED_NO;
 	}
 
+    /**
+     * Preloads scope objects cache with objects from $oQuery
+     *
+     * @param Application $oApp
+     * @param DBSearch $oSet
+     * @param array $aExtKeysToPreload
+     */
+	public static function PreloadForCache(Application $oApp, DBSearch $oSearch, $aExtKeysToPreload = null)
+    {
+        $sObjectClass = $oSearch->GetClass();
+        $aObjectIds = array();
+        $aExtKeysIds = array();
+        $aColumnsToLoad = array();
+
+        if($aExtKeysToPreload !== null)
+        {
+            foreach($aExtKeysToPreload as $sAttCode)
+            {
+                /** @var \AttributeDefinition $oAttDef */
+                $oAttDef = MetaModel::GetAttributeDef($sObjectClass, $sAttCode);
+                if($oAttDef->IsExternalKey())
+                {
+                    $aExtKeysIds[$oAttDef->GetTargetClass()] = array();
+                    $aColumnsToLoad[] = $sAttCode;
+                }
+            }
+        }
+
+        // Retrieving IDs of all objects
+        // Note: We have to clone $oSet otherwise the source object will be modified
+        $oSet = new DBObjectSet($oSearch);
+        $oSet->OptimizeColumnLoad(array($oSearch->GetClassAlias() => $aColumnsToLoad));
+        while($oCurrentRow = $oSet->Fetch())
+        {
+            // Note: By presetting value to false, it is quicker to find which objects where not returned by the scope query later
+            $aObjectIds[$oCurrentRow->GetKey()] = false;
+
+            // Preparing ExtKeys to preload
+            foreach($aColumnsToLoad as $sAttCode)
+            {
+                $iExtKey = $oCurrentRow->Get($sAttCode);
+                if($iExtKey > 0)
+                {
+                    /** @var \AttributeExternalKey $oAttDef */
+                    $oAttDef = MetaModel::GetAttributeDef($sObjectClass, $sAttCode);
+                    if(!in_array($iExtKey, $aExtKeysIds[$oAttDef->GetTargetClass()]))
+                    {
+                        $aExtKeysIds[$oAttDef->GetTargetClass()][] = $iExtKey;
+                    }
+                }
+            }
+        }
+
+        foreach(array(UR_ACTION_READ, UR_ACTION_MODIFY) as $sScopeAction)
+        {
+            // Retrieving scope query
+            /** @var DBSearch $oScopeQuery */
+            $oScopeQuery = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sObjectClass, $sScopeAction);
+            if($oScopeQuery !== null)
+            {
+                // Restricting scope if specified
+                if(!empty($aObjectIds))
+                {
+                    $oScopeQuery->AddCondition('id', array_keys($aObjectIds), 'IN');
+                }
+
+                // Preparing object set
+                $oScopeSet = new DBObjectSet($oScopeQuery);
+                $oScopeSet->OptimizeColumnLoad(array());
+
+                // Checking objects status
+                $aScopeObjectIds = $aObjectIds;
+                while($oCurrentRow = $oScopeSet->Fetch())
+                {
+                    $aScopeObjectIds[$oCurrentRow->GetKey()] = true;
+                }
+
+                // Updating cache
+                if(!isset(static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass]))
+                {
+                    static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass] = $aScopeObjectIds;
+                }
+                else
+                {
+                    static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass] = array_merge_recursive(static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass], $aScopeObjectIds);
+                }
+            }
+        }
+
+        // Preloading ExtKeys
+        foreach($aExtKeysIds as $sTargetClass => $aTargetIds)
+        {
+            if(!empty($aTargetIds))
+            {
+                $oTargetSearch = new DBObjectSearch($sTargetClass);
+                $oTargetSearch->AddCondition('id', $aTargetIds, 'IN');
+
+                static::PreloadForCache($oApp, $oTargetSearch);
+            }
+        }
+    }
 }