274b0df6b9e39c0ad56bd7caed5c6a78da0e841f
[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_cookies, "1"
85      * use_only_cookies, "1"
86      * use_trans_sid, "0"
87      * upload_progress.enabled, "1"
88      * upload_progress.cleanup, "1"
89      * upload_progress.prefix, "upload_progress_"
90      * upload_progress.name, "PHP_SESSION_UPLOAD_PROGRESS"
91      * upload_progress.freq, "1%"
92      * upload_progress.min-freq, "1"
93      * url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset="
94      *
95      * @param array                                                            $options Session configuration options
96      * @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $handler
97      * @param MetadataBag                                                      $metaBag MetadataBag
98      */
99     public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null)
100     {
101         session_cache_limiter(''); // disable by default because it's managed by HeaderBag (if used)
102         ini_set('session.use_cookies', 1);
103
104         session_register_shutdown();
105
106         $this->setMetadataBag($metaBag);
107         $this->setOptions($options);
108         $this->setSaveHandler($handler);
109     }
110
111     /**
112      * Gets the save handler instance.
113      *
114      * @return AbstractProxy
115      */
116     public function getSaveHandler()
117     {
118         return $this->saveHandler;
119     }
120
121     /**
122      * {@inheritdoc}
123      */
124     public function start()
125     {
126         if ($this->started) {
127             return true;
128         }
129
130         if (PHP_VERSION_ID >= 50400 && \PHP_SESSION_ACTIVE === session_status()) {
131             throw new \RuntimeException('Failed to start the session: already started by PHP.');
132         }
133
134         if (PHP_VERSION_ID < 50400 && !$this->closed && isset($_SESSION) && session_id()) {
135             // not 100% fool-proof, but is the most reliable way to determine if a session is active in PHP 5.3
136             throw new \RuntimeException('Failed to start the session: already started by PHP ($_SESSION is set).');
137         }
138
139         if (ini_get('session.use_cookies') && headers_sent($file, $line)) {
140             throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
141         }
142
143         // ok to try and start the session
144         if (!session_start()) {
145             throw new \RuntimeException('Failed to start the session');
146         }
147
148         $this->loadSession();
149         if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) {
150             // This condition matches only PHP 5.3 with internal save handlers
151             $this->saveHandler->setActive(true);
152         }
153
154         return true;
155     }
156
157     /**
158      * {@inheritdoc}
159      */
160     public function getId()
161     {
162         return $this->saveHandler->getId();
163     }
164
165     /**
166      * {@inheritdoc}
167      */
168     public function setId($id)
169     {
170         $this->saveHandler->setId($id);
171     }
172
173     /**
174      * {@inheritdoc}
175      */
176     public function getName()
177     {
178         return $this->saveHandler->getName();
179     }
180
181     /**
182      * {@inheritdoc}
183      */
184     public function setName($name)
185     {
186         $this->saveHandler->setName($name);
187     }
188
189     /**
190      * {@inheritdoc}
191      */
192     public function regenerate($destroy = false, $lifetime = null)
193     {
194         // Cannot regenerate the session ID for non-active sessions.
195         if (PHP_VERSION_ID >= 50400 && \PHP_SESSION_ACTIVE !== session_status()) {
196             return false;
197         }
198
199         // Check if session ID exists in PHP 5.3
200         if (PHP_VERSION_ID < 50400 && '' === session_id()) {
201             return false;
202         }
203
204         if (null !== $lifetime) {
205             ini_set('session.cookie_lifetime', $lifetime);
206         }
207
208         if ($destroy) {
209             $this->metadataBag->stampNew();
210         }
211
212         $isRegenerated = session_regenerate_id($destroy);
213
214         // The reference to $_SESSION in session bags is lost in PHP7 and we need to re-create it.
215         // @see https://bugs.php.net/bug.php?id=70013
216         $this->loadSession();
217
218         return $isRegenerated;
219     }
220
221     /**
222      * {@inheritdoc}
223      */
224     public function save()
225     {
226         session_write_close();
227
228         if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) {
229             // This condition matches only PHP 5.3 with internal save handlers
230             $this->saveHandler->setActive(false);
231         }
232
233         $this->closed = true;
234         $this->started = false;
235     }
236
237     /**
238      * {@inheritdoc}
239      */
240     public function clear()
241     {
242         // clear out the bags
243         foreach ($this->bags as $bag) {
244             $bag->clear();
245         }
246
247         // clear out the session
248         $_SESSION = array();
249
250         // reconnect the bags to the session
251         $this->loadSession();
252     }
253
254     /**
255      * {@inheritdoc}
256      */
257     public function registerBag(SessionBagInterface $bag)
258     {
259         if ($this->started) {
260             throw new \LogicException('Cannot register a bag when the session is already started.');
261         }
262
263         $this->bags[$bag->getName()] = $bag;
264     }
265
266     /**
267      * {@inheritdoc}
268      */
269     public function getBag($name)
270     {
271         if (!isset($this->bags[$name])) {
272             throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name));
273         }
274
275         if ($this->saveHandler->isActive() && !$this->started) {
276             $this->loadSession();
277         } elseif (!$this->started) {
278             $this->start();
279         }
280
281         return $this->bags[$name];
282     }
283
284     /**
285      * Sets the MetadataBag.
286      *
287      * @param MetadataBag $metaBag
288      */
289     public function setMetadataBag(MetadataBag $metaBag = null)
290     {
291         if (null === $metaBag) {
292             $metaBag = new MetadataBag();
293         }
294
295         $this->metadataBag = $metaBag;
296     }
297
298     /**
299      * Gets the MetadataBag.
300      *
301      * @return MetadataBag
302      */
303     public function getMetadataBag()
304     {
305         return $this->metadataBag;
306     }
307
308     /**
309      * {@inheritdoc}
310      */
311     public function isStarted()
312     {
313         return $this->started;
314     }
315
316     /**
317      * Sets session.* ini variables.
318      *
319      * For convenience we omit 'session.' from the beginning of the keys.
320      * Explicitly ignores other ini keys.
321      *
322      * @param array $options Session ini directives array(key => value)
323      *
324      * @see http://php.net/session.configuration
325      */
326     public function setOptions(array $options)
327     {
328         $validOptions = array_flip(array(
329             'cache_limiter', 'cookie_domain', 'cookie_httponly',
330             'cookie_lifetime', 'cookie_path', 'cookie_secure',
331             'entropy_file', 'entropy_length', 'gc_divisor',
332             'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character',
333             'hash_function', 'name', 'referer_check',
334             'serialize_handler', 'use_cookies',
335             'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled',
336             'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name',
337             'upload_progress.freq', 'upload_progress.min-freq', 'url_rewriter.tags',
338         ));
339
340         foreach ($options as $key => $value) {
341             if (isset($validOptions[$key])) {
342                 ini_set('session.'.$key, $value);
343             }
344         }
345     }
346
347     /**
348      * Registers session save handler as a PHP session handler.
349      *
350      * To use internal PHP session save handlers, override this method using ini_set with
351      * session.save_handler and session.save_path e.g.
352      *
353      *     ini_set('session.save_handler', 'files');
354      *     ini_set('session.save_path', '/tmp');
355      *
356      * or pass in a NativeSessionHandler instance which configures session.save_handler in the
357      * constructor, for a template see NativeFileSessionHandler or use handlers in
358      * composer package drak/native-session
359      *
360      * @see http://php.net/session-set-save-handler
361      * @see http://php.net/sessionhandlerinterface
362      * @see http://php.net/sessionhandler
363      * @see http://github.com/drak/NativeSession
364      *
365      * @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $saveHandler
366      *
367      * @throws \InvalidArgumentException
368      */
369     public function setSaveHandler($saveHandler = null)
370     {
371         if (!$saveHandler instanceof AbstractProxy &&
372             !$saveHandler instanceof NativeSessionHandler &&
373             !$saveHandler instanceof \SessionHandlerInterface &&
374             null !== $saveHandler) {
375             throw new \InvalidArgumentException('Must be instance of AbstractProxy or NativeSessionHandler; implement \SessionHandlerInterface; or be null.');
376         }
377
378         // Wrap $saveHandler in proxy and prevent double wrapping of proxy
379         if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) {
380             $saveHandler = new SessionHandlerProxy($saveHandler);
381         } elseif (!$saveHandler instanceof AbstractProxy) {
382             $saveHandler = PHP_VERSION_ID >= 50400 ?
383                 new SessionHandlerProxy(new \SessionHandler()) : new NativeProxy();
384         }
385         $this->saveHandler = $saveHandler;
386
387         if ($this->saveHandler instanceof \SessionHandlerInterface) {
388             if (PHP_VERSION_ID >= 50400) {
389                 session_set_save_handler($this->saveHandler, false);
390             } else {
391                 session_set_save_handler(
392                     array($this->saveHandler, 'open'),
393                     array($this->saveHandler, 'close'),
394                     array($this->saveHandler, 'read'),
395                     array($this->saveHandler, 'write'),
396                     array($this->saveHandler, 'destroy'),
397                     array($this->saveHandler, 'gc')
398                 );
399             }
400         }
401     }
402
403     /**
404      * Load the session with attributes.
405      *
406      * After starting the session, PHP retrieves the session from whatever handlers
407      * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()).
408      * PHP takes the return value from the read() handler, unserializes it
409      * and populates $_SESSION with the result automatically.
410      *
411      * @param array|null $session
412      */
413     protected function loadSession(array &$session = null)
414     {
415         if (null === $session) {
416             $session = &$_SESSION;
417         }
418
419         $bags = array_merge($this->bags, array($this->metadataBag));
420
421         foreach ($bags as $bag) {
422             $key = $bag->getStorageKey();
423             $session[$key] = isset($session[$key]) ? $session[$key] : array();
424             $bag->initialize($session[$key]);
425         }
426
427         $this->started = true;
428         $this->closed = false;
429     }
430 }