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