1<?php
2/**
3 * Zend Framework
4 *
5 * LICENSE
6 *
7 * This source file is subject to the new BSD license that is bundled
8 * with this package in the file LICENSE.txt.
9 * It is also available through the world-wide-web at this URL:
10 * http://framework.zend.com/license/new-bsd
11 * If you did not receive a copy of the license and are unable to
12 * obtain it through the world-wide-web, please send an email
13 * to license@zend.com so we can send you a copy immediately.
14 *
15 * @category   Zend
16 * @package    Zend_Session
17 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
18 * @license    http://framework.zend.com/license/new-bsd     New BSD License
19 * @version    $Id: Namespace.php 23775 2011-03-01 17:25:24Z ralph $
20 * @since      Preview Release 0.2
21 */
22
23
24/**
25 * @see Zend_Session
26 */
27// require_once 'Zend/Session.php';
28
29
30/**
31 * @see Zend_Session_Abstract
32 */
33// require_once 'Zend/Session/Abstract.php';
34
35
36/**
37 * Zend_Session_Namespace
38 *
39 * @category   Zend
40 * @package    Zend_Session
41 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
42 * @license    http://framework.zend.com/license/new-bsd     New BSD License
43 */
44class Zend_Session_Namespace extends Zend_Session_Abstract implements IteratorAggregate
45{
46
47    /**
48     * used as option to constructor to prevent additional instances to the same namespace
49     */
50    const SINGLE_INSTANCE = true;
51
52    /**
53     * Namespace - which namespace this instance of zend-session is saving-to/getting-from
54     *
55     * @var string
56     */
57    protected $_namespace = "Default";
58
59    /**
60     * Namespace locking mechanism
61     *
62     * @var array
63     */
64    protected static $_namespaceLocks = array();
65
66    /**
67     * Single instance namespace array to ensure data security.
68     *
69     * @var array
70     */
71    protected static $_singleInstances = array();
72
73    /**
74     * resetSingleInstance()
75     *
76     * @param string $namespaceName
77     * @return null
78     */
79    public static function resetSingleInstance($namespaceName = null)
80    {
81        if ($namespaceName != null) {
82            if (array_key_exists($namespaceName, self::$_singleInstances)) {
83                unset(self::$_singleInstances[$namespaceName]);
84            }
85            return;
86        }
87
88        self::$_singleInstances = array();
89        return;
90    }
91
92    /**
93     * __construct() - Returns an instance object bound to a particular, isolated section
94     * of the session, identified by $namespace name (defaulting to 'Default').
95     * The optional argument $singleInstance will prevent construction of additional
96     * instance objects acting as accessors to this $namespace.
97     *
98     * @param string $namespace       - programmatic name of the requested namespace
99     * @param bool $singleInstance    - prevent creation of additional accessor instance objects for this namespace
100     * @return void
101     */
102    public function __construct($namespace = 'Default', $singleInstance = false)
103    {
104        if ($namespace === '') {
105            /**
106             * @see Zend_Session_Exception
107             */
108            // require_once 'Zend/Session/Exception.php';
109            throw new Zend_Session_Exception('Session namespace must be a non-empty string.');
110        }
111
112        if ($namespace[0] == "_") {
113            /**
114             * @see Zend_Session_Exception
115             */
116            // require_once 'Zend/Session/Exception.php';
117            throw new Zend_Session_Exception('Session namespace must not start with an underscore.');
118        }
119
120        if (preg_match('#(^[0-9])#i', $namespace[0])) {
121            /**
122             * @see Zend_Session_Exception
123             */
124            // require_once 'Zend/Session/Exception.php';
125            throw new Zend_Session_Exception('Session namespace must not start with a number.');
126        }
127
128        if (isset(self::$_singleInstances[$namespace])) {
129            /**
130             * @see Zend_Session_Exception
131             */
132            // require_once 'Zend/Session/Exception.php';
133            throw new Zend_Session_Exception("A session namespace object already exists for this namespace ('$namespace'), and no additional accessors (session namespace objects) for this namespace are permitted.");
134        }
135
136        if ($singleInstance === true) {
137            self::$_singleInstances[$namespace] = true;
138        }
139
140        $this->_namespace = $namespace;
141
142        // Process metadata specific only to this namespace.
143        Zend_Session::start(true); // attempt auto-start (throws exception if strict option set)
144
145        if (self::$_readable === false) {
146            /**
147             * @see Zend_Session_Exception
148             */
149            // require_once 'Zend/Session/Exception.php';
150            throw new Zend_Session_Exception(self::_THROW_NOT_READABLE_MSG);
151        }
152
153        if (!isset($_SESSION['__ZF'])) {
154            return; // no further processing needed
155        }
156
157        // do not allow write access to namespaces, after stop() or writeClose()
158        if (parent::$_writable === true) {
159            if (isset($_SESSION['__ZF'][$namespace])) {
160
161                // Expire Namespace by Namespace Hop (ENNH)
162                if (isset($_SESSION['__ZF'][$namespace]['ENNH'])) {
163                    $_SESSION['__ZF'][$namespace]['ENNH']--;
164
165                    if ($_SESSION['__ZF'][$namespace]['ENNH'] === 0) {
166                        if (isset($_SESSION[$namespace])) {
167                            self::$_expiringData[$namespace] = $_SESSION[$namespace];
168                            unset($_SESSION[$namespace]);
169                        }
170                        unset($_SESSION['__ZF'][$namespace]);
171                    }
172                }
173
174                // Expire Namespace Variables by Namespace Hop (ENVNH)
175                if (isset($_SESSION['__ZF'][$namespace]['ENVNH'])) {
176                    foreach ($_SESSION['__ZF'][$namespace]['ENVNH'] as $variable => $hops) {
177                        $_SESSION['__ZF'][$namespace]['ENVNH'][$variable]--;
178
179                        if ($_SESSION['__ZF'][$namespace]['ENVNH'][$variable] === 0) {
180                            if (isset($_SESSION[$namespace][$variable])) {
181                                self::$_expiringData[$namespace][$variable] = $_SESSION[$namespace][$variable];
182                                unset($_SESSION[$namespace][$variable]);
183                            }
184                            unset($_SESSION['__ZF'][$namespace]['ENVNH'][$variable]);
185                        }
186                    }
187                    if(empty($_SESSION['__ZF'][$namespace]['ENVNH'])) {
188                        unset($_SESSION['__ZF'][$namespace]['ENVNH']);
189                    }
190                }
191            }
192
193            if (empty($_SESSION['__ZF'][$namespace])) {
194                unset($_SESSION['__ZF'][$namespace]);
195            }
196
197            if (empty($_SESSION['__ZF'])) {
198                unset($_SESSION['__ZF']);
199            }
200        }
201    }
202
203
204    /**
205     * getIterator() - return an iteratable object for use in foreach and the like,
206     * this completes the IteratorAggregate interface
207     *
208     * @return ArrayObject - iteratable container of the namespace contents
209     */
210    public function getIterator(): ArrayObject
211    {
212        return new ArrayObject(parent::_namespaceGetAll($this->_namespace));
213    }
214
215
216    /**
217     * lock() - mark a session/namespace as readonly
218     *
219     * @return void
220     */
221    public function lock()
222    {
223        self::$_namespaceLocks[$this->_namespace] = true;
224    }
225
226
227    /**
228     * unlock() - unmark a session/namespace to enable read & write
229     *
230     * @return void
231     */
232    public function unlock()
233    {
234        unset(self::$_namespaceLocks[$this->_namespace]);
235    }
236
237
238    /**
239     * unlockAll() - unmark all session/namespaces to enable read & write
240     *
241     * @return void
242     */
243    public static function unlockAll()
244    {
245        self::$_namespaceLocks = array();
246    }
247
248
249    /**
250     * isLocked() - return lock status, true if, and only if, read-only
251     *
252     * @return bool
253     */
254    public function isLocked()
255    {
256        return isset(self::$_namespaceLocks[$this->_namespace]);
257    }
258
259
260    /**
261     * unsetAll() - unset all variables in this namespace
262     *
263     * @return true
264     */
265    public function unsetAll()
266    {
267        return parent::_namespaceUnset($this->_namespace);
268    }
269
270
271    /**
272     * __get() - method to get a variable in this object's current namespace
273     *
274     * @param string $name - programmatic name of a key, in a <key,value> pair in the current namespace
275     * @return mixed
276     */
277    public function & __get($name)
278    {
279        if ($name === '') {
280            /**
281             * @see Zend_Session_Exception
282             */
283            // require_once 'Zend/Session/Exception.php';
284            throw new Zend_Session_Exception("The '$name' key must be a non-empty string");
285        }
286
287        return parent::_namespaceGet($this->_namespace, $name);
288    }
289
290
291    /**
292     * __set() - method to set a variable/value in this object's namespace
293     *
294     * @param string $name - programmatic name of a key, in a <key,value> pair in the current namespace
295     * @param mixed $value - value in the <key,value> pair to assign to the $name key
296     * @throws Zend_Session_Exception
297     * @return true
298     */
299    public function __set($name, $value)
300    {
301        if (isset(self::$_namespaceLocks[$this->_namespace])) {
302            /**
303             * @see Zend_Session_Exception
304             */
305            // require_once 'Zend/Session/Exception.php';
306            throw new Zend_Session_Exception('This session/namespace has been marked as read-only.');
307        }
308
309        if ($name === '') {
310            /**
311             * @see Zend_Session_Exception
312             */
313            // require_once 'Zend/Session/Exception.php';
314            throw new Zend_Session_Exception("The '$name' key must be a non-empty string");
315        }
316
317        if (parent::$_writable === false) {
318            /**
319             * @see Zend_Session_Exception
320             */
321            // require_once 'Zend/Session/Exception.php';
322            throw new Zend_Session_Exception(parent::_THROW_NOT_WRITABLE_MSG);
323        }
324
325        $name = (string) $name;
326
327        $_SESSION[$this->_namespace][$name] = $value;
328    }
329
330
331    /**
332     * apply() - enables applying user-selected function, such as array_merge() to the namespace
333     * Parameters following the $callback argument are passed to the callback function.
334     * Caveat: ignores members expiring now.
335     *
336     * Example:
337     *   $namespace->apply('array_merge', array('tree' => 'apple', 'fruit' => 'peach'), array('flower' => 'rose'));
338     *   $namespace->apply('count');
339     *
340     * @param string|array $callback - callback function
341     */
342    public function apply($callback)
343    {
344        $arg_list = func_get_args();
345        $arg_list[0] = $_SESSION[$this->_namespace];
346        return call_user_func_array($callback, $arg_list);
347    }
348
349
350    /**
351     * applySet() - enables applying user-selected function, and sets entire namespace to the result
352     * Result of $callback must be an array.
353     * Parameters following the $callback argument are passed to the callback function.
354     * Caveat: ignores members expiring now.
355     *
356     * Example:
357     *   $namespace->applySet('array_merge', array('tree' => 'apple', 'fruit' => 'peach'), array('flower' => 'rose'));
358     *
359     * @param string|array $callback - callback function
360     */
361    public function applySet($callback)
362    {
363        $arg_list = func_get_args();
364        $arg_list[0] = $_SESSION[$this->_namespace];
365        $result = call_user_func_array($callback, $arg_list);
366        if (!is_array($result)) {
367            /**
368             * @see Zend_Session_Exception
369             */
370            // require_once 'Zend/Session/Exception.php';
371            throw new Zend_Session_Exception('Result must be an array. Got: ' . gettype($result));
372        }
373        $_SESSION[$this->_namespace] = $result;
374        return $result;
375    }
376
377
378    /**
379     * __isset() - determine if a variable in this object's namespace is set
380     *
381     * @param string $name - programmatic name of a key, in a <key,value> pair in the current namespace
382     * @return bool
383     */
384    public function __isset($name)
385    {
386        if ($name === '') {
387            /**
388             * @see Zend_Session_Exception
389             */
390            // require_once 'Zend/Session/Exception.php';
391            throw new Zend_Session_Exception("The '$name' key must be a non-empty string");
392        }
393
394        return parent::_namespaceIsset($this->_namespace, $name);
395    }
396
397
398    /**
399     * __unset() - unset a variable in this object's namespace.
400     *
401     * @param string $name - programmatic name of a key, in a <key,value> pair in the current namespace
402     * @return true
403     */
404    public function __unset($name)
405    {
406        if ($name === '') {
407            /**
408             * @see Zend_Session_Exception
409             */
410            // require_once 'Zend/Session/Exception.php';
411            throw new Zend_Session_Exception("The '$name' key must be a non-empty string");
412        }
413
414        return parent::_namespaceUnset($this->_namespace, $name);
415    }
416
417
418    /**
419     * setExpirationSeconds() - expire the namespace, or specific variables after a specified
420     * number of seconds
421     *
422     * @param int $seconds     - expires in this many seconds
423     * @param mixed $variables - OPTIONAL list of variables to expire (defaults to all)
424     * @throws Zend_Session_Exception
425     * @return void
426     */
427    public function setExpirationSeconds($seconds, $variables = null)
428    {
429        if (parent::$_writable === false) {
430            /**
431             * @see Zend_Session_Exception
432             */
433            // require_once 'Zend/Session/Exception.php';
434            throw new Zend_Session_Exception(parent::_THROW_NOT_WRITABLE_MSG);
435        }
436
437        if ($seconds <= 0) {
438            /**
439             * @see Zend_Session_Exception
440             */
441            // require_once 'Zend/Session/Exception.php';
442            throw new Zend_Session_Exception('Seconds must be positive.');
443        }
444
445        if ($variables === null) {
446
447            // apply expiration to entire namespace
448            $_SESSION['__ZF'][$this->_namespace]['ENT'] = time() + $seconds;
449
450        } else {
451
452            if (is_string($variables)) {
453                $variables = array($variables);
454            }
455
456            foreach ($variables as $variable) {
457                if (!empty($variable)) {
458                    $_SESSION['__ZF'][$this->_namespace]['ENVT'][$variable] = time() + $seconds;
459                }
460            }
461        }
462    }
463
464
465    /**
466     * setExpirationHops() - expire the namespace, or specific variables after a specified
467     * number of page hops
468     *
469     * @param int $hops        - how many "hops" (number of subsequent requests) before expiring
470     * @param mixed $variables - OPTIONAL list of variables to expire (defaults to all)
471     * @param boolean $hopCountOnUsageOnly - OPTIONAL if set, only count a hop/request if this namespace is used
472     * @throws Zend_Session_Exception
473     * @return void
474     */
475    public function setExpirationHops($hops, $variables = null, $hopCountOnUsageOnly = false)
476    {
477        if (parent::$_writable === false) {
478            /**
479             * @see Zend_Session_Exception
480             */
481            // require_once 'Zend/Session/Exception.php';
482            throw new Zend_Session_Exception(parent::_THROW_NOT_WRITABLE_MSG);
483        }
484
485        if ($hops <= 0) {
486            /**
487             * @see Zend_Session_Exception
488             */
489            // require_once 'Zend/Session/Exception.php';
490            throw new Zend_Session_Exception('Hops must be positive number.');
491        }
492
493        if ($variables === null) {
494
495            // apply expiration to entire namespace
496            if ($hopCountOnUsageOnly === false) {
497                $_SESSION['__ZF'][$this->_namespace]['ENGH'] = $hops;
498            } else {
499                $_SESSION['__ZF'][$this->_namespace]['ENNH'] = $hops;
500            }
501
502        } else {
503
504            if (is_string($variables)) {
505                $variables = array($variables);
506            }
507
508            foreach ($variables as $variable) {
509                if (!empty($variable)) {
510                    if ($hopCountOnUsageOnly === false) {
511                        $_SESSION['__ZF'][$this->_namespace]['ENVGH'][$variable] = $hops;
512                    } else {
513                        $_SESSION['__ZF'][$this->_namespace]['ENVNH'][$variable] = $hops;
514                    }
515                }
516            }
517        }
518    }
519
520    /**
521     * Returns the namespace name
522     *
523     * @return string
524     */
525    public function getNamespace()
526    {
527        return $this->_namespace;
528    }
529}
530