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 Zend\Form\Element\Button;
13use Zend\Form\Element\MonthSelect;
14use Zend\Form\Element\Captcha;
15use Zend\Form\ElementInterface;
16use Zend\Form\Exception;
17use Zend\Form\LabelAwareInterface;
18
19class FormRow extends AbstractHelper
20{
21    const LABEL_APPEND  = 'append';
22    const LABEL_PREPEND = 'prepend';
23
24    /**
25     * The class that is added to element that have errors
26     *
27     * @var string
28     */
29    protected $inputErrorClass = 'input-error';
30
31    /**
32     * The attributes for the row label
33     *
34     * @var array
35     */
36    protected $labelAttributes;
37
38    /**
39     * Where will be label rendered?
40     *
41     * @var string
42     */
43    protected $labelPosition = self::LABEL_PREPEND;
44
45    /**
46     * Are the errors are rendered by this helper?
47     *
48     * @var bool
49     */
50    protected $renderErrors = true;
51
52    /**
53     * Form label helper instance
54     *
55     * @var FormLabel
56     */
57    protected $labelHelper;
58
59    /**
60     * Form element helper instance
61     *
62     * @var FormElement
63     */
64    protected $elementHelper;
65
66    /**
67     * Form element errors helper instance
68     *
69     * @var FormElementErrors
70     */
71    protected $elementErrorsHelper;
72
73    /**
74     * @var string
75     */
76    protected $partial;
77
78    /**
79     * Invoke helper as functor
80     *
81     * Proxies to {@link render()}.
82     *
83     * @param  null|ElementInterface $element
84     * @param  null|string           $labelPosition
85     * @param  bool                  $renderErrors
86     * @param  string|null           $partial
87     * @return string|FormRow
88     */
89    public function __invoke(ElementInterface $element = null, $labelPosition = null, $renderErrors = null, $partial = null)
90    {
91        if (!$element) {
92            return $this;
93        }
94
95        if (is_null($labelPosition)) {
96            $labelPosition = $this->getLabelPosition();
97        }
98
99        if ($renderErrors !== null) {
100            $this->setRenderErrors($renderErrors);
101        }
102
103        if ($partial !== null) {
104            $this->setPartial($partial);
105        }
106
107        return $this->render($element, $labelPosition);
108    }
109
110    /**
111     * Utility form helper that renders a label (if it exists), an element and errors
112     *
113     * @param  ElementInterface $element
114     * @param  null|string      $labelPosition
115     * @throws \Zend\Form\Exception\DomainException
116     * @return string
117     */
118    public function render(ElementInterface $element, $labelPosition = null)
119    {
120        $escapeHtmlHelper    = $this->getEscapeHtmlHelper();
121        $labelHelper         = $this->getLabelHelper();
122        $elementHelper       = $this->getElementHelper();
123        $elementErrorsHelper = $this->getElementErrorsHelper();
124
125        $label           = $element->getLabel();
126        $inputErrorClass = $this->getInputErrorClass();
127
128        if (is_null($labelPosition)) {
129            $labelPosition = $this->labelPosition;
130        }
131
132        if (isset($label) && '' !== $label) {
133            // Translate the label
134            if (null !== ($translator = $this->getTranslator())) {
135                $label = $translator->translate($label, $this->getTranslatorTextDomain());
136            }
137        }
138
139        // Does this element have errors ?
140        if (count($element->getMessages()) > 0 && !empty($inputErrorClass)) {
141            $classAttributes = ($element->hasAttribute('class') ? $element->getAttribute('class') . ' ' : '');
142            $classAttributes = $classAttributes . $inputErrorClass;
143
144            $element->setAttribute('class', $classAttributes);
145        }
146
147        if ($this->partial) {
148            $vars = array(
149                'element'           => $element,
150                'label'             => $label,
151                'labelAttributes'   => $this->labelAttributes,
152                'labelPosition'     => $labelPosition,
153                'renderErrors'      => $this->renderErrors,
154            );
155
156            return $this->view->render($this->partial, $vars);
157        }
158
159        if ($this->renderErrors) {
160            $elementErrors = $elementErrorsHelper->render($element);
161        }
162
163        $elementString = $elementHelper->render($element);
164
165        // hidden elements do not need a <label> -https://github.com/zendframework/zf2/issues/5607
166        $type = $element->getAttribute('type');
167        if (isset($label) && '' !== $label && $type !== 'hidden') {
168            $labelAttributes = array();
169
170            if ($element instanceof LabelAwareInterface) {
171                $labelAttributes = $element->getLabelAttributes();
172            }
173
174            if (! $element instanceof LabelAwareInterface || ! $element->getLabelOption('disable_html_escape')) {
175                $label = $escapeHtmlHelper($label);
176            }
177
178            if (empty($labelAttributes)) {
179                $labelAttributes = $this->labelAttributes;
180            }
181
182            // Multicheckbox elements have to be handled differently as the HTML standard does not allow nested
183            // labels. The semantic way is to group them inside a fieldset
184            if ($type === 'multi_checkbox'
185                || $type === 'radio'
186                || $element instanceof MonthSelect
187                || $element instanceof Captcha
188            ) {
189                $markup = sprintf(
190                    '<fieldset><legend>%s</legend>%s</fieldset>',
191                    $label,
192                    $elementString
193                );
194            } else {
195                // Ensure element and label will be separated if element has an `id`-attribute.
196                // If element has label option `always_wrap` it will be nested in any case.
197                if ($element->hasAttribute('id')
198                    && ($element instanceof LabelAwareInterface && !$element->getLabelOption('always_wrap'))
199                ) {
200                    $labelOpen = '';
201                    $labelClose = '';
202                    $label = $labelHelper($element);
203                } else {
204                    $labelOpen  = $labelHelper->openTag($labelAttributes);
205                    $labelClose = $labelHelper->closeTag();
206                }
207
208                if ($label !== '' && (!$element->hasAttribute('id'))
209                    || ($element instanceof LabelAwareInterface && $element->getLabelOption('always_wrap'))
210                ) {
211                    $label = '<span>' . $label . '</span>';
212                }
213
214                // Button element is a special case, because label is always rendered inside it
215                if ($element instanceof Button) {
216                    $labelOpen = $labelClose = $label = '';
217                }
218
219                if ($element instanceof LabelAwareInterface && $element->getLabelOption('label_position')) {
220                    $labelPosition = $element->getLabelOption('label_position');
221                }
222
223                switch ($labelPosition) {
224                    case self::LABEL_PREPEND:
225                        $markup = $labelOpen . $label . $elementString . $labelClose;
226                        break;
227                    case self::LABEL_APPEND:
228                    default:
229                        $markup = $labelOpen . $elementString . $label . $labelClose;
230                        break;
231                }
232            }
233
234            if ($this->renderErrors) {
235                $markup .= $elementErrors;
236            }
237        } else {
238            if ($this->renderErrors) {
239                $markup = $elementString . $elementErrors;
240            } else {
241                $markup = $elementString;
242            }
243        }
244
245        return $markup;
246    }
247
248    /**
249     * Set the class that is added to element that have errors
250     *
251     * @param  string $inputErrorClass
252     * @return FormRow
253     */
254    public function setInputErrorClass($inputErrorClass)
255    {
256        $this->inputErrorClass = $inputErrorClass;
257        return $this;
258    }
259
260    /**
261     * Get the class that is added to element that have errors
262     *
263     * @return string
264     */
265    public function getInputErrorClass()
266    {
267        return $this->inputErrorClass;
268    }
269
270    /**
271     * Set the attributes for the row label
272     *
273     * @param  array $labelAttributes
274     * @return FormRow
275     */
276    public function setLabelAttributes($labelAttributes)
277    {
278        $this->labelAttributes = $labelAttributes;
279        return $this;
280    }
281
282    /**
283     * Get the attributes for the row label
284     *
285     * @return array
286     */
287    public function getLabelAttributes()
288    {
289        return $this->labelAttributes;
290    }
291
292    /**
293     * Set the label position
294     *
295     * @param  string $labelPosition
296     * @throws \Zend\Form\Exception\InvalidArgumentException
297     * @return FormRow
298     */
299    public function setLabelPosition($labelPosition)
300    {
301        $labelPosition = strtolower($labelPosition);
302        if (!in_array($labelPosition, array(self::LABEL_APPEND, self::LABEL_PREPEND))) {
303            throw new Exception\InvalidArgumentException(sprintf(
304                '%s expects either %s::LABEL_APPEND or %s::LABEL_PREPEND; received "%s"',
305                __METHOD__,
306                __CLASS__,
307                __CLASS__,
308                (string) $labelPosition
309            ));
310        }
311        $this->labelPosition = $labelPosition;
312
313        return $this;
314    }
315
316    /**
317     * Get the label position
318     *
319     * @return string
320     */
321    public function getLabelPosition()
322    {
323        return $this->labelPosition;
324    }
325
326    /**
327     * Set if the errors are rendered by this helper
328     *
329     * @param  bool $renderErrors
330     * @return FormRow
331     */
332    public function setRenderErrors($renderErrors)
333    {
334        $this->renderErrors = (bool) $renderErrors;
335        return $this;
336    }
337
338    /**
339     * Retrieve if the errors are rendered by this helper
340     *
341     * @return bool
342     */
343    public function getRenderErrors()
344    {
345        return $this->renderErrors;
346    }
347
348    /**
349     * Set a partial view script to use for rendering the row
350     *
351     * @param null|string $partial
352     * @return FormRow
353     */
354    public function setPartial($partial)
355    {
356        $this->partial = $partial;
357        return $this;
358    }
359
360    /**
361     * Retrieve current partial
362     *
363     * @return null|string
364     */
365    public function getPartial()
366    {
367        return $this->partial;
368    }
369
370    /**
371     * Retrieve the FormLabel helper
372     *
373     * @return FormLabel
374     */
375    protected function getLabelHelper()
376    {
377        if ($this->labelHelper) {
378            return $this->labelHelper;
379        }
380
381        if (method_exists($this->view, 'plugin')) {
382            $this->labelHelper = $this->view->plugin('form_label');
383        }
384
385        if (!$this->labelHelper instanceof FormLabel) {
386            $this->labelHelper = new FormLabel();
387        }
388
389        if ($this->hasTranslator()) {
390            $this->labelHelper->setTranslator(
391                $this->getTranslator(),
392                $this->getTranslatorTextDomain()
393            );
394        }
395
396        return $this->labelHelper;
397    }
398
399    /**
400     * Retrieve the FormElement helper
401     *
402     * @return FormElement
403     */
404    protected function getElementHelper()
405    {
406        if ($this->elementHelper) {
407            return $this->elementHelper;
408        }
409
410        if (method_exists($this->view, 'plugin')) {
411            $this->elementHelper = $this->view->plugin('form_element');
412        }
413
414        if (!$this->elementHelper instanceof FormElement) {
415            $this->elementHelper = new FormElement();
416        }
417
418        return $this->elementHelper;
419    }
420
421    /**
422     * Retrieve the FormElementErrors helper
423     *
424     * @return FormElementErrors
425     */
426    protected function getElementErrorsHelper()
427    {
428        if ($this->elementErrorsHelper) {
429            return $this->elementErrorsHelper;
430        }
431
432        if (method_exists($this->view, 'plugin')) {
433            $this->elementErrorsHelper = $this->view->plugin('form_element_errors');
434        }
435
436        if (!$this->elementErrorsHelper instanceof FormElementErrors) {
437            $this->elementErrorsHelper = new FormElementErrors();
438        }
439
440        return $this->elementErrorsHelper;
441    }
442}
443