1<?php
2
3use Elgg\Config;
4use Elgg\Database;
5use Elgg\Http\DatabaseSessionHandler;
6use Symfony\Component\HttpFoundation\Session\Session;
7use Symfony\Component\HttpFoundation\Session\SessionInterface;
8use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
9use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
10
11/**
12 * Elgg Session Management
13 *
14 * Reserved keys: last_forward_from, msg, sticky_forms, user, guid, id, code, name, username
15 *
16 * @see elgg_get_session()
17 */
18class ElggSession {
19
20	/**
21	 * @var SessionInterface
22	 */
23	protected $storage;
24
25	/**
26	 * @var \ElggUser|null
27	 */
28	protected $logged_in_user;
29
30	/**
31	 * @var bool
32	 */
33	protected $ignore_access = false;
34
35	/**
36	 * @var bool
37	 */
38	protected $show_disabled_entities = false;
39
40	/**
41	 * Constructor
42	 *
43	 * @param SessionInterface $storage The underlying Session implementation
44	 */
45	public function __construct(SessionInterface $storage) {
46		$this->storage = $storage;
47	}
48
49	/**
50	 * Start the session
51	 *
52	 * @return boolean
53	 * @throws RuntimeException If session fails to start.
54	 * @since 1.9
55	 */
56	public function start() {
57
58		if ($this->storage->getId()) {
59			return true;
60		}
61
62		$result = $this->storage->start();
63		$this->generateSessionToken();
64		return $result;
65	}
66
67	/**
68	 * Migrates the session to a new session id while maintaining session attributes
69	 *
70	 * @param boolean $destroy Whether to delete the session or let gc handle clean up
71	 * @return boolean
72	 * @since 1.9
73	 */
74	public function migrate($destroy = false) {
75		return $this->storage->migrate($destroy);
76	}
77
78	/**
79	 * Invalidates the session
80	 *
81	 * Deletes session data and session persistence. Starts a new session.
82	 *
83	 * @return boolean
84	 * @since 1.9
85	 */
86	public function invalidate() {
87		$this->storage->clear();
88		$this->logged_in_user = null;
89		$result = $this->migrate(true);
90		$this->generateSessionToken();
91		_elgg_services()->sessionCache->clear();
92		return $result;
93	}
94
95	/**
96	 * Save the session data and closes the session
97	 *
98	 * @return void
99	 * @since 3.0
100	 */
101	public function save() {
102		$this->storage->save();
103	}
104
105	/**
106	 * Has the session been started
107	 *
108	 * @return boolean
109	 * @since 1.9
110	 */
111	public function isStarted() {
112		return $this->storage->isStarted();
113	}
114
115	/**
116	 * Get the session ID
117	 *
118	 * @return string
119	 * @since 1.9
120	 */
121	public function getID() {
122		return $this->storage->getId();
123	}
124
125	/**
126	 * Set the session ID
127	 *
128	 * @param string $id Session ID
129	 * @return void
130	 * @since 1.9
131	 */
132	public function setId($id) {
133		$this->storage->setId($id);
134	}
135
136	/**
137	 * Get the session name
138	 *
139	 * @return string
140	 * @since 1.9
141	 */
142	public function getName() {
143		return $this->storage->getName();
144	}
145
146	/**
147	 * Set the session name
148	 *
149	 * @param string $name Session name
150	 * @return void
151	 * @since 1.9
152	 */
153	public function setName($name) {
154		$this->storage->setName($name);
155	}
156
157	/**
158	 * Get an attribute of the session
159	 *
160	 * @param string $name    Name of the attribute to get
161	 * @param mixed  $default Value to return if attribute is not set (default is null)
162	 * @return mixed
163	 */
164	public function get($name, $default = null) {
165		return $this->storage->get($name, $default);
166	}
167
168	/**
169	 * Set an attribute
170	 *
171	 * @param string $name  Name of the attribute to set
172	 * @param mixed  $value Value to be set
173	 * @return void
174	 */
175	public function set($name, $value) {
176		$this->storage->set($name, $value);
177	}
178
179	/**
180	 * Remove an attribute
181	 *
182	 * @param string $name The name of the attribute to remove
183	 * @return mixed The removed attribute
184	 * @since 1.9
185	 */
186	public function remove($name) {
187		return $this->storage->remove($name);
188	}
189
190	/**
191	 * Has the attribute been defined
192	 *
193	 * @param string $name Name of the attribute
194	 * @return bool
195	 * @since 1.9
196	 */
197	public function has($name) {
198		return $this->storage->has($name);
199	}
200
201	/**
202	 * Sets the logged in user
203	 *
204	 * @param \ElggUser $user The user who is logged in
205	 * @return void
206	 * @since 1.9
207	 */
208	public function setLoggedInUser(\ElggUser $user) {
209		$current_user = $this->getLoggedInUser();
210		if ($current_user != $user) {
211			$this->set('guid', $user->guid);
212			$this->logged_in_user = $user;
213			_elgg_services()->sessionCache->clear();
214			_elgg_services()->translator->setCurrentLanguage($user->language);
215		}
216	}
217
218	/**
219	 * Gets the logged in user
220	 *
221	 * @return \ElggUser|null
222	 * @since 1.9
223	 */
224	public function getLoggedInUser() {
225		return $this->logged_in_user;
226	}
227
228	/**
229	 * Return the current logged in user by guid.
230	 *
231	 * @see elgg_get_logged_in_user_entity()
232	 * @return int
233	 */
234	public function getLoggedInUserGuid() {
235		$user = $this->getLoggedInUser();
236		return $user ? $user->guid : 0;
237	}
238
239	/**
240	 * Returns whether or not the viewer is currently logged in and an admin user.
241	 *
242	 * @return bool
243	 */
244	public function isAdminLoggedIn() {
245		$user = $this->getLoggedInUser();
246
247		return $user && $user->isAdmin();
248	}
249
250	/**
251	 * Returns whether or not the user is currently logged in
252	 *
253	 * @return bool
254	 */
255	public function isLoggedIn() {
256		return (bool) $this->getLoggedInUser();
257	}
258
259	/**
260	 * Remove the logged in user
261	 *
262	 * @return void
263	 * @since 1.9
264	 */
265	public function removeLoggedInUser() {
266		$this->logged_in_user = null;
267		$this->remove('guid');
268		_elgg_services()->sessionCache->clear();
269	}
270
271	/**
272	 * Get current ignore access setting.
273	 *
274	 * @return bool
275	 */
276	public function getIgnoreAccess() {
277		return $this->ignore_access;
278	}
279
280	/**
281	 * Set ignore access.
282	 *
283	 * @param bool $ignore Ignore access
284	 *
285	 * @return bool Previous setting
286	 */
287	public function setIgnoreAccess($ignore = true) {
288		$prev = $this->ignore_access;
289		$this->ignore_access = $ignore;
290
291		return $prev;
292	}
293
294	/**
295	 * Are disabled entities shown?
296	 *
297	 * @return bool
298	 */
299	public function getDisabledEntityVisibility() {
300		global $ENTITY_SHOW_HIDDEN_OVERRIDE;
301		if (isset($ENTITY_SHOW_HIDDEN_OVERRIDE)) {
302			return $ENTITY_SHOW_HIDDEN_OVERRIDE;
303		}
304
305		return $this->show_disabled_entities;
306	}
307
308	/**
309	 * Include disabled entities in queries
310	 *
311	 * @param bool $show Visibility status
312	 *
313	 * @return bool Previous setting
314	 */
315	public function setDisabledEntityVisibility($show = true) {
316		global $ENTITY_SHOW_HIDDEN_OVERRIDE;
317		$ENTITY_SHOW_HIDDEN_OVERRIDE = $show;
318
319		$prev = $this->show_disabled_entities;
320		$this->show_disabled_entities = $show;
321
322		return $prev;
323	}
324
325	/**
326	 * Adds a token to the session
327	 *
328	 * This is used in creation of CSRF token, and is passed to the client to allow validating tokens
329	 * later, even if the PHP session was destroyed.
330	 *
331	 * @return void
332	 */
333	protected function generateSessionToken() {
334		// Generate a simple token that we store server side
335		if (!$this->has('__elgg_session')) {
336			$this->set('__elgg_session', _elgg_services()->crypto->getRandomString(22));
337		}
338	}
339
340	/**
341	 * Get an isolated ElggSession that does not persist between requests
342	 *
343	 * @return self
344	 *
345	 * @internal
346	 */
347	public static function getMock() {
348		$storage = new MockArraySessionStorage();
349		$session = new Session($storage);
350		return new self($session);
351	}
352
353	/**
354	 * Create a session stored in the DB.
355	 *
356	 * @param Config   $config Config
357	 * @param Database $db     Database
358	 *
359	 * @return ElggSession
360	 *
361	 * @internal
362	 */
363	public static function fromDatabase(Config $config, Database $db) {
364		$params = $config->getCookieConfig()['session'];
365		$options = [
366			// session.cache_limiter is unfortunately set to "" by the NativeSessionStorage
367			// constructor, so we must capture and inject it directly.
368			'cache_limiter' => session_cache_limiter(),
369
370			'name' => $params['name'],
371			'cookie_path' => $params['path'],
372			'cookie_domain' => $params['domain'],
373			'cookie_secure' => $params['secure'],
374			'cookie_httponly' => $params['httponly'],
375			'cookie_lifetime' => $params['lifetime'],
376		];
377
378		$handler = new DatabaseSessionHandler($db);
379		$storage = new NativeSessionStorage($options, $handler);
380		$session = new Session($storage);
381		return new self($session);
382	}
383
384	/**
385	 * Create a session stored in files
386	 *
387	 * @param Config $config Config
388	 *
389	 * @return ElggSession
390	 *
391	 * @internal
392	 */
393	public static function fromFiles(Config $config) {
394		$params = $config->getCookieConfig()['session'];
395		$options = [
396			// session.cache_limiter is unfortunately set to "" by the NativeSessionStorage
397			// constructor, so we must capture and inject it directly.
398			'cache_limiter' => session_cache_limiter(),
399
400			'name' => $params['name'],
401			'cookie_path' => $params['path'],
402			'cookie_domain' => $params['domain'],
403			'cookie_secure' => $params['secure'],
404			'cookie_httponly' => $params['httponly'],
405			'cookie_lifetime' => $params['lifetime'],
406		];
407
408		$storage = new NativeSessionStorage($options);
409		$session = new Session($storage);
410		return new self($session);
411	}
412}
413