Pluf Framework

Sign in or create your account | Project List | Help

Pluf Framework Git Source Tree

Root/src/Pluf/Form.php

1<?php
2/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3/*
4# ***** BEGIN LICENSE BLOCK *****
5# This file is part of Plume Framework, a simple PHP Application Framework.
6# Copyright (C) 2001-2007 Loic d'Anterroches and contributors.
7#
8# Plume Framework is free software; you can redistribute it and/or modify
9# it under the terms of the GNU Lesser General Public License as published by
10# the Free Software Foundation; either version 2.1 of the License, or
11# (at your option) any later version.
12#
13# Plume Framework is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Lesser General Public License for more details.
17#
18# You should have received a copy of the GNU Lesser General Public License
19# along with this program; if not, write to the Free Software
20# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21#
22# ***** END LICENSE BLOCK ***** */
23
24/**
25 * Form validation class.
26 *
27 * This class is used to generate a form. You basically build it the
28 * same way you build a model.
29 *
30 * The form handling is heavily inspired by the Django form handling.
31 *
32 */
33class Pluf_Form implements Iterator, ArrayAccess
34{
35    /**
36     * The fields of the form.
37     *
38     * They are the fully populated Pluf_Form_Field_* of the form. You
39     * define them in the initFields method.
40     */
41    public $fields = array();
42
43    /**
44     * Prefix for the names of the fields.
45     */
46    public $prefix = '';
47    public $id_fields = 'id_%s';
48    public $data = array();
49    public $cleaned_data = array();
50    public $errors = array();
51    public $is_bound = false;
52    public $f = null;
53    public $label_suffix = ':';
54
55    protected $is_valid = null;
56
57    function __construct($data=null, $extra=array(), $label_suffix=null)
58    {
59        if ($data !== null) {
60            $this->data = $data;
61            $this->is_bound = true;
62        }
63        if ($label_suffix !== null) $this->label_suffix = $label_suffix;
64
65        $this->initFields($extra);
66        $this->f = new Pluf_Form_FieldProxy($this);
67    }
68
69    function initFields($extra=array())
70    {
71        throw new Exception('Definition of the fields not implemented.');
72    }
73
74    /**
75     * Add the prefix to the form names.
76     *
77     * @param string Field name.
78     * @return string Field name or field name with form prefix.
79     */
80    function addPrefix($field_name)
81    {
82        if ('' !== $this->prefix) {
83            return $this->prefix.'-'.$field_name;
84        }
85        return $field_name;
86    }
87
88    /**
89     * Check if the form is valid.
90     *
91     * It is also encoding the data in the form to be then saved. It
92     * is very simple as it leaves the work to the field. It means
93     * that you can easily extend this form class to have a more
94     * complex validation procedure like checking if a field is equals
95     * to another in the form (like for password confirmation) etc.
96     *
97     * @param array Associative array of the request
98     * @return array Array of errors
99     */
100    function isValid()
101    {
102        if ($this->is_valid !== null) {
103            return $this->is_valid;
104        }
105        $this->cleaned_data = array();
106        $this->errors = array();
107        $form_methods = get_class_methods($this);
108        foreach ($this->fields as $name=>$field) {
109            $value = $field->widget->valueFromFormData($this->addPrefix($name),
110                                                       $this->data);
111            try {
112                $value = $field->clean($value);
113                $this->cleaned_data[$name] = $value;
114                if (in_array('clean_'.$name, $form_methods)) {
115                    $m = 'clean_'.$name;
116                    $value = $this->$m();
117                    $this->cleaned_data[$name] = $value;
118                }
119            } catch (Pluf_Form_Invalid $e) {
120                if (!isset($this->errors[$name])) $this->errors[$name] = array();
121                $this->errors[$name][] = $e->getMessage();
122                if (isset($this->cleaned_data[$name])) {
123                    unset($this->cleaned_data[$name]);
124                }
125            }
126        }
127        if (empty($this->errors)) {
128            try {
129                $this->cleaned_data = $this->clean();
130            } catch (Pluf_Form_Invalid $e) {
131                if (!isset($this->errors['__all__'])) $this->errors['__all__'] = array();
132                $this->errors['__all__'][] = $e->getMessage();
133            }
134        }
135        if (empty($this->errors)) {
136            $this->is_valid = true;
137            return true;
138        }
139        // as some errors, we do not have cleaned data available.
140        $this->failed();
141        $this->cleaned_data = array();
142        $this->is_valid = false;
143        return false;
144    }
145
146    /**
147     * Form wide cleaning function. That way you can check that if an
148     * input is given, then another one somewhere is also given,
149     * etc. If the cleaning is not ok, your method must throw a
150     * Pluf_Form_Invalid exception.
151     *
152     * @return array Cleaned data.
153     */
154    public function clean()
155    {
156        return $this->cleaned_data;
157    }
158
159    /**
160     * Method just called after the validation if the validation
161     * failed. This can be used to remove uploaded
162     * files. $this->['cleaned_data'] will be available but of course
163     * not fully populated and with possible garbage due to the error.
164     *
165     */
166    public function failed()
167    {
168    }
169
170    /**
171     * Get initial data for a given field.
172     *
173     * @param string Field name.
174     * @return string Initial data or '' of not defined.
175     */
176    public function initial($name)
177    {
178        if (isset($this->fields[$name])) {
179            return $this->fields[$name]->initial;
180        }
181        return '';
182    }
183
184    /**
185     * Get the top errors.
186     */
187    public function render_top_errors()
188    {
189        $top_errors = (isset($this->errors['__all__'])) ? $this->errors['__all__'] : array();
190        array_walk($top_errors, 'Pluf_Form_htmlspecialcharsArray');
191        return new Pluf_Template_SafeString(Pluf_Form_renderErrorsAsHTML($top_errors), true);
192    }
193
194    /**
195     * Get the top errors.
196     */
197    public function get_top_errors()
198    {
199        return (isset($this->errors['__all__'])) ? $this->errors['__all__'] : array();
200    }
201
202    /**
203     * Helper function to render the form.
204     *
205     * See render_p() for a usage example.
206     *
207     * @credit Django Project (http://www.djangoproject.com/)
208     * @param string Normal row.
209     * @param string Error row.
210     * @param string Row ender.
211     * @param string Help text HTML.
212     * @param bool Should we display errors on a separate row.
213     * @return string HTML of the form.
214     */
215    protected function htmlOutput($normal_row, $error_row, $row_ender,
216                                  $help_text_html, $errors_on_separate_row)
217    {
218        $top_errors = (isset($this->errors['__all__'])) ? $this->errors['__all__'] : array();
219        array_walk($top_errors, 'Pluf_Form_htmlspecialcharsArray');
220        $output = array();
221        $hidden_fields = array();
222        foreach ($this->fields as $name=>$field) {
223            $bf = new Pluf_Form_BoundField($this, $field, $name);
224            $bf_errors = $bf->errors;
225            array_walk($bf_errors, 'Pluf_Form_htmlspecialcharsArray');
226            if ($field->widget->is_hidden) {
227                foreach ($bf_errors as $_e) {
228                    $top_errors[] = sprintf(__('(Hidden field %1$s) %2$s'),
229                                            $name, $_e);
230                }
231                $hidden_fields[] = $bf; // Not rendered
232            } else {
233                if ($errors_on_separate_row and count($bf_errors)) {
234                    $output[] = sprintf($error_row, Pluf_Form_renderErrorsAsHTML($bf_errors));
235                }
236                if (strlen($bf->label) > 0) {
237                    $label = htmlspecialchars($bf->label, ENT_COMPAT, 'UTF-8');
238                    if ($this->label_suffix) {
239                        if (!in_array(mb_substr($label, -1, 1),
240                                      array(':','?','.','!'))) {
241                            $label .= $this->label_suffix;
242                        }
243                    }
244                    $label = $bf->labelTag($label);
245                } else {
246                    $label = '';
247                }
248                if ($bf->help_text) {
249                    // $bf->help_text can contains HTML and is not
250                    // escaped.
251                    $help_text = sprintf($help_text_html, $bf->help_text);
252                } else {
253                    $help_text = '';
254                }
255                $errors = '';
256                if (!$errors_on_separate_row and count($bf_errors)) {
257                    $errors = Pluf_Form_renderErrorsAsHTML($bf_errors);
258                }
259                $output[] = sprintf($normal_row, $errors, $label,
260                                    $bf->render_w(), $help_text);
261            }
262        }
263        if (count($top_errors)) {
264            $errors = sprintf($error_row,
265                              Pluf_Form_renderErrorsAsHTML($top_errors));
266            array_unshift($output, $errors);
267        }
268        if (count($hidden_fields)) {
269            $_tmp = '';
270            foreach ($hidden_fields as $hd) {
271                $_tmp .= $hd->render_w();
272            }
273            if (count($output)) {
274                $last_row = array_pop($output);
275                $last_row = substr($last_row, 0, -strlen($row_ender)).$_tmp
276                    .$row_ender;
277                $output[] = $last_row;
278            } else {
279                $output[] = $_tmp;
280            }
281
282        }
283        return new Pluf_Template_SafeString(implode("\n", $output), true);
284    }
285
286    /**
287     * Render the form as a list of paragraphs.
288     */
289    public function render_p()
290    {
291        return $this->htmlOutput('<p>%1$s%2$s %3$s%4$s</p>', '%s', '</p>',
292                                 ' %s', true);
293    }
294
295    /**
296     * Render the form as a list without the <ul></ul>.
297     */
298    public function render_ul()
299    {
300        return $this->htmlOutput('<li>%1$s%2$s %3$s%4$s</li>', '<li>%s</li>',
301                                 '</li>', ' %s', false);
302    }
303
304    /**
305     * Render the form as a table without <table></table>.
306     */
307    public function render_table()
308    {
309        return $this->htmlOutput('<tr><th>%2$s</th><td>%1$s%3$s%4$s</td></tr>',
310                                 '<tr><td colspan="2">%s</td></tr>',
311                                 '</td></tr>', '<br /><span class="helptext">%s</span>', false);
312    }
313
314    /**
315     * Overloading of the get method.
316     *
317     * The overloading is to be able to use property call in the
318     * templates.
319     */
320    function __get($prop)
321    {
322        if (!in_array($prop, array('render_p', 'render_ul', 'render_table', 'render_top_errors', 'get_top_errors'))) {
323            return $this->$prop;
324        }
325        return $this->$prop();
326    }
327
328    /**
329     * Get a given field by key.
330     */
331    public function field($key)
332    {
333        return new Pluf_Form_BoundField($this, $this->fields[$key], $key);
334
335    }
336
337    /**
338     * Iterator method to iterate over the fields.
339     *
340     * Get the current item.
341     */
342     public function current()
343    {
344        $field = current($this->fields);
345        $name = key($this->fields);
346        return new Pluf_Form_BoundField($this, $field, $name);
347    }
348
349     public function key()
350    {
351        return key($this->fields);
352    }
353
354     public function next()
355    {
356        next($this->fields);
357    }
358
359     public function rewind()
360    {
361        reset($this->fields);
362    }
363
364     public function valid()
365    {
366        // We know that the boolean false will not be stored as a
367        // field, so we can test against false to check if valid or
368        // not.
369        return (false !== current($this->fields));
370    }
371
372    public function offsetUnset($index)
373    {
374        unset($this->fields[$index]);
375    }
376 
377    public function offsetSet($index, $value)
378    {
379        $this->fields[$index] = $value;
380    }
381
382    public function offsetGet($index)
383    {
384        if (!isset($this->fields[$index])) {
385            throw new Exception('Undefined index: '.$index);
386        }
387        return $this->fields[$index];
388    }
389
390    public function offsetExists($index)
391    {
392        return (isset($this->fields[$index]));
393    }
394}
395
396
397function Pluf_Form_htmlspecialcharsArray(&$item, $key)
398{
399    $item = htmlspecialchars($item, ENT_COMPAT, 'UTF-8');
400}
401
402function Pluf_Form_renderErrorsAsHTML($errors)
403{
404    $tmp = array();
405    foreach ($errors as $err) {
406        $tmp[] = '<li>'.$err.'</li>';
407    }
408    return '<ul class="errorlist">'.implode("\n", $tmp).'</ul>';
409}
410

Archive Download this file

Branches:
develop
master