Yaffs site version 1.1
[yaffs-website] / vendor / symfony / http-foundation / Session / Storage / NativeSessionStorage.php
1 <?php
2
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Symfony\Component\HttpFoundation\Session\Storage;
13
14 use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
15 use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler;
16 use Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy;
17 use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
18 use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
19
20 /**
21  * This provides a base class for session attribute storage.
22  *
23  * @author Drak <drak@zikula.org>
24  */
25 class NativeSessionStorage implements SessionStorageInterface
26 {
27     /**
28      * Array of SessionBagInterface.
29      *
30      * @var SessionBagInterface[]
31      */
32     protected $bags;
33
34     /**
35      * @var bool
36      */
37     protected $started = false;
38
39     /**
40      * @var bool
41      */
42     protected $closed = false;
43
44     /**
45      * @var AbstractProxy
46      */
47     protected $saveHandler;
48
49     /**
50      * @var MetadataBag
51      */
52     protected $metadataBag;
53
54     /**
55      * Constructor.
56      *
57      * Depending on how you want the storage driver to behave you probably
58      * want to override this constructor entirely.
59      *
60      * List of options for $options array with their defaults.
61      *
62      * @see http://php.net/session.configuration for options
63      * but we omit 'session.' from the beginning of the keys for convenience.
64      *
65      * ("auto_start", is not supported as it tells PHP to start a session before
66      * PHP starts to execute user-land code. Setting during runtime has no effect).
67      *
68      * cache_limiter, "" (use "0" to prevent headers from being sent entirely).
69      * cookie_domain, ""
70      * cookie_httponly, ""
71      * cookie_lifetime, "0"
72      * cookie_path, "/"
73      * cookie_secure, ""
74      * entropy_file, ""
75      * entropy_length, "0"
76      * gc_divisor, "100"
77      * gc_maxlifetime, "1440"
78      * gc_probability, "1"
79      * hash_bits_per_character, "4"
80      * hash_function, "0"
81      * name, "PHPSESSID"
82      * referer_check, ""
83      * serialize_handler, "php"
84      * use_strict_mode, "0"
85      * use_cookies, "1"
86      * use_only_cookies, "1"
87      * use_trans_sid, "0"
88      * upload_progress.enabled, "1"
89      * upload_progress.cleanup, "1"
90      * upload_progress.prefix, "upload_progress_"
91      * upload_progress.name, "PHP_SESSION_UPLOAD_PROGRESS"
92      * upload_progress.freq, "1%"
93      * upload_progress.min-freq, "1"
94      * url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset="
95      *
96      * @param array                                                            $options Session configuration options
97      * @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $handler
98      * @param MetadataBag                                                      $metaBag MetadataBag
99      */
100     public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null)
101     {
102         session_cache_limiter(''); // disable by default because it's managed by HeaderBag (if used)
103         ini_set('session.use_cookies', 1);
104
105         session_register_shutdown();
106
107         $this->setMetadataBag($metaBag);
108         $this->setOptions($options);
109         $this->setSaveHandler($handler);
110     }
111
112     /**
113      * Gets the save handler instance.
114      *
115      * @return AbstractProxy
116      */
117     public function getSaveHandler()
118     {
119         return $this->saveHandler;
120     }
121
122     /**
123      * {@inheritdoc}
124      */
125     public function start()
126     {
127         if ($this->started) {
128             return true;
129         }
130
131         if (\PHP_VERSION_ID >= 50400 && \PHP_SESSION_ACTIVE === session_status()) {
132             throw new \RuntimeException('Failed to start the session: already started by PHP.');
133         }
134
135         if (\PHP_VERSION_ID < 50400 && !$this->closed && isset($_SESSION) && session_id()) {
136             // not 100% fool-proof, but is the most reliable way to determine if a session is active in PHP 5.3
137             throw new \RuntimeException('Failed to start the session: already started by PHP ($_SESSION is set).');
138         }
139
140         if (ini_get('session.use_cookies') && headers_sent($file, $line)) {
141             throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
142         }
143
144         // ok to try and start the session
145         if (!session_start()) {
146             throw new \RuntimeException('Failed to start the session');
147         }
148
149         $this->loadSession();
150         if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) {
151             // This condition matches only PHP 5.3 with internal save handlers
152             $this->saveHandler->setActive(true);
153         }
154
155         return true;
156     }
157
158     /**
159      * {@inheritdoc}
160      */
161     public function getId()
162     {
163         return $this->saveHandler->getId();
164     }
165
166     /**
167      * {@inheritdoc}
168      */
169     public function setId($id)
170     {
171         $this->saveHandler->setId($id);
172     }
173
174     /**
175      * {@inheritdoc}
176      */
177     public function getName()
178     {
179         return $this->saveHandler->getName();
180     }
181
182     /**
183      * {@inheritdoc}
184      */
185     public function setName($name)
186     {
187         $this->saveHandler->setName($name);
188     }
189
190     /**
191      * {@inheritdoc}
192      */
193     public function regenerate($destroy = false, $lifetime = null)
194     {
195         // Cannot regenerate the session ID for non-active sessions.
196         if (\PHP_VERSION_ID >= 50400 && \PHP_SESSION_ACTIVE !== session_status()) {
197             return false;
198         }
199
200         // Check if session ID exists in PHP 5.3
201         if (\PHP_VERSION_ID < 50400 && '' === session_id()) {
202             return false;
203         }
204
205         if (null !== $lifetime) {
206             ini_set('session.cookie_lifetime', $lifetime);
207         }
208
209         if ($destroy) {
210             $this->metadataBag->stampNew();
211         }
212
213         $isRegenerated = session_regenerate_id($destroy);
214
215         // The reference to $_SESSION in session bags is lost in PHP7 and we need to re-create it.
216         // @see https://bugs.php.net/bug.php?id=70013
217         $this->loadSession();
218
219         return $isRegenerated;
220     }
221
222     /**
223      * {@inheritdoc}
224      */
225     public function save()
226     {
227         session_write_close();
228
229         if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) {
230             // This condition matches only PHP 5.3 with internal save handlers
231             $this->saveHandler->setActive(false);
232         }
233
234         $this->closed = true;
235         $this->started = false;
236     }
237
238     /**
239      * {@inheritdoc}
240      */
241     public function clear()
242     {
243         // clear out the bags
244         foreach ($this->bags as $bag) {
245             $bag->clear();
246         }
247
248         // clear out the session
249         $_SESSION = array();
250
251         // reconnect the bags to the session
252         $this->loadSession();
253     }
254
255     /**
256      * {@inheritdoc}
257      */
258     public function registerBag(SessionBagInterface $bag)
259     {
260         if ($this->started) {
261             throw new \LogicException('Cannot register a bag when the session is already started.');
262         }
263
264         $this->bags[$bag->getName()] = $bag;
265     }
266
267     /**
268      * {@inheritdoc}
269      */
270     public function getBag($name)
271     {
272         if (!isset($this->bags[$name])) {
273             throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name));
274         }
275
276         if ($this->saveHandler->isActive() && !$this->started) {
277             $this->loadSession();
278         } elseif (!$this->started) {
279             $this->start();
280         }
281
282         return $this->bags[$name];
283     }
284
285     /**
286      * Sets the MetadataBag.
287      *
288      * @param MetadataBag $metaBag
289      */
290     public function setMetadataBag(MetadataBag $metaBag = null)
291     {
292         if (null === $metaBag) {
293             $metaBag = new MetadataBag();
294         }
295
296         $this->metadataBag = $metaBag;
297     }
298
299     /**
300      * Gets the MetadataBag.
301      *
302      * @return MetadataBag
303      */
304     public function getMetadataBag()
305     {
306         return $this->metadataBag;
307     }
308
309     /**
310      * {@inheritdoc}
311      */
312     public function isStarted()
313     {
314         return $this->started;
315     }
316
317     /**
318      * Sets session.* ini variables.
319      *
320      * For convenience we omit 'session.' from the beginning of the keys.
321      * Explicitly ignores other ini keys.
322      *
323      * @param array $options Session ini directives array(key => value)
324      *
325      * @see http://php.net/session.configuration
326      */
327     public function setOptions(array $options)
328     {
329         $validOptions = array_flip(array(
330             'cache_limiter', 'cookie_domain', 'cookie_httponly',
331             'cookie_lifetime', 'cookie_path', 'cookie_secure',
332             'entropy_file', 'entropy_length', 'gc_divisor',
333             'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character',
334             'hash_function', 'name', 'referer_check',
335             'serialize_handler', 'use_strict_mode', 'use_cookies',
336             'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled',
337             'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name',
338             'upload_progress.freq', 'upload_progress.min-freq', 'url_rewriter.tags',
339         ));
340
341         foreach ($options as $key => $value) {
342             if (isset($validOptions[$key])) {
343                 ini_set('session.'.$key, $value);
344             }
345         }
346     }
347
348     /**
349      * Registers session save handler as a PHP session handler.
350      *
351      * To use internal PHP session save handlers, override this method using ini_set with
352      * session.save_handler and session.save_path e.g.
353      *
354      *     ini_set('session.save_handler', 'files');
355      *     ini_set('session.save_path', '/tmp');
356      *
357      * or pass in a NativeSessionHandler instance which configures session.save_handler in the
358      * constructor, for a template see NativeFileSessionHandler or use handlers in
359      * composer package drak/native-session
360      *
361      * @see http://php.net/session-set-save-handler
362      * @see http://php.net/sessionhandlerinterface
363      * @see http://php.net/sessionhandler
364      * @see http://github.com/drak/NativeSession
365      *
366      * @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $saveHandler
367      *
368      * @throws \InvalidArgumentException
369      */
370     public function setSaveHandler($saveHandler = null)
371     {
372         if (!$saveHandler instanceof AbstractProxy &&
373             !$saveHandler instanceof NativeSessionHandler &&
374             !$saveHandler instanceof \SessionHandlerInterface &&
375             null !== $saveHandler) {
376             throw new \InvalidArgumentException('Must be instance of AbstractProxy or NativeSessionHandler; implement \SessionHandlerInterface; or be null.');
377         }
378
379         // Wrap $saveHandler in proxy and prevent double wrapping of proxy
380         if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) {
381             $saveHandler = new SessionHandlerProxy($saveHandler);
382         } elseif (!$saveHandler instanceof AbstractProxy) {
383             $saveHandler = \PHP_VERSION_ID >= 50400 ?
384                 new SessionHandlerProxy(new \SessionHandler()) : new NativeProxy();
385         }
386         $this->saveHandler = $saveHandler;
387
388         if ($this->saveHandler instanceof \SessionHandlerInterface) {
389             if (\PHP_VERSION_ID >= 50400) {
390                 session_set_save_handler($this->saveHandler, false);
391             } else {
392                 session_set_save_handler(
393                     array($this->saveHandler, 'open'),
394                     array($this->saveHandler, 'close'),
395                     array($this->saveHandler, 'read'),
396                     array($this->saveHandler, 'write'),
397                     array($this->saveHandler, 'destroy'),
398                     array($this->saveHandler, 'gc')
399                 );
400             }
401         }
402     }
403
404     /**
405      * Load the session with attributes.
406      *
407      * After starting the session, PHP retrieves the session from whatever handlers
408      * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()).
409      * PHP takes the return value from the read() handler, unserializes it
410      * and populates $_SESSION with the result automatically.
411      *
412      * @param array|null $session
413      */
414     protected function loadSession(array &$session = null)
415     {
416         if (null === $session) {
417             $session = &$_SESSION;
418         }
419
420         $bags = array_merge($this->bags, array($this->metadataBag));
421
422         foreach ($bags as $bag) {
423             $key = $bag->getStorageKey();
424             $session[$key] = isset($session[$key]) ? $session[$key] : array();
425             $bag->initialize($session[$key]);
426         }
427
428         $this->started = true;
429         $this->closed = false;
430     }
431 }