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\Form\View\Helper;
11
12use RuntimeException;
13use Zend\Form\Element;
14use Zend\Form\ElementInterface;
15use Zend\Form\Element\Collection as CollectionElement;
16use Zend\Form\FieldsetInterface;
17use Zend\Form\LabelAwareInterface;
18use Zend\View\Helper\AbstractHelper as BaseAbstractHelper;
19
20class FormCollection extends AbstractHelper
21{
22    /**
23     * If set to true, collections are automatically wrapped around a fieldset
24     *
25     * @var bool
26     */
27    protected $shouldWrap = true;
28
29    /**
30     * This is the default wrapper that the collection is wrapped into
31     *
32     * @var string
33     */
34    protected $wrapper = '<fieldset%4$s>%2$s%1$s%3$s</fieldset>';
35
36    /**
37     * This is the default label-wrapper
38     *
39     * @var string
40     */
41    protected $labelWrapper = '<legend>%s</legend>';
42
43    /**
44     * Where shall the template-data be inserted into
45     *
46     * @var string
47     */
48    protected $templateWrapper = '<span data-template="%s"></span>';
49
50    /**
51     * The name of the default view helper that is used to render sub elements.
52     *
53     * @var string
54     */
55    protected $defaultElementHelper = 'formrow';
56
57    /**
58     * The view helper used to render sub elements.
59     *
60     * @var AbstractHelper
61     */
62    protected $elementHelper;
63
64    /**
65     * The view helper used to render sub fieldsets.
66     *
67     * @var AbstractHelper
68     */
69    protected $fieldsetHelper;
70
71    /**
72     * Invoke helper as function
73     *
74     * Proxies to {@link render()}.
75     *
76     * @param  ElementInterface|null $element
77     * @param  bool                  $wrap
78     * @return string|FormCollection
79     */
80    public function __invoke(ElementInterface $element = null, $wrap = true)
81    {
82        if (!$element) {
83            return $this;
84        }
85
86        $this->setShouldWrap($wrap);
87
88        return $this->render($element);
89    }
90
91    /**
92     * Render a collection by iterating through all fieldsets and elements
93     *
94     * @param  ElementInterface $element
95     * @return string
96     */
97    public function render(ElementInterface $element)
98    {
99        $renderer = $this->getView();
100        if (!method_exists($renderer, 'plugin')) {
101            // Bail early if renderer is not pluggable
102            return '';
103        }
104
105        $markup           = '';
106        $templateMarkup   = '';
107        $elementHelper    = $this->getElementHelper();
108        $fieldsetHelper   = $this->getFieldsetHelper();
109
110        if ($element instanceof CollectionElement && $element->shouldCreateTemplate()) {
111            $templateMarkup = $this->renderTemplate($element);
112        }
113
114        foreach ($element->getIterator() as $elementOrFieldset) {
115            if ($elementOrFieldset instanceof FieldsetInterface) {
116                $markup .= $fieldsetHelper($elementOrFieldset, $this->shouldWrap());
117            } elseif ($elementOrFieldset instanceof ElementInterface) {
118                $markup .= $elementHelper($elementOrFieldset);
119            }
120        }
121
122        // Every collection is wrapped by a fieldset if needed
123        if ($this->shouldWrap) {
124            $attributes = $element->getAttributes();
125            unset($attributes['name']);
126            $attributesString = count($attributes) ? ' ' . $this->createAttributesString($attributes) : '';
127
128            $label = $element->getLabel();
129            $legend = '';
130
131            if (!empty($label)) {
132                if (null !== ($translator = $this->getTranslator())) {
133                    $label = $translator->translate(
134                        $label,
135                        $this->getTranslatorTextDomain()
136                    );
137                }
138
139                if (! $element instanceof LabelAwareInterface || ! $element->getLabelOption('disable_html_escape')) {
140                    $escapeHtmlHelper = $this->getEscapeHtmlHelper();
141                    $label = $escapeHtmlHelper($label);
142                }
143
144                $legend = sprintf(
145                    $this->labelWrapper,
146                    $label
147                );
148            }
149
150            $markup = sprintf(
151                $this->wrapper,
152                $markup,
153                $legend,
154                $templateMarkup,
155                $attributesString
156            );
157        } else {
158            $markup .= $templateMarkup;
159        }
160
161        return $markup;
162    }
163
164    /**
165     * Only render a template
166     *
167     * @param  CollectionElement $collection
168     * @return string
169     */
170    public function renderTemplate(CollectionElement $collection)
171    {
172        $elementHelper          = $this->getElementHelper();
173        $escapeHtmlAttribHelper = $this->getEscapeHtmlAttrHelper();
174        $fieldsetHelper         = $this->getFieldsetHelper();
175
176        $templateMarkup         = '';
177
178        $elementOrFieldset = $collection->getTemplateElement();
179
180        if ($elementOrFieldset instanceof FieldsetInterface) {
181            $templateMarkup .= $fieldsetHelper($elementOrFieldset, $this->shouldWrap());
182        } elseif ($elementOrFieldset instanceof ElementInterface) {
183            $templateMarkup .= $elementHelper($elementOrFieldset);
184        }
185
186        return sprintf(
187            $this->getTemplateWrapper(),
188            $escapeHtmlAttribHelper($templateMarkup)
189        );
190    }
191
192    /**
193     * If set to true, collections are automatically wrapped around a fieldset
194     *
195     * @param  bool $wrap
196     * @return FormCollection
197     */
198    public function setShouldWrap($wrap)
199    {
200        $this->shouldWrap = (bool) $wrap;
201        return $this;
202    }
203
204    /**
205     * Get wrapped
206     *
207     * @return bool
208     */
209    public function shouldWrap()
210    {
211        return $this->shouldWrap;
212    }
213
214    /**
215     * Sets the name of the view helper that should be used to render sub elements.
216     *
217     * @param  string $defaultSubHelper The name of the view helper to set.
218     * @return FormCollection
219     */
220    public function setDefaultElementHelper($defaultSubHelper)
221    {
222        $this->defaultElementHelper = $defaultSubHelper;
223        return $this;
224    }
225
226    /**
227     * Gets the name of the view helper that should be used to render sub elements.
228     *
229     * @return string
230     */
231    public function getDefaultElementHelper()
232    {
233        return $this->defaultElementHelper;
234    }
235
236    /**
237     * Sets the element helper that should be used by this collection.
238     *
239     * @param  AbstractHelper $elementHelper The element helper to use.
240     * @return FormCollection
241     */
242    public function setElementHelper(AbstractHelper $elementHelper)
243    {
244        $this->elementHelper = $elementHelper;
245        return $this;
246    }
247
248    /**
249     * Retrieve the element helper.
250     *
251     * @return AbstractHelper
252     * @throws RuntimeException
253     */
254    protected function getElementHelper()
255    {
256        if ($this->elementHelper) {
257            return $this->elementHelper;
258        }
259
260        if (method_exists($this->view, 'plugin')) {
261            $this->elementHelper = $this->view->plugin($this->getDefaultElementHelper());
262        }
263
264        if (!$this->elementHelper instanceof BaseAbstractHelper) {
265            // @todo Ideally the helper should implement an interface.
266            throw new RuntimeException('Invalid element helper set in FormCollection. The helper must be an instance of AbstractHelper.');
267        }
268
269        return $this->elementHelper;
270    }
271
272    /**
273     * Sets the fieldset helper that should be used by this collection.
274     *
275     * @param  AbstractHelper $fieldsetHelper The fieldset helper to use.
276     * @return FormCollection
277     */
278    public function setFieldsetHelper(AbstractHelper $fieldsetHelper)
279    {
280        $this->fieldsetHelper = $fieldsetHelper;
281        return $this;
282    }
283
284    /**
285     * Retrieve the fieldset helper.
286     *
287     * @return FormCollection
288     */
289    protected function getFieldsetHelper()
290    {
291        if ($this->fieldsetHelper) {
292            return $this->fieldsetHelper;
293        }
294
295        return $this;
296    }
297
298    /**
299     * Get the wrapper for the collection
300     *
301     * @return string
302     */
303    public function getWrapper()
304    {
305        return $this->wrapper;
306    }
307
308    /**
309     * Set the wrapper for this collection
310     *
311     * The string given will be passed through sprintf with the following three
312     * replacements:
313     *
314     * 1. The content of the collection
315     * 2. The label of the collection. If no label is given this will be an empty
316     *   string
317     * 3. The template span-tag. This might also be an empty string
318     *
319     * The preset default is <pre><fieldset>%2$s%1$s%3$s</fieldset></pre>
320     *
321     * @param string $wrapper
322     *
323     * @return self
324     */
325    public function setWrapper($wrapper)
326    {
327        $this->wrapper = $wrapper;
328
329        return $this;
330    }
331
332    /**
333     * Set the label-wrapper
334     * The string will be passed through sprintf with the label as single
335     * parameter
336     * This defaults to '<legend>%s</legend>'
337     *
338     * @param string $labelWrapper
339     *
340     * @return self
341     */
342    public function setLabelWrapper($labelWrapper)
343    {
344        $this->labelWrapper = $labelWrapper;
345
346        return $this;
347    }
348
349    /**
350     * Get the wrapper for the label
351     *
352     * @return string
353     */
354    public function getLabelWrapper()
355    {
356        return $this->labelWrapper;
357    }
358
359    /**
360     * Ge the wrapper for the template
361     *
362     * @return string
363     */
364    public function getTemplateWrapper()
365    {
366        return $this->templateWrapper;
367    }
368
369    /**
370     * Set the string where the template will be inserted into
371     *
372     * This string will be passed through sprintf and has the template as single
373     * parameter
374     *
375     * THis defaults to '<span data-template="%s"></span>'
376     *
377     * @param string $templateWrapper
378     *
379     * @return self
380     */
381    public function setTemplateWrapper($templateWrapper)
382    {
383        $this->templateWrapper = $templateWrapper;
384
385        return $this;
386    }
387}
388