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_View
17 * @subpackage Helper
18 * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
19 * @version    $Id$
20 * @license    http://framework.zend.com/license/new-bsd     New BSD License
21 */
22
23/** Zend_View_Helper_Placeholder_Container_Standalone */
24
25/**
26 * Helper for setting and retrieving stylesheets
27 *
28 * @uses       Zend_View_Helper_Placeholder_Container_Standalone
29 * @package    Zend_View
30 * @subpackage Helper
31 * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
32 * @license    http://framework.zend.com/license/new-bsd     New BSD License
33 * @method $this appendStyle($content, array $attributes = array())
34 * @method $this offsetSetStyle($index, $content, array $attributes = array())
35 * @method $this prependStyle($content, array $attributes = array())
36 * @method $this setStyle($content, array $attributes = array())
37 */
38class Zend_View_Helper_HeadStyle extends Zend_View_Helper_Placeholder_Container_Standalone
39{
40    /**
41     * Registry key for placeholder
42     * @var string
43     */
44    protected $_regKey = 'Zend_View_Helper_HeadStyle';
45
46    /**
47     * Allowed optional attributes
48     * @var array
49     */
50    protected $_optionalAttributes = array('lang', 'title', 'media', 'dir');
51
52    /**
53     * Allowed media types
54     * @var array
55     */
56    protected $_mediaTypes = array(
57        'all', 'aural', 'braille', 'handheld', 'print',
58        'projection', 'screen', 'tty', 'tv'
59    );
60
61    /**
62     * Capture type and/or attributes (used for hinting during capture)
63     * @var string
64     */
65    protected $_captureAttrs = null;
66
67    /**
68     * Capture lock
69     * @var bool
70     */
71    protected $_captureLock;
72
73    /**
74     * Capture type (append, prepend, set)
75     * @var string
76     */
77    protected $_captureType;
78
79    /**
80     * Constructor
81     *
82     * Set separator to PHP_EOL.
83     *
84     * @return void
85     */
86    public function __construct()
87    {
88        parent::__construct();
89        $this->setSeparator(PHP_EOL);
90    }
91
92    /**
93     * Return headStyle object
94     *
95     * Returns headStyle helper object; optionally, allows specifying
96     *
97     * @param  string $content Stylesheet contents
98     * @param  string $placement Append, prepend, or set
99     * @param  string|array $attributes Optional attributes to utilize
100     * @return Zend_View_Helper_HeadStyle
101     */
102    public function headStyle($content = null, $placement = 'APPEND', $attributes = array())
103    {
104        if ((null !== $content) && is_string($content)) {
105            switch (strtoupper($placement)) {
106                case 'SET':
107                    $action = 'setStyle';
108                    break;
109                case 'PREPEND':
110                    $action = 'prependStyle';
111                    break;
112                case 'APPEND':
113                default:
114                    $action = 'appendStyle';
115                    break;
116            }
117            $this->$action($content, $attributes);
118        }
119
120        return $this;
121    }
122
123    /**
124     * Overload method calls
125     *
126     * Allows the following method calls:
127     * - appendStyle($content, $attributes = array())
128     * - offsetSetStyle($index, $content, $attributes = array())
129     * - prependStyle($content, $attributes = array())
130     * - setStyle($content, $attributes = array())
131     *
132     * @param  string $method
133     * @param  array $args
134     * @return void
135     * @throws Zend_View_Exception When no $content provided or invalid method
136     */
137    public function __call($method, $args)
138    {
139        if (preg_match('/^(?P<action>set|(ap|pre)pend|offsetSet)(Style)$/', $method, $matches)) {
140            $index  = null;
141            $argc   = count($args);
142            $action = $matches['action'];
143
144            if ('offsetSet' == $action) {
145                if (0 < $argc) {
146                    $index = array_shift($args);
147                    --$argc;
148                }
149            }
150
151            if (1 > $argc) {
152                $e = new Zend_View_Exception(sprintf('Method "%s" requires minimally content for the stylesheet', $method));
153                $e->setView($this->view);
154                throw $e;
155            }
156
157            $content = $args[0];
158            $attrs   = array();
159            if (isset($args[1])) {
160                $attrs = (array) $args[1];
161            }
162
163            $item = $this->createData($content, $attrs);
164
165            if ('offsetSet' == $action) {
166                $this->offsetSet($index, $item);
167            } else {
168                $this->$action($item);
169            }
170
171            return $this;
172        }
173
174        return parent::__call($method, $args);
175    }
176
177    /**
178     * Determine if a value is a valid style tag
179     *
180     * @param  mixed $value
181     * @param  string $method
182     * @return boolean
183     */
184    protected function _isValid($value)
185    {
186        if ((!$value instanceof stdClass)
187            || !isset($value->content)
188            || !isset($value->attributes))
189        {
190            return false;
191        }
192
193        return true;
194    }
195
196    /**
197     * Override append to enforce style creation
198     *
199     * @param  mixed $value
200     * @return void
201     */
202    public function append($value)
203    {
204        if (!$this->_isValid($value)) {
205            $e = new Zend_View_Exception('Invalid value passed to append; please use appendStyle()');
206            $e->setView($this->view);
207            throw $e;
208        }
209
210        return $this->getContainer()->append($value);
211    }
212
213    /**
214     * Override offsetSet to enforce style creation
215     *
216     * @param  string|int $index
217     * @param  mixed $value
218     * @return void
219     */
220    public function offsetSet($index, $value)
221    {
222        if (!$this->_isValid($value)) {
223            $e = new Zend_View_Exception('Invalid value passed to offsetSet; please use offsetSetStyle()');
224            $e->setView($this->view);
225            throw $e;
226        }
227
228        return $this->getContainer()->offsetSet($index, $value);
229    }
230
231    /**
232     * Override prepend to enforce style creation
233     *
234     * @param  mixed $value
235     * @return void
236     */
237    public function prepend($value)
238    {
239        if (!$this->_isValid($value)) {
240            $e = new Zend_View_Exception('Invalid value passed to prepend; please use prependStyle()');
241            $e->setView($this->view);
242            throw $e;
243        }
244
245        return $this->getContainer()->prepend($value);
246    }
247
248    /**
249     * Override set to enforce style creation
250     *
251     * @param  mixed $value
252     * @return void
253     */
254    public function set($value)
255    {
256        if (!$this->_isValid($value)) {
257            $e = new Zend_View_Exception('Invalid value passed to set; please use setStyle()');
258            $e->setView($this->view);
259            throw $e;
260        }
261
262        return $this->getContainer()->set($value);
263    }
264
265    /**
266     * Start capture action
267     *
268     * @param  mixed $captureType
269     * @param  string $typeOrAttrs
270     * @return void
271     */
272    public function captureStart($type = Zend_View_Helper_Placeholder_Container_Abstract::APPEND, $attrs = null)
273    {
274        if ($this->_captureLock) {
275            $e = new Zend_View_Helper_Placeholder_Container_Exception('Cannot nest headStyle captures');
276            $e->setView($this->view);
277            throw $e;
278        }
279
280        $this->_captureLock        = true;
281        $this->_captureAttrs       = $attrs;
282        $this->_captureType        = $type;
283        ob_start();
284    }
285
286    /**
287     * End capture action and store
288     *
289     * @return void
290     */
291    public function captureEnd()
292    {
293        $content             = ob_get_clean();
294        $attrs               = $this->_captureAttrs;
295        $this->_captureAttrs = null;
296        $this->_captureLock  = false;
297
298        switch ($this->_captureType) {
299            case Zend_View_Helper_Placeholder_Container_Abstract::SET:
300                $this->setStyle($content, $attrs);
301                break;
302            case Zend_View_Helper_Placeholder_Container_Abstract::PREPEND:
303                $this->prependStyle($content, $attrs);
304                break;
305            case Zend_View_Helper_Placeholder_Container_Abstract::APPEND:
306            default:
307                $this->appendStyle($content, $attrs);
308                break;
309        }
310    }
311
312    /**
313     * Convert content and attributes into valid style tag
314     *
315     * @param  stdClass $item Item to render
316     * @param  string $indent Indentation to use
317     * @return string
318     */
319    public function itemToString(stdClass $item, $indent)
320    {
321        $attrString = '';
322        if (!empty($item->attributes)) {
323            $enc = 'UTF-8';
324            if ($this->view instanceof Zend_View_Interface
325                && method_exists($this->view, 'getEncoding')
326            ) {
327                $enc = $this->view->getEncoding();
328            }
329            foreach ($item->attributes as $key => $value) {
330                if (!in_array($key, $this->_optionalAttributes)) {
331                    continue;
332                }
333                if ('media' == $key) {
334                    if(false === strpos($value, ',')) {
335                        if (!in_array($value, $this->_mediaTypes)) {
336                            continue;
337                        }
338                    } else {
339                        $media_types = explode(',', $value);
340                        $value = '';
341                        foreach($media_types as $type) {
342                            $type = trim($type);
343                            if (!in_array($type, $this->_mediaTypes)) {
344                                continue;
345                            }
346                            $value .= $type .',';
347                        }
348                        $value = substr($value, 0, -1);
349                    }
350                }
351                $attrString .= sprintf(' %s="%s"', $key, htmlspecialchars($value, ENT_COMPAT, $enc));
352            }
353        }
354
355        $escapeStart = $indent . '<!--'. PHP_EOL;
356        $escapeEnd = $indent . '-->'. PHP_EOL;
357        if (isset($item->attributes['conditional'])
358            && !empty($item->attributes['conditional'])
359            && is_string($item->attributes['conditional'])
360        ) {
361            $escapeStart = null;
362            $escapeEnd = null;
363        }
364
365        $html = '<style type="text/css"' . $attrString . '>' . PHP_EOL
366              . $escapeStart . $indent . $item->content . PHP_EOL . $escapeEnd
367              . '</style>';
368
369        if (null == $escapeStart && null == $escapeEnd) {
370            if (str_replace(' ', '', $item->attributes['conditional']) === '!IE') {
371                $html = '<!-->' . $html . '<!--';
372            }
373            $html = '<!--[if ' . $item->attributes['conditional'] . ']>' . $html . '<![endif]-->';
374        }
375
376        return $html;
377    }
378
379    /**
380     * Create string representation of placeholder
381     *
382     * @param  string|int $indent
383     * @return string
384     */
385    public function toString($indent = null)
386    {
387        $indent = (null !== $indent)
388                ? $this->getWhitespace($indent)
389                : $this->getIndent();
390
391        $items = array();
392        $this->getContainer()->ksort();
393        foreach ($this as $item) {
394            if (!$this->_isValid($item)) {
395                continue;
396            }
397            $items[] = $this->itemToString($item, $indent);
398        }
399
400        $return = $indent . implode($this->getSeparator() . $indent, $items);
401        $return = preg_replace("/(\r\n?|\n)/", '$1' . $indent, $return);
402        return $return;
403    }
404
405    /**
406     * Create data item for use in stack
407     *
408     * @param  string $content
409     * @param  array $attributes
410     * @return stdClass
411     */
412    public function createData($content, array $attributes)
413    {
414        if (!isset($attributes['media'])) {
415            $attributes['media'] = 'screen';
416        } else if(is_array($attributes['media'])) {
417            $attributes['media'] = implode(',', $attributes['media']);
418        }
419
420        $data = new stdClass();
421        $data->content    = $content;
422        $data->attributes = $attributes;
423
424        return $data;
425    }
426}
427