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\View\Helper\Placeholder\Container;
11
12use ArrayAccess;
13use Countable;
14use IteratorAggregate;
15use Zend\Escaper\Escaper;
16use Zend\View\Exception;
17use Zend\View\Helper\AbstractHelper;
18use Zend\View\Renderer\RendererInterface;
19
20/**
21 * Base class for targeted placeholder helpers
22 */
23abstract class AbstractStandalone extends AbstractHelper implements
24    IteratorAggregate,
25    Countable,
26    ArrayAccess
27{
28    /**
29     * Flag whether to automatically escape output, must also be
30     * enforced in the child class if __toString/toString is overridden
31     *
32     * @var bool
33     */
34    protected $autoEscape = true;
35
36    /**
37     * @var AbstractContainer
38     */
39    protected $container;
40
41    /**
42     * Default container class
43     * @var string
44     */
45    protected $containerClass = 'Zend\View\Helper\Placeholder\Container';
46
47    /**
48     * @var Escaper[]
49     */
50    protected $escapers = array();
51
52    /**
53     * Constructor
54     *
55     */
56    public function __construct()
57    {
58        $this->setContainer($this->getContainer());
59    }
60
61    /**
62     * Overload
63     *
64     * Proxy to container methods
65     *
66     * @param  string $method
67     * @param  array $args
68     * @throws Exception\BadMethodCallException
69     * @return mixed
70     */
71    public function __call($method, $args)
72    {
73        $container = $this->getContainer();
74        if (method_exists($container, $method)) {
75            $return = call_user_func_array(array($container, $method), $args);
76            if ($return === $container) {
77                // If the container is returned, we really want the current object
78                return $this;
79            }
80            return $return;
81        }
82
83        throw new Exception\BadMethodCallException('Method "' . $method . '" does not exist');
84    }
85
86    /**
87     * Overloading: set property value
88     *
89     * @param  string $key
90     * @param  mixed $value
91     * @return void
92     */
93    public function __set($key, $value)
94    {
95        $container = $this->getContainer();
96        $container[$key] = $value;
97    }
98
99    /**
100     * Overloading: retrieve property
101     *
102     * @param  string $key
103     * @return mixed
104     */
105    public function __get($key)
106    {
107        $container = $this->getContainer();
108        if (isset($container[$key])) {
109            return $container[$key];
110        }
111
112        return;
113    }
114
115    /**
116     * Overloading: check if property is set
117     *
118     * @param  string $key
119     * @return bool
120     */
121    public function __isset($key)
122    {
123        $container = $this->getContainer();
124        return isset($container[$key]);
125    }
126
127    /**
128     * Overloading: unset property
129     *
130     * @param  string $key
131     * @return void
132     */
133    public function __unset($key)
134    {
135        $container = $this->getContainer();
136        if (isset($container[$key])) {
137            unset($container[$key]);
138        }
139    }
140
141    /**
142     * Cast to string representation
143     *
144     * @return string
145     */
146    public function __toString()
147    {
148        return $this->toString();
149    }
150
151    /**
152     * String representation
153     *
154     * @return string
155     */
156    public function toString()
157    {
158        return $this->getContainer()->toString();
159    }
160
161    /**
162     * Escape a string
163     *
164     * @param  string $string
165     * @return string
166     */
167    protected function escape($string)
168    {
169        if ($this->getView() instanceof RendererInterface
170            && method_exists($this->getView(), 'getEncoding')
171        ) {
172            $escaper = $this->getView()->plugin('escapeHtml');
173            return $escaper((string) $string);
174        }
175
176        return $this->getEscaper()->escapeHtml((string) $string);
177    }
178
179    /**
180     * Set whether or not auto escaping should be used
181     *
182     * @param  bool $autoEscape whether or not to auto escape output
183     * @return AbstractStandalone
184     */
185    public function setAutoEscape($autoEscape = true)
186    {
187        $this->autoEscape = (bool) $autoEscape;
188        return $this;
189    }
190
191    /**
192     * Return whether autoEscaping is enabled or disabled
193     *
194     * return bool
195     */
196    public function getAutoEscape()
197    {
198        return $this->autoEscape;
199    }
200
201    /**
202     * Set container on which to operate
203     *
204     * @param  AbstractContainer $container
205     * @return AbstractStandalone
206     */
207    public function setContainer(AbstractContainer $container)
208    {
209        $this->container = $container;
210        return $this;
211    }
212
213    /**
214     * Retrieve placeholder container
215     *
216     * @return AbstractContainer
217     */
218    public function getContainer()
219    {
220        if (!$this->container instanceof AbstractContainer) {
221            $this->container = new $this->containerClass();
222        }
223        return $this->container;
224    }
225
226    /**
227     * Delete a container
228     *
229     * @return bool
230     */
231    public function deleteContainer()
232    {
233        if (null != $this->container) {
234            $this->container = null;
235            return true;
236        }
237
238        return false;
239    }
240
241    /**
242     * Set the container class to use
243     *
244     * @param  string $name
245     * @throws Exception\InvalidArgumentException
246     * @throws Exception\DomainException
247     * @return \Zend\View\Helper\Placeholder\Container\AbstractStandalone
248     */
249    public function setContainerClass($name)
250    {
251        if (!class_exists($name)) {
252            throw new Exception\DomainException(
253                sprintf(
254                    '%s expects a valid container class name; received "%s", which did not resolve',
255                    __METHOD__,
256                    $name
257                )
258            );
259        }
260
261        if (!in_array('Zend\View\Helper\Placeholder\Container\AbstractContainer', class_parents($name))) {
262            throw new Exception\InvalidArgumentException('Invalid Container class specified');
263        }
264
265        $this->containerClass = $name;
266        return $this;
267    }
268
269    /**
270     * Retrieve the container class
271     *
272     * @return string
273     */
274    public function getContainerClass()
275    {
276        return $this->containerClass;
277    }
278
279    /**
280     * Set Escaper instance
281     *
282     * @param  Escaper $escaper
283     * @return AbstractStandalone
284     */
285    public function setEscaper(Escaper $escaper)
286    {
287        $encoding = $escaper->getEncoding();
288        $this->escapers[$encoding] = $escaper;
289
290        return $this;
291    }
292
293    /**
294     * Get Escaper instance
295     *
296     * Lazy-loads one if none available
297     *
298     * @param  string|null $enc Encoding to use
299     * @return mixed
300     */
301    public function getEscaper($enc = 'UTF-8')
302    {
303        $enc = strtolower($enc);
304        if (!isset($this->escapers[$enc])) {
305            $this->setEscaper(new Escaper($enc));
306        }
307
308        return $this->escapers[$enc];
309    }
310
311    /**
312     * Countable
313     *
314     * @return int
315     */
316    public function count()
317    {
318        $container = $this->getContainer();
319        return count($container);
320    }
321
322    /**
323     * ArrayAccess: offsetExists
324     *
325     * @param  string|int $offset
326     * @return bool
327     */
328    public function offsetExists($offset)
329    {
330        return $this->getContainer()->offsetExists($offset);
331    }
332
333    /**
334     * ArrayAccess: offsetGet
335     *
336     * @param  string|int $offset
337     * @return mixed
338     */
339    public function offsetGet($offset)
340    {
341        return $this->getContainer()->offsetGet($offset);
342    }
343
344    /**
345     * ArrayAccess: offsetSet
346     *
347     * @param  string|int $offset
348     * @param  mixed $value
349     * @return void
350     */
351    public function offsetSet($offset, $value)
352    {
353        return $this->getContainer()->offsetSet($offset, $value);
354    }
355
356    /**
357     * ArrayAccess: offsetUnset
358     *
359     * @param  string|int $offset
360     * @return void
361     */
362    public function offsetUnset($offset)
363    {
364        return $this->getContainer()->offsetUnset($offset);
365    }
366
367    /**
368     * IteratorAggregate: get Iterator
369     *
370     * @return \Iterator
371     */
372    public function getIterator()
373    {
374        return $this->getContainer()->getIterator();
375    }
376}
377