1<?php
2/**
3 * Zend Framework (http://framework.zend.com/)
4 *
5 * @link      http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license   http://framework.zend.com/license/new-bsd New BSD License
8 */
9
10namespace Zend\Cache\Pattern;
11
12use Traversable;
13use Zend\Cache\Exception;
14use Zend\Cache\StorageFactory;
15use Zend\Cache\Storage\StorageInterface as Storage;
16use Zend\Stdlib\AbstractOptions;
17
18class PatternOptions extends AbstractOptions
19{
20    /**
21     * Used by:
22     * - ClassCache
23     * - ObjectCache
24     * @var bool
25     */
26    protected $cacheByDefault = true;
27
28    /**
29     * Used by:
30     * - CallbackCache
31     * - ClassCache
32     * - ObjectCache
33     * @var bool
34     */
35    protected $cacheOutput = true;
36
37    /**
38     * Used by:
39     * - ClassCache
40     * @var null|string
41     */
42    protected $class;
43
44    /**
45     * Used by:
46     * - ClassCache
47     * @var array
48     */
49    protected $classCacheMethods = array();
50
51    /**
52     * Used by:
53     * - ClassCache
54     * @var array
55     */
56    protected $classNonCacheMethods = array();
57
58    /**
59     * Used by:
60     * - CaptureCache
61     * @var false|int
62     */
63    protected $umask = false;
64
65    /**
66     * Used by:
67     * - CaptureCache
68     * @var false|int
69     */
70    protected $dirPermission = 0700;
71
72    /**
73     * Used by:
74     * - CaptureCache
75     * @var false|int
76     */
77    protected $filePermission = 0600;
78
79    /**
80     * Used by:
81     * - CaptureCache
82     * @var bool
83     */
84    protected $fileLocking = true;
85
86    /**
87     * Used by:
88     * - CaptureCache
89     * @var string
90     */
91    protected $indexFilename = 'index.html';
92
93    /**
94     * Used by:
95     * - ObjectCache
96     * @var null|object
97     */
98    protected $object;
99
100    /**
101     * Used by:
102     * - ObjectCache
103     * @var bool
104     */
105    protected $objectCacheMagicProperties = false;
106
107    /**
108     * Used by:
109     * - ObjectCache
110     * @var array
111     */
112    protected $objectCacheMethods = array();
113
114    /**
115     * Used by:
116     * - ObjectCache
117     * @var null|string
118     */
119    protected $objectKey;
120
121    /**
122     * Used by:
123     * - ObjectCache
124     * @var array
125     */
126    protected $objectNonCacheMethods = array('__tostring');
127
128    /**
129     * Used by:
130     * - CaptureCache
131     * @var null|string
132     */
133    protected $publicDir;
134
135    /**
136     * Used by:
137     * - CallbackCache
138     * - ClassCache
139     * - ObjectCache
140     * - OutputCache
141     * @var null|Storage
142     */
143    protected $storage;
144
145    /**
146     * Constructor
147     *
148     * @param  array|Traversable|null $options
149     * @return PatternOptions
150     * @throws Exception\InvalidArgumentException
151     */
152    public function __construct($options = null)
153    {
154        // disable file/directory permissions by default on windows systems
155        if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
156            $this->filePermission = false;
157            $this->dirPermission = false;
158        }
159
160        parent::__construct($options);
161    }
162
163    /**
164     * Set flag indicating whether or not to cache by default
165     *
166     * Used by:
167     * - ClassCache
168     * - ObjectCache
169     *
170     * @param  bool $cacheByDefault
171     * @return PatternOptions
172     */
173    public function setCacheByDefault($cacheByDefault)
174    {
175        $this->cacheByDefault = $cacheByDefault;
176        return $this;
177    }
178
179    /**
180     * Do we cache by default?
181     *
182     * Used by:
183     * - ClassCache
184     * - ObjectCache
185     *
186     * @return bool
187     */
188    public function getCacheByDefault()
189    {
190        return $this->cacheByDefault;
191    }
192
193    /**
194     * Set whether or not to cache output
195     *
196     * Used by:
197     * - CallbackCache
198     * - ClassCache
199     * - ObjectCache
200     *
201     * @param  bool $cacheOutput
202     * @return PatternOptions
203     */
204    public function setCacheOutput($cacheOutput)
205    {
206        $this->cacheOutput = (bool) $cacheOutput;
207        return $this;
208    }
209
210    /**
211     * Will we cache output?
212     *
213     * Used by:
214     * - CallbackCache
215     * - ClassCache
216     * - ObjectCache
217     *
218     * @return bool
219     */
220    public function getCacheOutput()
221    {
222        return $this->cacheOutput;
223    }
224
225    /**
226     * Set class name
227     *
228     * Used by:
229     * - ClassCache
230     *
231     * @param  string $class
232     * @throws Exception\InvalidArgumentException
233     * @return PatternOptions
234     */
235    public function setClass($class)
236    {
237        if (!is_string($class)) {
238            throw new Exception\InvalidArgumentException('Invalid classname provided; must be a string');
239        }
240        $this->class = $class;
241        return $this;
242    }
243
244    /**
245     * Get class name
246     *
247     * Used by:
248     * - ClassCache
249     *
250     * @return null|string
251     */
252    public function getClass()
253    {
254        return $this->class;
255    }
256
257    /**
258     * Set list of method return values to cache
259     *
260     * Used by:
261     * - ClassCache
262     *
263     * @param  array $classCacheMethods
264     * @return PatternOptions
265     */
266    public function setClassCacheMethods(array $classCacheMethods)
267    {
268        $this->classCacheMethods = $this->recursiveStrtolower($classCacheMethods);
269        return $this;
270    }
271
272    /**
273     * Get list of methods from which to cache return values
274     *
275     * Used by:
276     * - ClassCache
277     *
278     * @return array
279     */
280    public function getClassCacheMethods()
281    {
282        return $this->classCacheMethods;
283    }
284
285    /**
286     * Set list of method return values NOT to cache
287     *
288     * Used by:
289     * - ClassCache
290     *
291     * @param  array $classNonCacheMethods
292     * @return PatternOptions
293     */
294    public function setClassNonCacheMethods(array $classNonCacheMethods)
295    {
296        $this->classNonCacheMethods = $this->recursiveStrtolower($classNonCacheMethods);
297        return $this;
298    }
299
300    /**
301     * Get list of methods from which NOT to cache return values
302     *
303     * Used by:
304     * - ClassCache
305     *
306     * @return array
307     */
308    public function getClassNonCacheMethods()
309    {
310        return $this->classNonCacheMethods;
311    }
312
313    /**
314     * Set directory permission
315     *
316     * @param  false|int $dirPermission
317     * @throws Exception\InvalidArgumentException
318     * @return PatternOptions
319     */
320    public function setDirPermission($dirPermission)
321    {
322        if ($dirPermission !== false) {
323            if (is_string($dirPermission)) {
324                $dirPermission = octdec($dirPermission);
325            } else {
326                $dirPermission = (int) $dirPermission;
327            }
328
329            // validate
330            if (($dirPermission & 0700) != 0700) {
331                throw new Exception\InvalidArgumentException(
332                    'Invalid directory permission: need permission to execute, read and write by owner'
333                );
334            }
335        }
336
337        $this->dirPermission = $dirPermission;
338        return $this;
339    }
340
341    /**
342     * Gets directory permission
343     *
344     * @return false|int
345     */
346    public function getDirPermission()
347    {
348        return $this->dirPermission;
349    }
350
351    /**
352     * Set umask
353     *
354     * Used by:
355     * - CaptureCache
356     *
357     * @param  false|int $umask
358     * @throws Exception\InvalidArgumentException
359     * @return PatternOptions
360     */
361    public function setUmask($umask)
362    {
363        if ($umask !== false) {
364            if (is_string($umask)) {
365                $umask = octdec($umask);
366            } else {
367                $umask = (int) $umask;
368            }
369
370            // validate
371            if ($umask & 0700) {
372                throw new Exception\InvalidArgumentException(
373                    'Invalid umask: need permission to execute, read and write by owner'
374                );
375            }
376
377            // normalize
378            $umask = $umask & ~0002;
379        }
380
381        $this->umask = $umask;
382        return $this;
383    }
384
385    /**
386     * Get umask
387     *
388     * Used by:
389     * - CaptureCache
390     *
391     * @return false|int
392     */
393    public function getUmask()
394    {
395        return $this->umask;
396    }
397
398    /**
399     * Set whether or not file locking should be used
400     *
401     * Used by:
402     * - CaptureCache
403     *
404     * @param  bool $fileLocking
405     * @return PatternOptions
406     */
407    public function setFileLocking($fileLocking)
408    {
409        $this->fileLocking = (bool) $fileLocking;
410        return $this;
411    }
412
413    /**
414     * Is file locking enabled?
415     *
416     * Used by:
417     * - CaptureCache
418     *
419     * @return bool
420     */
421    public function getFileLocking()
422    {
423        return $this->fileLocking;
424    }
425
426    /**
427     * Set file permission
428     *
429     * @param  false|int $filePermission
430     * @throws Exception\InvalidArgumentException
431     * @return PatternOptions
432     */
433    public function setFilePermission($filePermission)
434    {
435        if ($filePermission !== false) {
436            if (is_string($filePermission)) {
437                $filePermission = octdec($filePermission);
438            } else {
439                $filePermission = (int) $filePermission;
440            }
441
442            // validate
443            if (($filePermission & 0600) != 0600) {
444                throw new Exception\InvalidArgumentException(
445                    'Invalid file permission: need permission to read and write by owner'
446                );
447            } elseif ($filePermission & 0111) {
448                throw new Exception\InvalidArgumentException(
449                    "Invalid file permission: Files shoudn't be executable"
450                );
451            }
452        }
453
454        $this->filePermission = $filePermission;
455        return $this;
456    }
457
458    /**
459     * Gets file permission
460     *
461     * @return false|int
462     */
463    public function getFilePermission()
464    {
465        return $this->filePermission;
466    }
467
468    /**
469     * Set value for index filename
470     *
471     * @param  string $indexFilename
472     * @return PatternOptions
473     */
474    public function setIndexFilename($indexFilename)
475    {
476        $this->indexFilename = (string) $indexFilename;
477        return $this;
478    }
479
480    /**
481     * Get value for index filename
482     *
483     * @return string
484     */
485    public function getIndexFilename()
486    {
487        return $this->indexFilename;
488    }
489
490    /**
491     * Set object to cache
492     *
493     * @param  mixed $object
494     * @throws Exception\InvalidArgumentException
495     * @return PatternOptions
496     */
497    public function setObject($object)
498    {
499        if (!is_object($object)) {
500            throw new Exception\InvalidArgumentException(
501                sprintf('%s expects an object; received "%s"', __METHOD__, gettype($object))
502            );
503        }
504        $this->object = $object;
505        return $this;
506    }
507
508    /**
509     * Get object to cache
510     *
511     * @return null|object
512     */
513    public function getObject()
514    {
515        return $this->object;
516    }
517
518    /**
519     * Set flag indicating whether or not to cache magic properties
520     *
521     * Used by:
522     * - ObjectCache
523     *
524     * @param  bool $objectCacheMagicProperties
525     * @return PatternOptions
526     */
527    public function setObjectCacheMagicProperties($objectCacheMagicProperties)
528    {
529        $this->objectCacheMagicProperties = (bool) $objectCacheMagicProperties;
530        return $this;
531    }
532
533    /**
534     * Should we cache magic properties?
535     *
536     * Used by:
537     * - ObjectCache
538     *
539     * @return bool
540     */
541    public function getObjectCacheMagicProperties()
542    {
543        return $this->objectCacheMagicProperties;
544    }
545
546    /**
547     * Set list of object methods for which to cache return values
548     *
549     * @param  array $objectCacheMethods
550     * @return PatternOptions
551     * @throws Exception\InvalidArgumentException
552     */
553    public function setObjectCacheMethods(array $objectCacheMethods)
554    {
555        $this->objectCacheMethods = $this->normalizeObjectMethods($objectCacheMethods);
556        return $this;
557    }
558
559    /**
560     * Get list of object methods for which to cache return values
561     *
562     * @return array
563     */
564    public function getObjectCacheMethods()
565    {
566        return $this->objectCacheMethods;
567    }
568
569    /**
570     * Set the object key part.
571     *
572     * Used to generate a callback key in order to speed up key generation.
573     *
574     * Used by:
575     * - ObjectCache
576     *
577     * @param  null|string $objectKey The object key or NULL to use the objects class name
578     * @return PatternOptions
579     */
580    public function setObjectKey($objectKey)
581    {
582        if ($objectKey !== null) {
583            $this->objectKey = (string) $objectKey;
584        } else {
585            $this->objectKey = null;
586        }
587        return $this;
588    }
589
590    /**
591     * Get object key
592     *
593     * Used by:
594     * - ObjectCache
595     *
596     * @return string
597     */
598    public function getObjectKey()
599    {
600        if ($this->objectKey === null) {
601            return get_class($this->getObject());
602        }
603        return $this->objectKey;
604    }
605
606    /**
607     * Set list of object methods for which NOT to cache return values
608     *
609     * @param  array $objectNonCacheMethods
610     * @return PatternOptions
611     * @throws Exception\InvalidArgumentException
612     */
613    public function setObjectNonCacheMethods(array $objectNonCacheMethods)
614    {
615        $this->objectNonCacheMethods = $this->normalizeObjectMethods($objectNonCacheMethods);
616        return $this;
617    }
618
619    /**
620     * Get list of object methods for which NOT to cache return values
621     *
622     * @return array
623     */
624    public function getObjectNonCacheMethods()
625    {
626        return $this->objectNonCacheMethods;
627    }
628
629    /**
630     * Set location of public directory
631     *
632     * Used by:
633     * - CaptureCache
634     *
635     * @param  string $publicDir
636     * @throws Exception\InvalidArgumentException
637     * @return PatternOptions
638     */
639    public function setPublicDir($publicDir)
640    {
641        $publicDir = (string) $publicDir;
642
643        if (!is_dir($publicDir)) {
644            throw new Exception\InvalidArgumentException(
645                "Public directory '{$publicDir}' not found or not a directory"
646            );
647        } elseif (!is_writable($publicDir)) {
648            throw new Exception\InvalidArgumentException(
649                "Public directory '{$publicDir}' not writable"
650            );
651        } elseif (!is_readable($publicDir)) {
652            throw new Exception\InvalidArgumentException(
653                "Public directory '{$publicDir}' not readable"
654            );
655        }
656
657        $this->publicDir = rtrim(realpath($publicDir), DIRECTORY_SEPARATOR);
658        return $this;
659    }
660
661    /**
662     * Get location of public directory
663     *
664     * Used by:
665     * - CaptureCache
666     *
667     * @return null|string
668     */
669    public function getPublicDir()
670    {
671        return $this->publicDir;
672    }
673
674    /**
675     * Set storage adapter
676     *
677     * Required for the following Pattern classes:
678     * - CallbackCache
679     * - ClassCache
680     * - ObjectCache
681     * - OutputCache
682     *
683     * @param  string|array|Storage $storage
684     * @return PatternOptions
685     */
686    public function setStorage($storage)
687    {
688        $this->storage = $this->storageFactory($storage);
689        return $this;
690    }
691
692    /**
693     * Get storage adapter
694     *
695     * Used by:
696     * - CallbackCache
697     * - ClassCache
698     * - ObjectCache
699     * - OutputCache
700     *
701     * @return null|Storage
702     */
703    public function getStorage()
704    {
705        return $this->storage;
706    }
707
708    /**
709     * Recursively apply strtolower on all values of an array, and return as a
710     * list of unique values
711     *
712     * @param  array $array
713     * @return array
714     */
715    protected function recursiveStrtolower(array $array)
716    {
717        return array_values(array_unique(array_map('strtolower', $array)));
718    }
719
720    /**
721     * Normalize object methods
722     *
723     * Recursively casts values to lowercase, then determines if any are in a
724     * list of methods not handled, raising an exception if so.
725     *
726     * @param  array $methods
727     * @return array
728     * @throws Exception\InvalidArgumentException
729     */
730    protected function normalizeObjectMethods(array $methods)
731    {
732        $methods   = $this->recursiveStrtolower($methods);
733        $intersect = array_intersect(array('__set', '__get', '__unset', '__isset'), $methods);
734        if (!empty($intersect)) {
735            throw new Exception\InvalidArgumentException(
736                "Magic properties are handled by option 'cache_magic_properties'"
737            );
738        }
739        return $methods;
740    }
741
742    /**
743     * Create a storage object from a given specification
744     *
745     * @param  array|string|Storage $storage
746     * @throws Exception\InvalidArgumentException
747     * @return Storage
748     */
749    protected function storageFactory($storage)
750    {
751        if (is_array($storage)) {
752            $storage = StorageFactory::factory($storage);
753        } elseif (is_string($storage)) {
754            $storage = StorageFactory::adapterFactory($storage);
755        } elseif (!($storage instanceof Storage)) {
756            throw new Exception\InvalidArgumentException(
757                'The storage must be an instanceof Zend\Cache\Storage\StorageInterface '
758                . 'or an array passed to Zend\Cache\Storage::factory '
759                . 'or simply the name of the storage adapter'
760            );
761        }
762
763        return $storage;
764    }
765}
766