123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- <?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\Helper;
- use \Exception;
- use \DOMNodeList;
- use \DOMFormatException;
- use \utils;
- use \UserRights;
- use \ProfilesConfig;
- use \MetaModel;
- use \DBSearch;
- use \DBUnionSearch;
- use \Combodo\iTop\DesignElement;
- class LifecycleValidatorHelper
- {
- const DEFAULT_GENERATED_CLASS = 'PortalLifecycleValues';
- protected $sCachePath;
- protected $sFilename;
- protected $sInstancePrefix;
- protected $sGeneratedClass;
- protected $aProfilesMatrix;
- public function __construct($sFilename, $sCachePath = null)
- {
- $this->sFilename = $sFilename;
- $this->sCachePath = $sCachePath;
- $this->sInstancePrefix = '';
- $this->sGeneratedClass = static::DEFAULT_GENERATED_CLASS;
- $this->aProfilesMatrix = array();
- }
- /**
- * Returns the path where to cache the compiled lifecycles file
- *
- * @return string
- */
- public function GetCachePath()
- {
- return $this->sCachePath;
- }
- /**
- * Returns the name of the compiled lifecycles file
- *
- * @return string
- */
- public function GetFilename()
- {
- return $this->sFilename;
- }
- /**
- * Returns the instance prefix used for the generated lifecycles class name
- *
- * @return string
- */
- public function GetInstancePrefix()
- {
- return $this->sInstancePrefix;
- }
- /**
- * Returns the name of the generated lifecycles class
- *
- * @return string
- */
- public function GetGeneratedClass()
- {
- return $this->sGeneratedClass;
- }
- /**
- * Sets the lifecycle validator instance prefix.
- *
- * This is used to create a unique lifecycle values class in the cache directory (/data/cache-<ENV>) as there can be several instance of the portal.
- *
- * @param string $sInstancePrefix
- * @return \Combodo\iTop\Portal\Helper\LifecycleValidatorHelper
- */
- public function SetInstancePrefix($sInstancePrefix)
- {
- $sInstancePrefix = preg_replace('/[-_]/', ' ', $sInstancePrefix);
- $sInstancePrefix = ucwords($sInstancePrefix);
- $sInstancePrefix = str_replace(' ', '', $sInstancePrefix);
- $this->sInstancePrefix = $sInstancePrefix;
- $this->sGeneratedClass = $this->sInstancePrefix . static::DEFAULT_GENERATED_CLASS;
- return $this;
- }
- /**
- * Initializes the LifecycleValidator by generating and caching the lifecycles compilation in the $this->sCachePath.$this->sFilename file.
- *
- * @param DOMNodeList $oNodes
- * @throws DOMFormatException
- * @throws Exception
- */
- public function Init(DOMNodeList $oNodes)
- {
- // Checking cache path
- if ($this->sCachePath === null)
- {
- $this->sCachePath = utils::GetCachePath();
- }
- // Building full pathname for file
- $sFilePath = $this->sCachePath . $this->sFilename;
- // Creating file if not existing
- // Note: This is a temporary cache system, it should soon evolve to a cache provider (fs, apc, memcache, ...)
- if (!file_exists($sFilePath))
- {
- // - Build php array from xml
- $aProfiles = array();
- // This will be used to know which classes have been set, so we can set the missing ones.
- $aProfileClasses = array();
- // Iterating over the class nodes
- foreach ($oNodes as $oClassNode)
- {
- // Retrieving mandatory class id attribute
- $sClass = $oClassNode->getAttribute('id');
- if ($sClass === '')
- {
- throw new DOMFormatException('Class tag must have an id attribute.', null, null, $oClassNode);
- }
- // Retrieving lifecycle node of the class
- $oLifecycleNode = $oClassNode->GetOptionalElement('lifecycle');
- if($oLifecycleNode !== null)
- {
- // Iterating over scope nodes of the class
- $oStimuliNode = $oLifecycleNode->GetOptionalElement('stimuli');
- if ($oStimuliNode !== null)
- {
- foreach ($oStimuliNode->GetNodes('./stimulus') as $oStimulusNode)
- {
- // Retrieving mandatory scope id attribute
- $sStimulusId = $oStimulusNode->getAttribute('id');
- if ($sStimulusId === '')
- {
- throw new DOMFormatException('Stimulus tag must have an id attribute.', null, null, $oStimulusNode);
- }
- // Retrieving profiles for the stimulus
- $oProfilesNode = $oStimulusNode->GetOptionalElement('denied_profiles');
- $aProfilesNames = array();
- // If no profile is specified, we consider that it's for ALL the profiles
- if (($oProfilesNode === null) || ($oProfilesNode->GetNodes('./denied_profile')->length === 0))
- {
- foreach (ProfilesConfig::GetProfilesValues() as $iKey => $aValue)
- {
- $aProfilesNames[] = $aValue['name'];
- }
- }
- else
- {
- foreach ($oProfilesNode->GetNodes('./denied_profile') as $oProfileNode)
- {
- // Retrieving mandatory profile id attribute
- $sProfileId = $oProfileNode->getAttribute('id');
- if ($sProfileId === '')
- {
- throw new DOMFormatException('Profile tag must have an id attribute.', null, null, $oProfileNode);
- }
- $aProfilesNames[] = $sProfileId;
- }
- }
- //
- foreach ($aProfilesNames as $sProfileName)
- {
- // Stimulus profile id
- $iProfileId = $this->GetProfileIdFromProfileName($sProfileName);
- // Now that we have the queries infos, we are going to build the queries for that profile / class
- $sMatrixPrefix = $iProfileId . '_' . $sClass;
- // - Creating profile / class entry if not already present
- if(!array_key_exists($sMatrixPrefix, $aProfiles))
- {
- $aProfiles[$sMatrixPrefix] = array();
- }
- // - Adding stimulus if not already present
- if(!in_array($sStimulusId, $aProfiles[$sMatrixPrefix]))
- {
- $aProfiles[$sMatrixPrefix][] = $sStimulusId;
- }
- }
- }
- $aProfileClasses[] = $sClass;
- }
- }
- }
- // Filling the array with missing classes from MetaModel, so we can have an inheritance principle on the stimuli
- // For each class explicitly given in the stimuli, we check if its child classes were also in the stimuli :
- // If not, we add them
- //
- // Note: Classes / Stimuli not in the matrix are implicitly ALLOWED. That can happen by omitting the <lifecycle> in a <class>
- foreach ($aProfileClasses as $sProfileClass)
- {
- foreach (MetaModel::EnumChildClasses($sProfileClass) as $sChildClass)
- {
- // If the child class is not in the scope, we are going to try to add it
- if (!in_array($sChildClass, $aProfileClasses))
- {
- foreach (ProfilesConfig::GetProfilesValues() as $iKey => $aValue)
- {
- $iProfileId = $iKey;
- // If the current profile has scope for that class in that mode, we duplicate it
- if (isset($aProfiles[$iProfileId . '_' . $sProfileClass]))
- {
- $aProfiles[$iProfileId . '_' . $sChildClass] = $aProfiles[$iProfileId . '_' . $sProfileClass];
- }
- }
- }
- }
- }
- // - Build php class
- $sPHP = $this->BuildPHPClass($aProfiles);
- // - Write file on disk
- // - Creating dir if necessary
- if (!is_dir($this->sCachePath))
- {
- mkdir($this->sCachePath, 0777, true);
- }
- // -- Then creating the file
- $ret = file_put_contents($sFilePath, $sPHP);
- if ($ret === false)
- {
- $iLen = strlen($sPHP);
- $fFree = @disk_free_space(dirname($sFilePath));
- $aErr = error_get_last();
- throw new Exception("Failed to write '$sFilePath'. Last error: '{$aErr['message']}', content to write: $iLen bytes, available free space on disk: $fFree.");
- }
- }
- if (!class_exists($this->sGeneratedClass))
- {
- require_once $this->sCachePath . $this->sFilename;
- }
- }
- /**
- * Returns an array of available stimuli for the $sProfile for the class $sClass
- *
- * @param string $sProfile
- * @param string $sClass
- * @return DBSearch
- */
- public function GetStimuliForProfile($sProfile, $sClass)
- {
- return $this->GetStimuliForProfiles(array($sProfile), $sClass);
- }
- /**
- * Returns an array of available stimuli for the $aProfiles for the class $sClass.
- * Profiles are a OR condition.
- *
- * @param array $aProfiles
- * @param string $sClass
- * @return DBSearch
- */
- public function GetStimuliForProfiles($aProfiles, $sClass)
- {
- $aStimuli = array();
- // Preparing available stimuli
- foreach(MetaModel::EnumStimuli($sClass) as $sStimulusCode => $aData)
- {
- $aStimuli[$sStimulusCode] = true;
- }
- // Iterating on profiles to retrieving the different OQLs parts
- foreach ($aProfiles as $sProfile)
- {
- // Retrieving matrix informtions
- $iProfileId = $this->GetProfileIdFromProfileName($sProfile);
- // Retrieving profile stimuli
- $sLifecycleValuesClass = $this->sGeneratedClass;
- $aProfileMatrix = $sLifecycleValuesClass::GetProfileStimuli($iProfileId, $sClass);
- foreach($aProfileMatrix as $sStimulusCode)
- {
- if(array_key_exists($sStimulusCode, $aStimuli))
- {
- unset($aStimuli[$sStimulusCode]);
- }
- }
- }
- return array_keys($aStimuli);
- }
- /**
- * Returns the profile id from a string being either a constant or its name.
- *
- * @param string $sProfile
- * @return integer
- * @throws Exception
- */
- protected function GetProfileIdFromProfileName($sProfile)
- {
- $iProfileId = null;
- // We try to find the profile from its name in order to retrieve it's id
- // - If the regular UserRights addon is installed we check the profiles array
- if (class_exists('ProfilesConfig'))
- {
- if (defined($sProfile) && in_array($sProfile, ProfilesConfig::GetProfilesValues()))
- {
- $iProfileId = constant($sProfile);
- }
- else
- {
- foreach (ProfilesConfig::GetProfilesValues() as $iKey => $aValue)
- {
- if ($aValue['name'] === $sProfile)
- {
- $iProfileId = $iKey;
- break;
- }
- }
- }
- }
- // - Else, we can't find the id from the name as we don't know the used UserRights addon. It has to be a constant
- else
- {
- throw new Exception('Lifecycle validator : Unknown UserRights addon, lifecycle\'s profile must be a constant');
- }
- // If profile was not found from its name or from a constant, we throw an exception
- if ($iProfileId === null)
- {
- throw new Exception('Lifecycle validator : Could not find "' . $sProfile . '" in the profiles list');
- }
- return $iProfileId;
- }
- /**
- * Returns a string containing the generated PHP class for the compiled scopes
- *
- * @param array $aProfiles
- * @return string
- */
- protected function BuildPHPClass($aProfiles = array())
- {
- $sProfiles = var_export($aProfiles, true);
- $sClassName = $this->sGeneratedClass;
- $sPHP = <<<EOF
- <?php
- // File generated by LifeCycleValidatorHelper
- //
- // Please do not edit manually
- // List of denied stimuli by profiles in the lifecycles
- // - used by the portal LifecycleValidatorHelper
- //
- class $sClassName
- {
- protected static \$aPROFILES = $sProfiles;
- /**
- * Returns the denied stimuli for a profile / class
- *
- * @param integer \$iProfileId
- * @param string \$sClass
- */
- public static function GetProfileStimuli(\$iProfileId, \$sClass)
- {
- \$aStimuli = null;
- \$sLifecycleKey = \$iProfileId.'_'.\$sClass;
- if (isset(self::\$aPROFILES[\$sLifecycleKey]))
- {
- \$aStimuli = self::\$aPROFILES[\$sLifecycleKey];
- }
- return \$aStimuli;
- }
- }
- EOF;
- return $sPHP;
- }
- }
|