1<?php
2/**
3 * EGroupware - eTemplate request object storing the data in the session
4 *
5 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
6 * @package api
7 * @subpackage etemplate
8 * @link http://www.egroupware.org
9 * @author Ralf Becker <RalfBecker@outdoor-training.de>
10 * @copyright (c) 2007-16 by Ralf Becker <RalfBecker@outdoor-training.de>
11 * @version $Id$
12 */
13
14namespace EGroupware\Api\Etemplate\Request;
15
16use EGroupware\Api\Etemplate;
17use EGroupware\Api;
18
19/**
20 * Class to represent the persitent information stored on the server for each eTemplate request
21 *
22 * The information is stored in the users session, which causes the session to constantly grow.
23 * We implement here some garbadge collection to remove old requests.
24 *
25 * The request object should be instancated only via the factory method Api\Etemplate\Request::read($id=null)
26 *
27 * $request = Api\Etemplate\Request::read();
28 *
29 * // add request data
30 *
31 * $id = $request->id();
32 *
33 * b) open or modify an existing request:
34 *
35 * if (!($request = Api\Etemplate\Request::read($id)))
36 * {
37 * 		// request not found
38 * }
39 *
40 * Ajax requests can use this object to open the original request by using the id, they have to transmitt back,
41 * and register further variables, modify the registered ones or delete them AND then update the id, if it changed:
42 *
43 *	if (($new_id = $request->id()) != $id)
44 *	{
45 *		$response->addAssign('etemplate_exec_id','value',$new_id);
46 *	}
47 *
48 * For an example look in link_widget::ajax_search()
49 */
50class Session extends Etemplate\Request
51{
52	/**
53	 * request id
54	 *
55	 * @var string
56	 */
57	protected $id;
58
59	/**
60	 * Private constructor to force the instancation of this class only via it's static factory method read
61	 *
62	 * @param string|null $id =null
63	 */
64	private function __construct($id=null)
65	{
66		if (!$id) $id = self::request_id();
67
68		$this->id = $id;
69
70		// hack to quiten IDE Warning for not calling parent::__construct, which we can not!
71		if (false) parent::__construct();
72	}
73
74	/**
75	 * return the id of this request
76	 *
77	 * @return string
78	 */
79	public function &id()
80	{
81		//error_log(__METHOD__."() id=$this->id");
82		return $this->id;
83	}
84
85	/**
86	 * Factory method to get a new request object or the one for an existing request
87	 *
88	 * @param string $id =null
89	 * @return etemplate_request|boolean the object or false if $id is not found
90	 */
91	static function read($id=null)
92	{
93		$request = new Session($id);
94
95		if (!is_null($id))
96		{
97			if (!($data = Api\Cache::getSession('etemplate', $id)))
98			{
99				return false;	// request not found
100			}
101			$request->data = $data;
102		}
103		//error_log(__METHOD__."(id=$id");
104		return $request;
105	}
106
107	/**
108	 * saves content,readonlys,template-keys, ... via eGW's appsession function
109	 *
110	 * As a user may open several windows with the same content/template wie generate a location-id from microtime
111	 * which is used as location for request to descriminate between the different windows. This location-id
112	 * is then saved as a hidden-var in the form. The above mentions session-id has nothing to do / is different
113	 * from the session-id which is constant for all windows opened in one session.
114	 */
115	function __destruct()
116	{
117		if ($this->remove_if_not_modified && !$this->data_modified)
118		{
119			//error_log(__METHOD__."() destroying $this->id");
120			Api\Cache::unsetSession('etemplate', $this->id);
121		}
122		elseif (!$this->destroyed && $this->data_modified)
123		{
124			$this->cleanup();
125
126			Api\Cache::setSession('etemplate', $this->id, $this->data);
127		}
128		if (!$this->garbage_collection_done)
129		{
130			$this->_php4_request_garbage_collection();
131		}
132	}
133
134	/**
135	 * a little bit of garbage collection for php4 sessions (their size is limited by memory_limit)
136	 *
137	 * With constant eTemplate use it can grow quite big and lead to unusable sessions (php terminates
138	 * before any output with "Allowed memory size of ... exhausted").
139	 * We delete now sessions once used after 10min and sessions never or multiple used after 60min.
140	 */
141	protected function _php4_request_garbage_collection()
142	{
143		// now we are on php4 sessions and do a bit of garbage collection
144		$appsessions =& $_SESSION[Api\Session::EGW_APPSESSION_VAR]['etemplate'];
145		$session_used =& $appsessions['session_used'];
146
147		if ($this->id)
148		{
149			//echo "session_used[$id_used]='".$session_used[$id_used]."'<br/>\n";
150			++$session_used[$this->id];	// count the number of times a session got used
151		}
152		$this->garbage_collection_done = true;
153
154		if (count($appsessions) < 20) return;	// we dont need to care
155
156		$now = (int) (100 * microtime(true));	// gives precision of 1/100 sec
157
158		foreach(array_keys($appsessions) as $id)
159		{
160			list(,$time) = explode(':',$id);
161
162			if (!$time) continue;	// other data, no session
163
164			//echo ++$n.') '.$id.': '.(($now-$time)/100.0)."secs old, used=".$session_used[$id].", size=".strlen($appsessions[$id])."<br>\n";
165
166			if ($session_used[$id] == 1 && $time < $now - 10*6000 || // session used and older then 10min
167				$time < $now - 30*6000)	// session not used and older then 30min
168			{
169				//echo "<p>boetemplate::php4_session_garbage_collection('$id_used'): unsetting session '$id' (now=$now)</p>\n";
170				unset($appsessions[$id]);
171				unset($session_used[$id]);
172			}
173		}
174	}
175}