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