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
19
20/**
21 * Value set definitions (from a fixed list or from a query, etc.)
22 *
23 * @copyright   Copyright (C) 2010-2017 Combodo SARL
24 * @license     http://opensource.org/licenses/AGPL-3.0
25 */
26
27require_once('MyHelpers.class.inc.php');
28
29/**
30 * ValueSetDefinition
31 * value sets API and implementations
32 *
33 * @package     iTopORM
34 */
35abstract class ValueSetDefinition
36{
37	protected $m_bIsLoaded = false;
38	protected $m_aValues = array();
39
40
41	// Displayable description that could be computed out of the std usage context
42	public function GetValuesDescription()
43	{
44		$aValues = $this->GetValues(array(), '');
45		$aDisplayedValues = array();
46		foreach($aValues as $key => $value)
47		{
48			$aDisplayedValues[] = "$key => $value";
49		}
50		$sAllowedValues = implode(', ', $aDisplayedValues);
51		return $sAllowedValues;
52	}
53
54
55	public function GetValues($aArgs, $sContains = '', $sOperation = 'contains')
56	{
57		if (!$this->m_bIsLoaded)
58		{
59			$this->LoadValues($aArgs);
60			$this->m_bIsLoaded = true;
61		}
62		if (strlen($sContains) == 0)
63		{
64			// No filtering
65			$aRet = $this->m_aValues;
66		}
67		else
68		{
69			// Filter on results containing the needle <sContain>
70			$aRet = array();
71			foreach ($this->m_aValues as $sKey=>$sValue)
72			{
73				if (stripos($sValue, $sContains) !== false)
74				{
75					$aRet[$sKey] = $sValue;
76				}
77			}
78		}
79		// Sort on the display value
80		asort($aRet);
81		return $aRet;
82	}
83
84	abstract protected function LoadValues($aArgs);
85}
86
87
88/**
89 * Set of existing values for an attribute, given a search filter
90 *
91 * @package     iTopORM
92 */
93class ValueSetObjects extends ValueSetDefinition
94{
95	protected $m_sContains;
96	protected $m_sOperation;
97	protected $m_sFilterExpr; // in OQL
98	protected $m_sValueAttCode;
99	protected $m_aOrderBy;
100	protected $m_aExtraConditions;
101	private $m_bAllowAllData;
102	private $m_aModifierProperties;
103	private $m_bSort;
104	private $m_iLimit;
105
106
107	/**
108	 * @param hash $aOrderBy Array of '[<classalias>.]attcode' => bAscending
109	 */
110	public function __construct($sFilterExp, $sValueAttCode = '', $aOrderBy = array(), $bAllowAllData = false, $aModifierProperties = array())
111	{
112		$this->m_sContains = '';
113		$this->m_sOperation = '';
114		$this->m_sFilterExpr = $sFilterExp;
115		$this->m_sValueAttCode = $sValueAttCode;
116		$this->m_aOrderBy = $aOrderBy;
117		$this->m_bAllowAllData = $bAllowAllData;
118		$this->m_aModifierProperties = $aModifierProperties;
119		$this->m_aExtraConditions = array();
120		$this->m_bSort = true;
121		$this->m_iLimit = 0;
122	}
123
124	public function SetModifierProperty($sPluginClass, $sProperty, $value)
125	{
126		$this->m_aModifierProperties[$sPluginClass][$sProperty] = $value;
127	}
128
129	public function AddCondition(DBSearch $oFilter)
130	{
131		$this->m_aExtraConditions[] = $oFilter;
132	}
133
134	public function ToObjectSet($aArgs = array(), $sContains = '', $iAdditionalValue = null)
135	{
136		if ($this->m_bAllowAllData)
137		{
138			$oFilter = DBObjectSearch::FromOQL_AllData($this->m_sFilterExpr);
139		}
140		else
141		{
142			$oFilter = DBObjectSearch::FromOQL($this->m_sFilterExpr);
143		}
144		foreach($this->m_aExtraConditions as $oExtraFilter)
145		{
146			$oFilter = $oFilter->Intersect($oExtraFilter);
147		}
148		foreach($this->m_aModifierProperties as $sPluginClass => $aProperties)
149		{
150			foreach ($aProperties as $sProperty => $value)
151			{
152				$oFilter->SetModifierProperty($sPluginClass, $sProperty, $value);
153			}
154		}
155		if ($iAdditionalValue > 0)
156		{
157			$oSearchAdditionalValue = new DBObjectSearch($oFilter->GetClass());
158			$oSearchAdditionalValue->AddConditionExpression( new BinaryExpression(
159			    new FieldExpression('id', $oSearchAdditionalValue->GetClassAlias()),
160                '=',
161                new VariableExpression('current_extkey_id'))
162            );
163			$oSearchAdditionalValue->AllowAllData();
164			$oSearchAdditionalValue->SetArchiveMode(true);
165			$oSearchAdditionalValue->SetInternalParams( array('current_extkey_id' => $iAdditionalValue) );
166
167			$oFilter = new DBUnionSearch(array($oFilter, $oSearchAdditionalValue));
168		}
169
170		return new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs);
171	}
172
173    /**
174     * @param        $aArgs
175     * @param string $sContains
176     * @param string $sOperation for the values @see self::LoadValues()
177     *
178     * @return array
179     * @throws CoreException
180     * @throws OQLException
181     */
182    public function GetValues($aArgs, $sContains = '', $sOperation = 'contains')
183	{
184		if (!$this->m_bIsLoaded || ($sContains != $this->m_sContains) || ($sOperation != $this->m_sOperation))
185		{
186			$this->LoadValues($aArgs, $sContains, $sOperation);
187			$this->m_bIsLoaded = true;
188		}
189		// The results are already filtered and sorted (on friendly name)
190		$aRet = $this->m_aValues;
191		return $aRet;
192	}
193
194	/**
195	 * @param $aArgs
196	 * @param string $sContains
197	 * @param string $sOperation 'contains' or 'equals_start_with'
198	 *
199	 * @return bool
200	 * @throws \CoreException
201	 * @throws \OQLException
202	 */
203	protected function LoadValues($aArgs, $sContains = '', $sOperation = 'contains')
204	{
205		$this->m_sContains = $sContains;
206		$this->m_sOperation = $sOperation;
207
208		$this->m_aValues = array();
209
210		if ($this->m_bAllowAllData)
211		{
212			$oFilter = DBObjectSearch::FromOQL_AllData($this->m_sFilterExpr);
213		}
214		else
215		{
216			$oFilter = DBObjectSearch::FromOQL($this->m_sFilterExpr);
217		}
218		if (!$oFilter) return false;
219		foreach($this->m_aExtraConditions as $oExtraFilter)
220		{
221			$oFilter = $oFilter->Intersect($oExtraFilter);
222		}
223		foreach($this->m_aModifierProperties as $sPluginClass => $aProperties)
224		{
225			foreach ($aProperties as $sProperty => $value)
226			{
227				$oFilter->SetModifierProperty($sPluginClass, $sProperty, $value);
228			}
229		}
230
231		$oExpression = DBObjectSearch::GetPolymorphicExpression($oFilter->GetClass(), 'friendlyname');
232		$aFields = $oExpression->ListRequiredFields();
233		$sClass = $oFilter->GetClass();
234		foreach($aFields as $sField)
235		{
236			$aFieldItems = explode('.', $sField);
237			if ($aFieldItems[0] != $sClass)
238			{
239				$sOperation = 'contains';
240				break;
241			}
242		}
243
244		switch ($sOperation)
245		{
246			case 'equals':
247				$aAttributes = MetaModel::GetFriendlyNameAttributeCodeList($sClass);
248				$sClassAlias = $oFilter->GetClassAlias();
249				$aFilters = array();
250				$oValueExpr = new ScalarExpression($sContains);
251				foreach($aAttributes as $sAttribute)
252				{
253					$oNewFilter = $oFilter->DeepClone();
254					$oNameExpr = new FieldExpression($sAttribute, $sClassAlias);
255					$oCondition = new BinaryExpression($oNameExpr, '=', $oValueExpr);
256					$oNewFilter->AddConditionExpression($oCondition);
257					$aFilters[] = $oNewFilter;
258				}
259                // Unions are much faster than OR conditions
260				$oFilter = new DBUnionSearch($aFilters);
261				break;
262            case 'start_with':
263                $aAttributes = MetaModel::GetFriendlyNameAttributeCodeList($sClass);
264                $sClassAlias = $oFilter->GetClassAlias();
265                $aFilters = array();
266                $oValueExpr = new ScalarExpression($sContains.'%');
267                foreach($aAttributes as $sAttribute)
268                {
269                    $oNewFilter = $oFilter->DeepClone();
270                    $oNameExpr = new FieldExpression($sAttribute, $sClassAlias);
271                    $oCondition = new BinaryExpression($oNameExpr, 'LIKE', $oValueExpr);
272                    $oNewFilter->AddConditionExpression($oCondition);
273                    $aFilters[] = $oNewFilter;
274                }
275                // Unions are much faster than OR conditions
276                $oFilter = new DBUnionSearch($aFilters);
277                break;
278
279			default:
280				$oValueExpr = new ScalarExpression('%'.$sContains.'%');
281				$oNameExpr = new FieldExpression('friendlyname', $oFilter->GetClassAlias());
282				$oNewCondition = new BinaryExpression($oNameExpr, 'LIKE', $oValueExpr);
283				$oFilter->AddConditionExpression($oNewCondition);
284				break;
285		}
286
287		$oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs, null, $this->m_iLimit, 0, $this->m_bSort);
288		if (empty($this->m_sValueAttCode))
289		{
290			$aAttToLoad = array($oFilter->GetClassAlias() => array('friendlyname'));
291		}
292		else
293		{
294			$aAttToLoad = array($oFilter->GetClassAlias() => array($this->m_sValueAttCode));
295		}
296		$oObjects->OptimizeColumnLoad($aAttToLoad);
297		while ($oObject = $oObjects->Fetch())
298		{
299			if (empty($this->m_sValueAttCode))
300			{
301				$this->m_aValues[$oObject->GetKey()] = $oObject->GetName();
302			}
303			else
304			{
305				$this->m_aValues[$oObject->GetKey()] = $oObject->Get($this->m_sValueAttCode);
306			}
307		}
308		return true;
309	}
310
311	public function GetValuesDescription()
312	{
313		return 'Filter: '.$this->m_sFilterExpr;
314	}
315
316	public function GetFilterExpression()
317	{
318		return $this->m_sFilterExpr;
319	}
320
321	/**
322	 * @param $iLimit
323	 */
324	public function SetLimit($iLimit)
325	{
326		$this->m_iLimit = $iLimit;
327	}
328
329	/**
330	 * @param $bSort
331	 */
332	public function SetSort($bSort)
333	{
334		$this->m_bSort = $bSort;
335	}
336}
337
338
339/**
340 * Fixed set values (could be hardcoded in the business model)
341 *
342 * @package     iTopORM
343 */
344class ValueSetEnum extends ValueSetDefinition
345{
346	protected $m_values;
347
348	public function __construct($Values)
349	{
350		$this->m_values = $Values;
351	}
352
353	// Helper to export the datat model
354	public function GetValueList()
355	{
356		$this->LoadValues($aArgs = array());
357		return $this->m_aValues;
358	}
359
360	protected function LoadValues($aArgs)
361	{
362		if (is_array($this->m_values))
363		{
364			$aValues = $this->m_values;
365		}
366		elseif (is_string($this->m_values) && strlen($this->m_values) > 0)
367		{
368			$aValues = array();
369			foreach (explode(",", $this->m_values) as $sVal)
370			{
371				$sVal = trim($sVal);
372				$sKey = $sVal;
373				$aValues[$sKey] = $sVal;
374			}
375		}
376		else
377		{
378			$aValues = array();
379		}
380		$this->m_aValues = $aValues;
381		return true;
382	}
383}
384
385/**
386 * Fixed set values, defined as a range: 0..59 (with an optional increment)
387 *
388 * @package     iTopORM
389 */
390class ValueSetRange extends ValueSetDefinition
391{
392	protected $m_iStart;
393	protected $m_iEnd;
394
395	public function __construct($iStart, $iEnd, $iStep = 1)
396	{
397		$this->m_iStart = $iStart;
398		$this->m_iEnd = $iEnd;
399		$this->m_iStep = $iStep;
400	}
401
402	protected function LoadValues($aArgs)
403	{
404		$iValue = $this->m_iStart;
405		for($iValue = $this->m_iStart; $iValue <= $this->m_iEnd; $iValue += $this->m_iStep)
406		{
407			$this->m_aValues[$iValue] = $iValue;
408		}
409		return true;
410	}
411}
412
413
414/**
415 * Data model classes
416 *
417 * @package     iTopORM
418 */
419class ValueSetEnumClasses extends ValueSetEnum
420{
421	protected $m_sCategories;
422
423	public function __construct($sCategories = '', $sAdditionalValues = '')
424	{
425		$this->m_sCategories = $sCategories;
426		parent::__construct($sAdditionalValues);
427	}
428
429	protected function LoadValues($aArgs)
430	{
431		// Call the parent to parse the additional values...
432		parent::LoadValues($aArgs);
433
434		// Translate the labels of the additional values
435		foreach($this->m_aValues as $sClass => $void)
436		{
437			if (MetaModel::IsValidClass($sClass))
438			{
439				$this->m_aValues[$sClass] = MetaModel::GetName($sClass);
440			}
441			else
442			{
443				unset($this->m_aValues[$sClass]);
444			}
445		}
446
447		// Then, add the classes from the category definition
448		foreach (MetaModel::GetClasses($this->m_sCategories) as $sClass)
449		{
450			if (MetaModel::IsValidClass($sClass))
451			{
452				$this->m_aValues[$sClass] = MetaModel::GetName($sClass);
453			}
454			else
455			{
456				unset($this->m_aValues[$sClass]);
457			}
458		}
459
460		return true;
461	}
462}
463