1<?php
2// Copyright (C) 2010-2018 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 * Class ApplicationContext
22 *
23 * @copyright   Copyright (C) 2010-2018 Combodo SARL
24 * @license     http://opensource.org/licenses/AGPL-3.0
25 */
26
27require_once(APPROOT."/application/utils.inc.php");
28
29/**
30 * Interface for directing end-users to the relevant application
31 */
32interface iDBObjectURLMaker
33{
34    /**
35     * @param string $sClass
36     * @param string $iId
37     *
38     * @return string
39     */
40	public static function MakeObjectURL($sClass, $iId);
41}
42
43/**
44 * Direct end-users to the standard iTop application: UI.php
45 */
46class iTopStandardURLMaker implements iDBObjectURLMaker
47{
48    /**
49     * @param string $sClass
50     * @param string $iId
51     *
52     * @return string
53     * @throws \Exception
54     */
55	public static function MakeObjectURL($sClass, $iId)
56	{
57		$sPage = DBObject::ComputeStandardUIPage($sClass);
58		$sAbsoluteUrl = utils::GetAbsoluteUrlAppRoot();
59		$sUrl = "{$sAbsoluteUrl}pages/$sPage?operation=details&class=$sClass&id=$iId";
60		return $sUrl;
61	}
62}
63
64/**
65 * Direct end-users to the standard Portal application
66 */
67class PortalURLMaker implements iDBObjectURLMaker
68{
69    /**
70     * @param string $sClass
71     * @param string $iId
72     *
73     * @return string
74     * @throws \Exception
75     */
76	public static function MakeObjectURL($sClass, $iId)
77	{
78		$sAbsoluteUrl = utils::GetAbsoluteUrlAppRoot();
79		$sUrl = "{$sAbsoluteUrl}portal/index.php?operation=details&class=$sClass&id=$iId";
80		return $sUrl;
81	}
82}
83
84
85/**
86 * Helper class to store and manipulate the parameters that make the application's context
87 *
88 * Usage:
89 * 1) Build the application's context by constructing the object
90 *   (the object will read some of the page's parameters)
91 *
92 * 2) Add these parameters to hyperlinks or to forms using the helper, functions
93 *    GetForLink(), GetForForm() or GetAsHash()
94 */
95class ApplicationContext
96{
97    public static $m_sUrlMakerClass = null;
98    protected static $m_aPluginProperties = null;
99    protected static $aDefaultValues; // Cache shared among all instances
100
101	protected $aNames;
102	protected $aValues;
103
104    /**
105     * ApplicationContext constructor.
106     *
107     * @param bool $bReadContext
108     *
109     * @throws \Exception
110     */
111	public function __construct($bReadContext = true)
112	{
113		$this->aNames = array(
114			'org_id', 'menu'
115		);
116		if ($bReadContext)
117		{
118			$this->ReadContext();
119		}
120
121	}
122
123    /**
124     * Read the context directly in the PHP parameters (either POST or GET)
125     * return nothing
126     *
127     * @throws \Exception
128     */
129	protected function ReadContext()
130	{
131		if (!isset(self::$aDefaultValues))
132		{
133			self::$aDefaultValues = array();
134			$aContext = utils::ReadParam('c', array(), false, 'context_param');
135			foreach($this->aNames as $sName)
136			{
137				$sValue = isset($aContext[$sName]) ? $aContext[$sName] : '';
138				// TO DO: check if some of the context parameters are mandatory (or have default values)
139				if (!empty($sValue))
140				{
141					self::$aDefaultValues[$sName] = $sValue;
142				}
143				// Hmm, there must be a better (more generic) way to handle the case below:
144				// When there is only one possible (allowed) organization, the context must be
145				// fixed to this org unless there is only one organization in the system then
146				// no filter is applied
147				if ($sName == 'org_id')
148				{
149					if (MetaModel::IsValidClass('Organization'))
150					{
151						$oSearchFilter = new DBObjectSearch('Organization');
152						$oSet = new CMDBObjectSet($oSearchFilter);
153						$iCount = $oSet->CountWithLimit(2);
154						if ($iCount > 1)
155						{
156							$oSearchFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true);
157							$oSet = new CMDBObjectSet($oSearchFilter);
158							$iCount = $oSet->CountWithLimit(2);
159							if ($iCount == 1)
160							{
161								// Only one possible value for org_id, set it in the context
162								$oOrg = $oSet->Fetch();
163								self::$aDefaultValues[$sName] = $oOrg->GetKey();
164							}
165						}
166					}
167				}
168			}
169		}
170		$this->aValues = self::$aDefaultValues;
171	}
172
173    /**
174     * Returns the current value for the given parameter
175     *
176     * @param string $sParamName Name of the parameter to read
177     * @param string $defaultValue
178     *
179     * @return mixed The value for this parameter
180     */
181	public function GetCurrentValue($sParamName, $defaultValue = '')
182	{
183		if (isset($this->aValues[$sParamName]))
184		{
185			return $this->aValues[$sParamName];
186		}
187		return $defaultValue;
188	}
189
190	/**
191	 * Returns the context as string with the format name1=value1&name2=value2....
192	 * @return string The context as a string to be appended to an href property
193	 */
194	public function GetForLink()
195	{
196		$aParams = array();
197		foreach($this->aValues as $sName => $sValue)
198		{
199			$aParams[] = "c[$sName]".'='.urlencode($sValue);
200		}
201		return implode("&", $aParams);
202	}
203
204	/**
205	 * Returns the context as sequence of input tags to be inserted inside a <form> tag
206	 * @return string The context as a sequence of <input type="hidden" /> tags
207	 */
208	public function GetForForm()
209	{
210		$sContext = "";
211		foreach($this->aValues as $sName => $sValue)
212		{
213			$sContext .= "<input type=\"hidden\" name=\"c[$sName]\" value=\"".htmlentities($sValue, ENT_QUOTES, 'UTF-8')."\" />\n";
214		}
215		return $sContext;
216	}
217
218	/**
219	 * Returns the context as a hash array 'parameter_name' => value
220	 * @return array The context information
221	 */
222	public function GetAsHash()
223	{
224		$aReturn = array();
225		foreach($this->aValues as $sName => $sValue)
226		{
227			$aReturn["c[$sName]"] = $sValue;
228		}
229		return $aReturn;
230	}
231
232	/**
233	 * Returns an array of the context parameters NAMEs
234	 * @return array The list of context parameters
235	 */
236	public function GetNames()
237	{
238		return $this->aNames;
239	}
240	/**
241	 * Removes the specified parameter from the context, for example when the same parameter
242	 * is already a search parameter
243	 * @param string $sParamName Name of the parameter to remove
244	 */
245	public function Reset($sParamName)
246	{
247		if (isset($this->aValues[$sParamName]))
248		{
249			unset($this->aValues[$sParamName]);
250		}
251	}
252
253	/**
254	 * Initializes the given object with the default values provided by the context
255	 *
256	 * @param \DBObject $oObj
257	 *
258	 * @throws \Exception
259	 * @throws \CoreUnexpectedValue
260	 */
261	public function InitObjectFromContext(DBObject &$oObj)
262	{
263		$sClass = get_class($oObj);
264		foreach($this->GetNames() as $key)
265		{
266			$aCallSpec = array($sClass, 'MapContextParam');
267			if (is_callable($aCallSpec))
268			{
269				$sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter
270
271				if (MetaModel::IsValidAttCode($sClass, $sAttCode))
272				{
273					$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
274					if ($oAttDef->IsWritable())
275					{
276						$value = $this->GetCurrentValue($key, null);
277						if (!is_null($value))
278						{
279							$oObj->Set($sAttCode, $value);
280						}
281					}
282				}
283			}
284		}
285	}
286
287	/**
288	 * Set the current application url provider
289	 * @param string $sClass Class implementing iDBObjectURLMaker
290	 * @return string
291	 */
292	public static function SetUrlMakerClass($sClass = 'iTopStandardURLMaker')
293	{
294		$sPrevious = self::GetUrlMakerClass();
295
296		self::$m_sUrlMakerClass = $sClass;
297		$_SESSION['UrlMakerClass'] = $sClass;
298
299		return $sPrevious;
300	}
301
302	/**
303	 * Get the current application url provider
304	 * @return string the name of the class
305	 */
306	public static function GetUrlMakerClass()
307	{
308		if (is_null(self::$m_sUrlMakerClass))
309		{
310			if (isset($_SESSION['UrlMakerClass']))
311			{
312				self::$m_sUrlMakerClass = $_SESSION['UrlMakerClass'];
313			}
314			else
315			{
316				self::$m_sUrlMakerClass = 'iTopStandardURLMaker';
317			}
318		}
319		return self::$m_sUrlMakerClass;
320	}
321
322	/**
323	 * Get the current application url provider
324	 *
325	 * @param string $sObjClass
326	 * @param string $sObjKey
327	 * @param null $sUrlMakerClass
328	 * @param bool $bWithNavigationContext
329	 *
330	 * @return string the name of the class
331	 * @throws \Exception
332	 */
333   public static function MakeObjectUrl($sObjClass, $sObjKey, $sUrlMakerClass = null, $bWithNavigationContext = true)
334   {
335   	$oAppContext = new ApplicationContext();
336
337      if (is_null($sUrlMakerClass))
338      {
339			$sUrlMakerClass = self::GetUrlMakerClass();
340		}
341		$sUrl = call_user_func(array($sUrlMakerClass, 'MakeObjectUrl'), $sObjClass, $sObjKey);
342		if (strlen($sUrl) > 0)
343		{
344			if ($bWithNavigationContext)
345			{
346				return $sUrl."&".$oAppContext->GetForLink();
347			}
348			else
349			{
350				return $sUrl;
351			}
352		}
353		else
354		{
355			return '';
356		}
357	}
358
359	/**
360	 * Load plugin properties for the current session
361	 * @return void
362	 */
363	protected static function LoadPluginProperties()
364	{
365		if (isset($_SESSION['PluginProperties']))
366		{
367			self::$m_aPluginProperties = $_SESSION['PluginProperties'];
368		}
369		else
370		{
371			self::$m_aPluginProperties = array();
372		}
373	}
374
375	/**
376	 * Set plugin properties
377	 * @param string $sPluginClass Class implementing any plugin interface
378	 * @param string $sProperty Name of the property
379	 * @param mixed $value Value (numeric or string)
380	 * @return void
381	 */
382	public static function SetPluginProperty($sPluginClass, $sProperty, $value)
383	{
384		if (is_null(self::$m_aPluginProperties)) self::LoadPluginProperties();
385
386		self::$m_aPluginProperties[$sPluginClass][$sProperty] = $value;
387		$_SESSION['PluginProperties'][$sPluginClass][$sProperty] = $value;
388	}
389
390	/**
391	 * Get plugin properties
392	 * @param string $sPluginClass Class implementing any plugin interface
393	 * @return array of sProperty=>value pairs
394	 */
395	public static function GetPluginProperties($sPluginClass)
396	{
397		if (is_null(self::$m_aPluginProperties)) self::LoadPluginProperties();
398
399		if (array_key_exists($sPluginClass, self::$m_aPluginProperties))
400		{
401			return self::$m_aPluginProperties[$sPluginClass];
402		}
403		else
404		{
405			return array();
406		}
407	}
408
409}
410