lifecyclevalidatorhelper.class.inc.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. <?php
  2. // Copyright (C) 2010-2017 Combodo SARL
  3. //
  4. // This file is part of iTop.
  5. //
  6. // iTop is free software; you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // iTop is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with iTop. If not, see <http://www.gnu.org/licenses/>
  18. namespace Combodo\iTop\Portal\Helper;
  19. use \Exception;
  20. use \DOMNodeList;
  21. use \DOMFormatException;
  22. use \utils;
  23. use \UserRights;
  24. use \ProfilesConfig;
  25. use \MetaModel;
  26. use \DBSearch;
  27. use \DBUnionSearch;
  28. use \Combodo\iTop\DesignElement;
  29. class LifecycleValidatorHelper
  30. {
  31. const DEFAULT_GENERATED_CLASS = 'PortalLifecycleValues';
  32. protected $sCachePath;
  33. protected $sFilename;
  34. protected $sInstancePrefix;
  35. protected $sGeneratedClass;
  36. protected $aProfilesMatrix;
  37. public function __construct($sFilename, $sCachePath = null)
  38. {
  39. $this->sFilename = $sFilename;
  40. $this->sCachePath = $sCachePath;
  41. $this->sInstancePrefix = '';
  42. $this->sGeneratedClass = static::DEFAULT_GENERATED_CLASS;
  43. $this->aProfilesMatrix = array();
  44. }
  45. /**
  46. * Returns the path where to cache the compiled lifecycles file
  47. *
  48. * @return string
  49. */
  50. public function GetCachePath()
  51. {
  52. return $this->sCachePath;
  53. }
  54. /**
  55. * Returns the name of the compiled lifecycles file
  56. *
  57. * @return string
  58. */
  59. public function GetFilename()
  60. {
  61. return $this->sFilename;
  62. }
  63. /**
  64. * Returns the instance prefix used for the generated lifecycles class name
  65. *
  66. * @return string
  67. */
  68. public function GetInstancePrefix()
  69. {
  70. return $this->sInstancePrefix;
  71. }
  72. /**
  73. * Returns the name of the generated lifecycles class
  74. *
  75. * @return string
  76. */
  77. public function GetGeneratedClass()
  78. {
  79. return $this->sGeneratedClass;
  80. }
  81. /**
  82. * Sets the lifecycle validator instance prefix.
  83. *
  84. * 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.
  85. *
  86. * @param string $sInstancePrefix
  87. * @return \Combodo\iTop\Portal\Helper\LifecycleValidatorHelper
  88. */
  89. public function SetInstancePrefix($sInstancePrefix)
  90. {
  91. $sInstancePrefix = preg_replace('/[-_]/', ' ', $sInstancePrefix);
  92. $sInstancePrefix = ucwords($sInstancePrefix);
  93. $sInstancePrefix = str_replace(' ', '', $sInstancePrefix);
  94. $this->sInstancePrefix = $sInstancePrefix;
  95. $this->sGeneratedClass = $this->sInstancePrefix . static::DEFAULT_GENERATED_CLASS;
  96. return $this;
  97. }
  98. /**
  99. * Initializes the LifecycleValidator by generating and caching the lifecycles compilation in the $this->sCachePath.$this->sFilename file.
  100. *
  101. * @param DOMNodeList $oNodes
  102. * @throws DOMFormatException
  103. * @throws Exception
  104. */
  105. public function Init(DOMNodeList $oNodes)
  106. {
  107. // Checking cache path
  108. if ($this->sCachePath === null)
  109. {
  110. $this->sCachePath = utils::GetCachePath();
  111. }
  112. // Building full pathname for file
  113. $sFilePath = $this->sCachePath . $this->sFilename;
  114. // Creating file if not existing
  115. // Note: This is a temporary cache system, it should soon evolve to a cache provider (fs, apc, memcache, ...)
  116. if (!file_exists($sFilePath))
  117. {
  118. // - Build php array from xml
  119. $aProfiles = array();
  120. // This will be used to know which classes have been set, so we can set the missing ones.
  121. $aProfileClasses = array();
  122. // Iterating over the class nodes
  123. foreach ($oNodes as $oClassNode)
  124. {
  125. // Retrieving mandatory class id attribute
  126. $sClass = $oClassNode->getAttribute('id');
  127. if ($sClass === '')
  128. {
  129. throw new DOMFormatException('Class tag must have an id attribute.', null, null, $oClassNode);
  130. }
  131. // Retrieving lifecycle node of the class
  132. $oLifecycleNode = $oClassNode->GetOptionalElement('lifecycle');
  133. if($oLifecycleNode !== null)
  134. {
  135. // Iterating over scope nodes of the class
  136. $oStimuliNode = $oLifecycleNode->GetOptionalElement('stimuli');
  137. if ($oStimuliNode !== null)
  138. {
  139. foreach ($oStimuliNode->GetNodes('./stimulus') as $oStimulusNode)
  140. {
  141. // Retrieving mandatory scope id attribute
  142. $sStimulusId = $oStimulusNode->getAttribute('id');
  143. if ($sStimulusId === '')
  144. {
  145. throw new DOMFormatException('Stimulus tag must have an id attribute.', null, null, $oStimulusNode);
  146. }
  147. // Retrieving profiles for the stimulus
  148. $oProfilesNode = $oStimulusNode->GetOptionalElement('denied_profiles');
  149. $aProfilesNames = array();
  150. // If no profile is specified, we consider that it's for ALL the profiles
  151. if (($oProfilesNode === null) || ($oProfilesNode->GetNodes('./denied_profile')->length === 0))
  152. {
  153. foreach (ProfilesConfig::GetProfilesValues() as $iKey => $aValue)
  154. {
  155. $aProfilesNames[] = $aValue['name'];
  156. }
  157. }
  158. else
  159. {
  160. foreach ($oProfilesNode->GetNodes('./denied_profile') as $oProfileNode)
  161. {
  162. // Retrieving mandatory profile id attribute
  163. $sProfileId = $oProfileNode->getAttribute('id');
  164. if ($sProfileId === '')
  165. {
  166. throw new DOMFormatException('Profile tag must have an id attribute.', null, null, $oProfileNode);
  167. }
  168. $aProfilesNames[] = $sProfileId;
  169. }
  170. }
  171. //
  172. foreach ($aProfilesNames as $sProfileName)
  173. {
  174. // Stimulus profile id
  175. $iProfileId = $this->GetProfileIdFromProfileName($sProfileName);
  176. // Now that we have the queries infos, we are going to build the queries for that profile / class
  177. $sMatrixPrefix = $iProfileId . '_' . $sClass;
  178. // - Creating profile / class entry if not already present
  179. if(!array_key_exists($sMatrixPrefix, $aProfiles))
  180. {
  181. $aProfiles[$sMatrixPrefix] = array();
  182. }
  183. // - Adding stimulus if not already present
  184. if(!in_array($sStimulusId, $aProfiles[$sMatrixPrefix]))
  185. {
  186. $aProfiles[$sMatrixPrefix][] = $sStimulusId;
  187. }
  188. }
  189. }
  190. $aProfileClasses[] = $sClass;
  191. }
  192. }
  193. }
  194. // Filling the array with missing classes from MetaModel, so we can have an inheritance principle on the stimuli
  195. // For each class explicitly given in the stimuli, we check if its child classes were also in the stimuli :
  196. // If not, we add them
  197. //
  198. // Note: Classes / Stimuli not in the matrix are implicitly ALLOWED. That can happen by omitting the <lifecycle> in a <class>
  199. foreach ($aProfileClasses as $sProfileClass)
  200. {
  201. foreach (MetaModel::EnumChildClasses($sProfileClass) as $sChildClass)
  202. {
  203. // If the child class is not in the scope, we are going to try to add it
  204. if (!in_array($sChildClass, $aProfileClasses))
  205. {
  206. foreach (ProfilesConfig::GetProfilesValues() as $iKey => $aValue)
  207. {
  208. $iProfileId = $iKey;
  209. // If the current profile has scope for that class in that mode, we duplicate it
  210. if (isset($aProfiles[$iProfileId . '_' . $sProfileClass]))
  211. {
  212. $aProfiles[$iProfileId . '_' . $sChildClass] = $aProfiles[$iProfileId . '_' . $sProfileClass];
  213. }
  214. }
  215. }
  216. }
  217. }
  218. // - Build php class
  219. $sPHP = $this->BuildPHPClass($aProfiles);
  220. // - Write file on disk
  221. // - Creating dir if necessary
  222. if (!is_dir($this->sCachePath))
  223. {
  224. mkdir($this->sCachePath, 0777, true);
  225. }
  226. // -- Then creating the file
  227. $ret = file_put_contents($sFilePath, $sPHP);
  228. if ($ret === false)
  229. {
  230. $iLen = strlen($sPHP);
  231. $fFree = @disk_free_space(dirname($sFilePath));
  232. $aErr = error_get_last();
  233. throw new Exception("Failed to write '$sFilePath'. Last error: '{$aErr['message']}', content to write: $iLen bytes, available free space on disk: $fFree.");
  234. }
  235. }
  236. if (!class_exists($this->sGeneratedClass))
  237. {
  238. require_once $this->sCachePath . $this->sFilename;
  239. }
  240. }
  241. /**
  242. * Returns an array of available stimuli for the $sProfile for the class $sClass
  243. *
  244. * @param string $sProfile
  245. * @param string $sClass
  246. * @return DBSearch
  247. */
  248. public function GetStimuliForProfile($sProfile, $sClass)
  249. {
  250. return $this->GetStimuliForProfiles(array($sProfile), $sClass);
  251. }
  252. /**
  253. * Returns an array of available stimuli for the $aProfiles for the class $sClass.
  254. * Profiles are a OR condition.
  255. *
  256. * @param array $aProfiles
  257. * @param string $sClass
  258. * @return DBSearch
  259. */
  260. public function GetStimuliForProfiles($aProfiles, $sClass)
  261. {
  262. $aStimuli = array();
  263. // Preparing available stimuli
  264. foreach(MetaModel::EnumStimuli($sClass) as $sStimulusCode => $aData)
  265. {
  266. $aStimuli[$sStimulusCode] = true;
  267. }
  268. // Iterating on profiles to retrieving the different OQLs parts
  269. foreach ($aProfiles as $sProfile)
  270. {
  271. // Retrieving matrix informtions
  272. $iProfileId = $this->GetProfileIdFromProfileName($sProfile);
  273. // Retrieving profile stimuli
  274. $sLifecycleValuesClass = $this->sGeneratedClass;
  275. $aProfileMatrix = $sLifecycleValuesClass::GetProfileStimuli($iProfileId, $sClass);
  276. foreach($aProfileMatrix as $sStimulusCode)
  277. {
  278. if(array_key_exists($sStimulusCode, $aStimuli))
  279. {
  280. unset($aStimuli[$sStimulusCode]);
  281. }
  282. }
  283. }
  284. return array_keys($aStimuli);
  285. }
  286. /**
  287. * Returns the profile id from a string being either a constant or its name.
  288. *
  289. * @param string $sProfile
  290. * @return integer
  291. * @throws Exception
  292. */
  293. protected function GetProfileIdFromProfileName($sProfile)
  294. {
  295. $iProfileId = null;
  296. // We try to find the profile from its name in order to retrieve it's id
  297. // - If the regular UserRights addon is installed we check the profiles array
  298. if (class_exists('ProfilesConfig'))
  299. {
  300. if (defined($sProfile) && in_array($sProfile, ProfilesConfig::GetProfilesValues()))
  301. {
  302. $iProfileId = constant($sProfile);
  303. }
  304. else
  305. {
  306. foreach (ProfilesConfig::GetProfilesValues() as $iKey => $aValue)
  307. {
  308. if ($aValue['name'] === $sProfile)
  309. {
  310. $iProfileId = $iKey;
  311. break;
  312. }
  313. }
  314. }
  315. }
  316. // - 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
  317. else
  318. {
  319. throw new Exception('Lifecycle validator : Unknown UserRights addon, lifecycle\'s profile must be a constant');
  320. }
  321. // If profile was not found from its name or from a constant, we throw an exception
  322. if ($iProfileId === null)
  323. {
  324. throw new Exception('Lifecycle validator : Could not find "' . $sProfile . '" in the profiles list');
  325. }
  326. return $iProfileId;
  327. }
  328. /**
  329. * Returns a string containing the generated PHP class for the compiled scopes
  330. *
  331. * @param array $aProfiles
  332. * @return string
  333. */
  334. protected function BuildPHPClass($aProfiles = array())
  335. {
  336. $sProfiles = var_export($aProfiles, true);
  337. $sClassName = $this->sGeneratedClass;
  338. $sPHP = <<<EOF
  339. <?php
  340. // File generated by LifeCycleValidatorHelper
  341. //
  342. // Please do not edit manually
  343. // List of denied stimuli by profiles in the lifecycles
  344. // - used by the portal LifecycleValidatorHelper
  345. //
  346. class $sClassName
  347. {
  348. protected static \$aPROFILES = $sProfiles;
  349. /**
  350. * Returns the denied stimuli for a profile / class
  351. *
  352. * @param integer \$iProfileId
  353. * @param string \$sClass
  354. */
  355. public static function GetProfileStimuli(\$iProfileId, \$sClass)
  356. {
  357. \$aStimuli = null;
  358. \$sLifecycleKey = \$iProfileId.'_'.\$sClass;
  359. if (isset(self::\$aPROFILES[\$sLifecycleKey]))
  360. {
  361. \$aStimuli = self::\$aPROFILES[\$sLifecycleKey];
  362. }
  363. return \$aStimuli;
  364. }
  365. }
  366. EOF;
  367. return $sPHP;
  368. }
  369. }