1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Part of the Joomla Framework Form Package |
4
|
|
|
* |
5
|
|
|
* @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved. |
6
|
|
|
* @license GNU General Public License version 2 or later; see LICENSE |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace Joomla\Form; |
10
|
|
|
|
11
|
|
|
use Joomla\Filter; |
12
|
|
|
use Joomla\Uri\Uri; |
13
|
|
|
use Joomla\Language\Language; |
14
|
|
|
use Joomla\Language\Text; |
15
|
|
|
use Joomla\Filesystem\Path; |
16
|
|
|
use Joomla\Registry\Registry; |
17
|
|
|
use Joomla\Utilities\ArrayHelper; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Form Class for the Joomla Framework. |
21
|
|
|
* |
22
|
|
|
* This class implements a robust API for constructing, populating, filtering, and validating forms. |
23
|
|
|
* It uses XML definitions to construct form fields and a variety of field and rule classes to |
24
|
|
|
* render and validate the form. |
25
|
|
|
* |
26
|
|
|
* @link http://www.w3.org/TR/html4/interact/forms.html |
27
|
|
|
* @link http://www.w3.org/TR/html5/forms.html |
28
|
|
|
* @since 1.0 |
29
|
|
|
* @deprecated The joomla/form package is deprecated |
30
|
|
|
*/ |
31
|
|
|
class Form |
32
|
|
|
{ |
33
|
|
|
/** |
34
|
|
|
* The Registry data store for form fields during display. |
35
|
|
|
* |
36
|
|
|
* @var object |
37
|
|
|
* @since 1.0 |
38
|
|
|
*/ |
39
|
|
|
protected $data; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* The form object errors array. |
43
|
|
|
* |
44
|
|
|
* @var array |
45
|
|
|
* @since 1.0 |
46
|
|
|
*/ |
47
|
|
|
protected $errors = array(); |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* The name of the form instance. |
51
|
|
|
* |
52
|
|
|
* @var string |
53
|
|
|
* @since 1.0 |
54
|
|
|
*/ |
55
|
|
|
protected $name; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* The form object options for use in rendering and validation. |
59
|
|
|
* |
60
|
|
|
* @var array |
61
|
|
|
* @since 1.0 |
62
|
|
|
*/ |
63
|
|
|
protected $options = array(); |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* The form XML definition. |
67
|
|
|
* |
68
|
|
|
* @var \SimpleXMLElement |
69
|
|
|
* @since 1.0 |
70
|
|
|
*/ |
71
|
|
|
protected $xml; |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Form instances. |
75
|
|
|
* |
76
|
|
|
* @var array |
77
|
|
|
* @since 1.0 |
78
|
|
|
*/ |
79
|
|
|
protected static $forms = array(); |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Method to instantiate the form object. |
83
|
|
|
* |
84
|
|
|
* @param string $name The name of the form. |
85
|
|
|
* @param array $options An array of form options. |
86
|
|
|
* |
87
|
|
|
* @since 1.0 |
88
|
|
|
*/ |
89
|
|
|
public function __construct($name, array $options = array()) |
90
|
|
|
{ |
91
|
|
|
// Set the name for the form. |
92
|
|
|
$this->name = $name; |
93
|
|
|
|
94
|
|
|
// Initialise the Registry data. |
95
|
|
|
$this->data = new Registry; |
96
|
|
|
|
97
|
|
|
// Set the options if specified. |
98
|
|
|
$this->options['control'] = isset($options['control']) ? $options['control'] : false; |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Method to bind data to the form. |
103
|
|
|
* |
104
|
|
|
* @param mixed $data An array or object of data to bind to the form. |
105
|
|
|
* |
106
|
|
|
* @return boolean True on success. |
107
|
|
|
* |
108
|
|
|
* @since 1.0 |
109
|
|
|
*/ |
110
|
|
|
public function bind($data) |
111
|
|
|
{ |
112
|
|
|
// Make sure there is a valid Form XML document. |
113
|
|
|
if (!($this->xml instanceof \SimpleXMLElement)) |
114
|
|
|
{ |
115
|
|
|
return false; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
// The data must be an object or array. |
119
|
|
|
if (!is_object($data) && !is_array($data)) |
120
|
|
|
{ |
121
|
|
|
return false; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
// Convert the object to an array. |
125
|
|
|
if ($data instanceof Registry) |
126
|
|
|
{ |
127
|
|
|
$data = $data->toArray(); |
128
|
|
|
} |
129
|
|
|
elseif (is_object($data)) |
130
|
|
|
{ |
131
|
|
|
$data = (array) $data; |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
// Process the input data. |
135
|
|
|
foreach ($data as $k => $v) |
136
|
|
|
{ |
137
|
|
|
if ($this->findField($k)) |
138
|
|
|
{ |
139
|
|
|
// If the field exists set the value. |
140
|
|
|
$this->data->set($k, $v); |
141
|
|
|
} |
142
|
|
|
elseif (is_object($v) || ArrayHelper::isAssociative($v)) |
143
|
|
|
{ |
144
|
|
|
// If the value is an object or an associative array hand it off to the recursive bind level method. |
145
|
|
|
$this->bindLevel($k, $v); |
146
|
|
|
} |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
return true; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Method to bind data to the form for the group level. |
154
|
|
|
* |
155
|
|
|
* @param string $group The dot-separated form group path on which to bind the data. |
156
|
|
|
* @param mixed $data An array or object of data to bind to the form for the group level. |
157
|
|
|
* |
158
|
|
|
* @return void |
159
|
|
|
* |
160
|
|
|
* @since 1.0 |
161
|
|
|
*/ |
162
|
|
|
protected function bindLevel($group, $data) |
163
|
|
|
{ |
164
|
|
|
// Ensure the input data is an array. |
165
|
|
|
settype($data, 'array'); |
166
|
|
|
|
167
|
|
|
// Process the input data. |
168
|
|
|
foreach ($data as $k => $v) |
169
|
|
|
{ |
170
|
|
|
if ($this->findField($k, $group)) |
171
|
|
|
{ |
172
|
|
|
// If the field exists set the value. |
173
|
|
|
$this->data->set($group . '.' . $k, $v); |
174
|
|
|
} |
175
|
|
|
elseif (is_object($v) || ArrayHelper::isAssociative($v)) |
176
|
|
|
{ |
177
|
|
|
// If the value is an object or an associative array, hand it off to the recursive bind level method |
178
|
|
|
$this->bindLevel($group . '.' . $k, $v); |
179
|
|
|
} |
180
|
|
|
} |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* Method to filter the form data. |
185
|
|
|
* |
186
|
|
|
* @param array $data An array of field values to filter. |
187
|
|
|
* @param string $group The dot-separated form group path on which to filter the fields. |
188
|
|
|
* |
189
|
|
|
* @return mixed Array or false. |
190
|
|
|
* |
191
|
|
|
* @since 1.0 |
192
|
|
|
*/ |
193
|
|
|
public function filter($data, $group = null) |
194
|
|
|
{ |
195
|
|
|
// Make sure there is a valid Form XML document. |
196
|
|
|
if (!($this->xml instanceof \SimpleXMLElement)) |
197
|
|
|
{ |
198
|
|
|
return false; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
$input = new Registry($data); |
202
|
|
|
$output = new Registry; |
203
|
|
|
|
204
|
|
|
// Get the fields for which to filter the data. |
205
|
|
|
$fields = $this->findFieldsByGroup($group); |
206
|
|
|
|
207
|
|
|
if (!$fields) |
208
|
|
|
{ |
209
|
|
|
// PANIC! |
210
|
|
|
return false; |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
// Filter the fields. |
214
|
|
|
foreach ($fields as $field) |
215
|
|
|
{ |
216
|
|
|
$name = (string) $field['name']; |
217
|
|
|
|
218
|
|
|
// Get the field groups for the element. |
219
|
|
|
$attrs = $field->xpath('ancestor::fields[@name]/@name'); |
220
|
|
|
$groups = array_map('strval', $attrs ? $attrs : array()); |
221
|
|
|
$group = implode('.', $groups); |
222
|
|
|
|
223
|
|
|
// Get the field value from the data input. |
224
|
|
|
if ($group) |
225
|
|
|
{ |
226
|
|
|
// Filter the value if it exists. |
227
|
|
|
if ($input->exists($group . '.' . $name)) |
228
|
|
|
{ |
229
|
|
|
$output->set($group . '.' . $name, $this->filterField($field, $input->get($group . '.' . $name, (string) $field['default']))); |
230
|
|
|
} |
231
|
|
|
} |
232
|
|
|
else |
233
|
|
|
{ |
234
|
|
|
// Filter the value if it exists. |
235
|
|
|
if ($input->exists($name)) |
236
|
|
|
{ |
237
|
|
|
$output->set($name, $this->filterField($field, $input->get($name, (string) $field['default']))); |
238
|
|
|
} |
239
|
|
|
} |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
return $output->toArray(); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* Return all errors, if any. |
247
|
|
|
* |
248
|
|
|
* @return array Array of error messages or RuntimeException objects. |
249
|
|
|
* |
250
|
|
|
* @since 1.0 |
251
|
|
|
*/ |
252
|
|
|
public function getErrors() |
253
|
|
|
{ |
254
|
|
|
return $this->errors; |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Method to get a form field represented as a Field object. |
259
|
|
|
* |
260
|
|
|
* @param string $name The name of the form field. |
261
|
|
|
* @param string $group The optional dot-separated form group path on which to find the field. |
262
|
|
|
* @param mixed $value The optional value to use as the default for the field. |
263
|
|
|
* |
264
|
|
|
* @return mixed The Field object for the field or boolean false on error. |
265
|
|
|
* |
266
|
|
|
* @since 1.0 |
267
|
|
|
*/ |
268
|
|
|
public function getField($name, $group = null, $value = null) |
269
|
|
|
{ |
270
|
|
|
// Make sure there is a valid Form XML document. |
271
|
|
|
if (!($this->xml instanceof \SimpleXMLElement)) |
272
|
|
|
{ |
273
|
|
|
return false; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
// Attempt to find the field by name and group. |
277
|
|
|
$element = $this->findField($name, $group); |
278
|
|
|
|
279
|
|
|
// If the field element was not found return false. |
280
|
|
|
if (!$element) |
281
|
|
|
{ |
282
|
|
|
return false; |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
return $this->loadField($element, $group, $value); |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
/** |
289
|
|
|
* Method to get an attribute value from a field XML element. If the attribute doesn't exist or |
290
|
|
|
* is null then the optional default value will be used. |
291
|
|
|
* |
292
|
|
|
* @param string $name The name of the form field for which to get the attribute value. |
293
|
|
|
* @param string $attribute The name of the attribute for which to get a value. |
294
|
|
|
* @param mixed $default The optional default value to use if no attribute value exists. |
295
|
|
|
* @param string $group The optional dot-separated form group path on which to find the field. |
296
|
|
|
* |
297
|
|
|
* @return mixed The attribute value for the field. |
298
|
|
|
* |
299
|
|
|
* @since 1.0 |
300
|
|
|
* @throws \UnexpectedValueException |
301
|
|
|
*/ |
302
|
|
|
public function getFieldAttribute($name, $attribute, $default = null, $group = null) |
303
|
|
|
{ |
304
|
|
|
// Make sure there is a valid Form XML document. |
305
|
|
|
if (!($this->xml instanceof \SimpleXMLElement)) |
306
|
|
|
{ |
307
|
|
|
throw new \UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this))); |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
// Find the form field element from the definition. |
311
|
|
|
$element = $this->findField($name, $group); |
312
|
|
|
|
313
|
|
|
// If the element exists and the attribute exists for the field return the attribute value. |
314
|
|
|
if (($element instanceof \SimpleXMLElement) && ((string) $element[$attribute])) |
315
|
|
|
{ |
316
|
|
|
return (string) $element[$attribute]; |
317
|
|
|
} |
318
|
|
|
else |
319
|
|
|
// Otherwise return the given default value. |
320
|
|
|
{ |
321
|
|
|
return $default; |
322
|
|
|
} |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
* Method to get an array of FormField objects in a given fieldset by name. If no name is |
327
|
|
|
* given then all fields are returned. |
328
|
|
|
* |
329
|
|
|
* @param string $set The optional name of the fieldset. |
330
|
|
|
* |
331
|
|
|
* @return array The array of FormField objects in the fieldset. |
332
|
|
|
* |
333
|
|
|
* @since 1.0 |
334
|
|
|
*/ |
335
|
|
View Code Duplication |
public function getFieldset($set = null) |
|
|
|
|
336
|
|
|
{ |
337
|
|
|
$fields = array(); |
338
|
|
|
|
339
|
|
|
// Get all of the field elements in the fieldset. |
340
|
|
|
if ($set) |
|
|
|
|
341
|
|
|
{ |
342
|
|
|
$elements = $this->findFieldsByFieldset($set); |
343
|
|
|
} |
344
|
|
|
else |
345
|
|
|
// Get all fields. |
346
|
|
|
{ |
347
|
|
|
$elements = $this->findFieldsByGroup(); |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
// If no field elements were found return empty. |
351
|
|
|
if (empty($elements)) |
352
|
|
|
{ |
353
|
|
|
return $fields; |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
// Build the result array from the found field elements. |
357
|
|
|
foreach ($elements as $element) |
358
|
|
|
{ |
359
|
|
|
// Get the field groups for the element. |
360
|
|
|
$attrs = $element->xpath('ancestor::fields[@name]/@name'); |
361
|
|
|
$groups = array_map('strval', $attrs ? $attrs : array()); |
362
|
|
|
$group = implode('.', $groups); |
363
|
|
|
|
364
|
|
|
// If the field is successfully loaded add it to the result array. |
365
|
|
|
if ($field = $this->loadField($element, $group)) |
366
|
|
|
{ |
367
|
|
|
$fields[$field->id] = $field; |
368
|
|
|
} |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
return $fields; |
372
|
|
|
} |
373
|
|
|
|
374
|
|
|
/** |
375
|
|
|
* Method to get an array of fieldset objects optionally filtered over a given field group. |
376
|
|
|
* |
377
|
|
|
* @param string $group The dot-separated form group path on which to filter the fieldsets. |
378
|
|
|
* |
379
|
|
|
* @return array The array of fieldset objects. |
380
|
|
|
* |
381
|
|
|
* @since 1.0 |
382
|
|
|
*/ |
383
|
|
|
public function getFieldsets($group = null) |
384
|
|
|
{ |
385
|
|
|
$fieldsets = array(); |
386
|
|
|
$sets = array(); |
387
|
|
|
|
388
|
|
|
// Make sure there is a valid Form XML document. |
389
|
|
|
if (!($this->xml instanceof \SimpleXMLElement)) |
390
|
|
|
{ |
391
|
|
|
return $fieldsets; |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
if ($group) |
|
|
|
|
395
|
|
|
{ |
396
|
|
|
// Get the fields elements for a given group. |
397
|
|
|
$elements = &$this->findGroup($group); |
398
|
|
|
|
399
|
|
View Code Duplication |
foreach ($elements as &$element) |
|
|
|
|
400
|
|
|
{ |
401
|
|
|
// Get an array of <fieldset /> elements and fieldset attributes within the fields element. |
402
|
|
|
if ($tmp = $element->xpath('descendant::fieldset[@name] | descendant::field[@fieldset]/@fieldset')) |
403
|
|
|
{ |
404
|
|
|
$sets = array_merge($sets, (array) $tmp); |
405
|
|
|
} |
406
|
|
|
} |
407
|
|
|
} |
408
|
|
|
else |
409
|
|
|
{ |
410
|
|
|
// Get an array of <fieldset /> elements and fieldset attributes. |
411
|
|
|
$sets = $this->xml->xpath('//fieldset[@name] | //field[@fieldset]/@fieldset'); |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
// If no fieldsets are found return empty. |
415
|
|
|
if (empty($sets)) |
416
|
|
|
{ |
417
|
|
|
return $fieldsets; |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
// Process each found fieldset. |
421
|
|
|
foreach ($sets as $set) |
422
|
|
|
{ |
423
|
|
|
// Are we dealing with a fieldset element? |
424
|
|
|
if ((string) $set['name']) |
425
|
|
|
{ |
426
|
|
|
// Only create it if it doesn't already exist. |
427
|
|
|
if (empty($fieldsets[(string) $set['name']])) |
428
|
|
|
{ |
429
|
|
|
// Build the fieldset object. |
430
|
|
|
$fieldset = (object) array('name' => '', 'label' => '', 'description' => ''); |
431
|
|
|
|
432
|
|
|
foreach ($set->attributes() as $name => $value) |
433
|
|
|
{ |
434
|
|
|
$fieldset->$name = (string) $value; |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
// Add the fieldset object to the list. |
438
|
|
|
$fieldsets[$fieldset->name] = $fieldset; |
439
|
|
|
} |
440
|
|
|
} |
441
|
|
|
else |
442
|
|
|
// Must be dealing with a fieldset attribute. |
443
|
|
|
{ |
444
|
|
|
// Only create it if it doesn't already exist. |
445
|
|
|
if (empty($fieldsets[(string) $set])) |
446
|
|
|
{ |
447
|
|
|
// Attempt to get the fieldset element for data (throughout the entire form document). |
448
|
|
|
$tmp = $this->xml->xpath('//fieldset[@name="' . (string) $set . '"]'); |
449
|
|
|
|
450
|
|
|
// If no element was found, build a very simple fieldset object. |
451
|
|
|
if (empty($tmp)) |
452
|
|
|
{ |
453
|
|
|
$fieldset = (object) array('name' => (string) $set, 'label' => '', 'description' => ''); |
454
|
|
|
} |
455
|
|
|
else |
456
|
|
|
// Build the fieldset object from the element. |
457
|
|
|
{ |
458
|
|
|
$fieldset = (object) array('name' => '', 'label' => '', 'description' => ''); |
459
|
|
|
|
460
|
|
|
foreach ($tmp[0]->attributes() as $name => $value) |
461
|
|
|
{ |
462
|
|
|
$fieldset->$name = (string) $value; |
463
|
|
|
} |
464
|
|
|
} |
465
|
|
|
|
466
|
|
|
// Add the fieldset object to the list. |
467
|
|
|
$fieldsets[$fieldset->name] = $fieldset; |
468
|
|
|
} |
469
|
|
|
} |
470
|
|
|
} |
471
|
|
|
|
472
|
|
|
return $fieldsets; |
473
|
|
|
} |
474
|
|
|
|
475
|
|
|
/** |
476
|
|
|
* Method to get the form control. This string serves as a container for all form fields. For |
477
|
|
|
* example, if there is a field named 'foo' and a field named 'bar' and the form control is |
478
|
|
|
* empty the fields will be rendered like: <input name="foo" /> and <input name="bar" />. If |
479
|
|
|
* the form control is set to 'joomla' however, the fields would be rendered like: |
480
|
|
|
* <input name="joomla[foo]" /> and <input name="joomla[bar]" />. |
481
|
|
|
* |
482
|
|
|
* @return string The form control string. |
483
|
|
|
* |
484
|
|
|
* @since 1.0 |
485
|
|
|
*/ |
486
|
|
|
public function getFormControl() |
487
|
|
|
{ |
488
|
|
|
return (string) $this->options['control']; |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
/** |
492
|
|
|
* Method to get an array of FormField objects in a given field group by name. |
493
|
|
|
* |
494
|
|
|
* @param string $group The dot-separated form group path for which to get the form fields. |
495
|
|
|
* @param boolean $nested True to also include fields in nested groups that are inside of the |
496
|
|
|
* group for which to find fields. |
497
|
|
|
* |
498
|
|
|
* @return array The array of FormField objects in the field group. |
499
|
|
|
* |
500
|
|
|
* @since 1.0 |
501
|
|
|
*/ |
502
|
|
View Code Duplication |
public function getGroup($group, $nested = false) |
|
|
|
|
503
|
|
|
{ |
504
|
|
|
$fields = array(); |
505
|
|
|
|
506
|
|
|
// Get all of the field elements in the field group. |
507
|
|
|
$elements = $this->findFieldsByGroup($group, $nested); |
508
|
|
|
|
509
|
|
|
// If no field elements were found return empty. |
510
|
|
|
if (empty($elements)) |
511
|
|
|
{ |
512
|
|
|
return $fields; |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
// Build the result array from the found field elements. |
516
|
|
|
foreach ($elements as $element) |
517
|
|
|
{ |
518
|
|
|
// Get the field groups for the element. |
519
|
|
|
$attrs = $element->xpath('ancestor::fields[@name]/@name'); |
520
|
|
|
$groups = array_map('strval', $attrs ? $attrs : array()); |
521
|
|
|
$group = implode('.', $groups); |
522
|
|
|
|
523
|
|
|
// If the field is successfully loaded add it to the result array. |
524
|
|
|
if ($field = $this->loadField($element, $group)) |
525
|
|
|
{ |
526
|
|
|
$fields[$field->id] = $field; |
527
|
|
|
} |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
return $fields; |
531
|
|
|
} |
532
|
|
|
|
533
|
|
|
/** |
534
|
|
|
* Method to get a form field markup for the field input. |
535
|
|
|
* |
536
|
|
|
* @param string $name The name of the form field. |
537
|
|
|
* @param string $group The optional dot-separated form group path on which to find the field. |
538
|
|
|
* @param mixed $value The optional value to use as the default for the field. |
539
|
|
|
* |
540
|
|
|
* @return string The form field markup. |
541
|
|
|
* |
542
|
|
|
* @since 1.0 |
543
|
|
|
*/ |
544
|
|
|
public function getInput($name, $group = null, $value = null) |
545
|
|
|
{ |
546
|
|
|
// Attempt to get the form field. |
547
|
|
|
if ($field = $this->getField($name, $group, $value)) |
548
|
|
|
{ |
549
|
|
|
return $field->input; |
550
|
|
|
} |
551
|
|
|
|
552
|
|
|
return ''; |
553
|
|
|
} |
554
|
|
|
|
555
|
|
|
/** |
556
|
|
|
* Method to get the label for a field input. |
557
|
|
|
* |
558
|
|
|
* @param string $name The name of the form field. |
559
|
|
|
* @param string $group The optional dot-separated form group path on which to find the field. |
560
|
|
|
* |
561
|
|
|
* @return string The form field label. |
562
|
|
|
* |
563
|
|
|
* @since 1.0 |
564
|
|
|
*/ |
565
|
|
|
public function getLabel($name, $group = null) |
566
|
|
|
{ |
567
|
|
|
// Attempt to get the form field. |
568
|
|
|
if ($field = $this->getField($name, $group)) |
569
|
|
|
{ |
570
|
|
|
return $field->label; |
571
|
|
|
} |
572
|
|
|
|
573
|
|
|
return ''; |
574
|
|
|
} |
575
|
|
|
|
576
|
|
|
/** |
577
|
|
|
* Method to get the form name. |
578
|
|
|
* |
579
|
|
|
* @return string The name of the form. |
580
|
|
|
* |
581
|
|
|
* @since 1.0 |
582
|
|
|
*/ |
583
|
|
|
public function getName() |
584
|
|
|
{ |
585
|
|
|
return $this->name; |
586
|
|
|
} |
587
|
|
|
|
588
|
|
|
/** |
589
|
|
|
* Method to get the value of a field. |
590
|
|
|
* |
591
|
|
|
* @param string $name The name of the field for which to get the value. |
592
|
|
|
* @param string $group The optional dot-separated form group path on which to get the value. |
593
|
|
|
* @param mixed $default The optional default value of the field value is empty. |
594
|
|
|
* |
595
|
|
|
* @return mixed The value of the field or the default value if empty. |
596
|
|
|
* |
597
|
|
|
* @since 1.0 |
598
|
|
|
*/ |
599
|
|
|
public function getValue($name, $group = null, $default = null) |
600
|
|
|
{ |
601
|
|
|
// If a group is set use it. |
602
|
|
|
if ($group) |
|
|
|
|
603
|
|
|
{ |
604
|
|
|
$return = $this->data->get($group . '.' . $name, $default); |
605
|
|
|
} |
606
|
|
|
else |
607
|
|
|
{ |
608
|
|
|
$return = $this->data->get($name, $default); |
609
|
|
|
} |
610
|
|
|
|
611
|
|
|
return $return; |
612
|
|
|
} |
613
|
|
|
|
614
|
|
|
/** |
615
|
|
|
* Method to load the form description from an XML string or object. |
616
|
|
|
* |
617
|
|
|
* The replace option works per field. If a field being loaded already exists in the current |
618
|
|
|
* form definition then the behavior or load will vary depending upon the replace flag. If it |
619
|
|
|
* is set to true, then the existing field will be replaced in its exact location by the new |
620
|
|
|
* field being loaded. If it is false, then the new field being loaded will be ignored and the |
621
|
|
|
* method will move on to the next field to load. |
622
|
|
|
* |
623
|
|
|
* @param string $data The name of an XML string or object. |
624
|
|
|
* @param string $replace Flag to toggle whether form fields should be replaced if a field |
625
|
|
|
* already exists with the same group/name. |
626
|
|
|
* @param string $xpath An optional xpath to search for the fields. |
627
|
|
|
* |
628
|
|
|
* @return boolean True on success, false otherwise. |
629
|
|
|
* |
630
|
|
|
* @since 1.0 |
631
|
|
|
*/ |
632
|
|
|
public function load($data, $replace = true, $xpath = false) |
633
|
|
|
{ |
634
|
|
|
// If the data to load isn't already an XML element or string return false. |
635
|
|
|
if ((!($data instanceof \SimpleXMLElement)) && (!is_string($data))) |
636
|
|
|
{ |
637
|
|
|
return false; |
638
|
|
|
} |
639
|
|
|
|
640
|
|
|
// Attempt to load the XML if a string. |
641
|
|
|
if (is_string($data)) |
642
|
|
|
{ |
643
|
|
|
try |
644
|
|
|
{ |
645
|
|
|
$data = new \SimpleXMLElement($data); |
646
|
|
|
} |
647
|
|
|
catch (\Exception $e) |
648
|
|
|
{ |
649
|
|
|
return false; |
650
|
|
|
} |
651
|
|
|
|
652
|
|
|
// Make sure the XML loaded correctly. |
653
|
|
|
if (!$data) |
654
|
|
|
{ |
655
|
|
|
return false; |
656
|
|
|
} |
657
|
|
|
} |
658
|
|
|
|
659
|
|
|
// If we have no XML definition at this point let's make sure we get one. |
660
|
|
|
if (empty($this->xml)) |
661
|
|
|
{ |
662
|
|
|
// If no XPath query is set to search for fields, and we have a <form />, set it and return. |
663
|
|
|
if (!$xpath && ($data->getName() == 'form')) |
|
|
|
|
664
|
|
|
{ |
665
|
|
|
$this->xml = $data; |
666
|
|
|
|
667
|
|
|
// Synchronize any paths found in the load. |
668
|
|
|
$this->syncPaths(); |
669
|
|
|
|
670
|
|
|
return true; |
671
|
|
|
} |
672
|
|
|
else |
673
|
|
|
// Create a root element for the form. |
674
|
|
|
{ |
675
|
|
|
$this->xml = new \SimpleXMLElement('<form></form>'); |
676
|
|
|
} |
677
|
|
|
} |
678
|
|
|
|
679
|
|
|
// Get the XML elements to load. |
680
|
|
|
$elements = array(); |
681
|
|
|
|
682
|
|
|
if ($xpath) |
|
|
|
|
683
|
|
|
{ |
684
|
|
|
$elements = $data->xpath($xpath); |
685
|
|
|
} |
686
|
|
|
elseif ($data->getName() == 'form') |
687
|
|
|
{ |
688
|
|
|
$elements = $data->children(); |
689
|
|
|
} |
690
|
|
|
|
691
|
|
|
// If there is nothing to load return true. |
692
|
|
|
if (empty($elements)) |
693
|
|
|
{ |
694
|
|
|
return true; |
695
|
|
|
} |
696
|
|
|
|
697
|
|
|
// Load the found form elements. |
698
|
|
|
foreach ($elements as $element) |
699
|
|
|
{ |
700
|
|
|
// Get an array of fields with the correct name. |
701
|
|
|
$fields = $element->xpath('descendant-or-self::field'); |
702
|
|
|
|
703
|
|
|
foreach ($fields as $field) |
704
|
|
|
{ |
705
|
|
|
// Get the group names as strings for ancestor fields elements. |
706
|
|
|
$attrs = $field->xpath('ancestor::fields[@name]/@name'); |
707
|
|
|
$groups = array_map('strval', $attrs ? $attrs : array()); |
708
|
|
|
|
709
|
|
|
// Check to see if the field exists in the current form. |
710
|
|
|
if ($current = $this->findField((string) $field['name'], implode('.', $groups))) |
711
|
|
|
{ |
712
|
|
|
// If set to replace found fields, replace the data and remove the field so we don't add it twice. |
713
|
|
|
if ($replace) |
714
|
|
|
{ |
715
|
|
|
$olddom = dom_import_simplexml($current); |
716
|
|
|
$loadeddom = dom_import_simplexml($field); |
717
|
|
|
$addeddom = $olddom->ownerDocument->importNode($loadeddom); |
718
|
|
|
$olddom->parentNode->replaceChild($addeddom, $olddom); |
719
|
|
|
$loadeddom->parentNode->removeChild($loadeddom); |
720
|
|
|
} |
721
|
|
|
else |
722
|
|
|
{ |
723
|
|
|
unset($field); |
724
|
|
|
} |
725
|
|
|
} |
726
|
|
|
} |
727
|
|
|
|
728
|
|
|
// Merge the new field data into the existing XML document. |
729
|
|
|
self::addNode($this->xml, $element); |
730
|
|
|
} |
731
|
|
|
|
732
|
|
|
// Synchronize any paths found in the load. |
733
|
|
|
$this->syncPaths(); |
734
|
|
|
|
735
|
|
|
return true; |
736
|
|
|
} |
737
|
|
|
|
738
|
|
|
/** |
739
|
|
|
* Method to load the form description from an XML file. |
740
|
|
|
* |
741
|
|
|
* The reset option works on a group basis. If the XML file references |
742
|
|
|
* groups that have already been created they will be replaced with the |
743
|
|
|
* fields in the new XML file unless the $reset parameter has been set |
744
|
|
|
* to false. |
745
|
|
|
* |
746
|
|
|
* @param string $file The filesystem path of an XML file. |
747
|
|
|
* @param string $reset Flag to toggle whether form fields should be replaced if a field |
748
|
|
|
* already exists with the same group/name. |
749
|
|
|
* @param string $xpath An optional xpath to search for the fields. |
750
|
|
|
* |
751
|
|
|
* @return boolean True on success, false otherwise. |
752
|
|
|
* |
753
|
|
|
* @since 1.0 |
754
|
|
|
*/ |
755
|
|
|
public function loadFile($file, $reset = true, $xpath = false) |
756
|
|
|
{ |
757
|
|
|
// Check to see if the path is an absolute path. |
758
|
|
|
if (!is_file($file)) |
759
|
|
|
{ |
760
|
|
|
// Not an absolute path so let's attempt to find one using JPath. |
761
|
|
|
$file = Path::find(FormHelper::addFormPath(), strtolower($file) . '.xml'); |
762
|
|
|
|
763
|
|
|
// If unable to find the file return false. |
764
|
|
|
if (!$file) |
|
|
|
|
765
|
|
|
{ |
766
|
|
|
return false; |
767
|
|
|
} |
768
|
|
|
} |
769
|
|
|
|
770
|
|
|
// Attempt to load the XML file. |
771
|
|
|
$xml = simplexml_load_file($file); |
772
|
|
|
|
773
|
|
|
return $this->load($xml, $reset, $xpath); |
774
|
|
|
} |
775
|
|
|
|
776
|
|
|
/** |
777
|
|
|
* Method to remove a field from the form definition. |
778
|
|
|
* |
779
|
|
|
* @param string $name The name of the form field for which remove. |
780
|
|
|
* @param string $group The optional dot-separated form group path on which to find the field. |
781
|
|
|
* |
782
|
|
|
* @return boolean True on success. |
783
|
|
|
* |
784
|
|
|
* @since 1.0 |
785
|
|
|
* @throws \UnexpectedValueException |
786
|
|
|
*/ |
787
|
|
View Code Duplication |
public function removeField($name, $group = null) |
|
|
|
|
788
|
|
|
{ |
789
|
|
|
// Make sure there is a valid Form XML document. |
790
|
|
|
if (!($this->xml instanceof \SimpleXMLElement)) |
791
|
|
|
{ |
792
|
|
|
throw new \UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this))); |
793
|
|
|
} |
794
|
|
|
|
795
|
|
|
// Find the form field element from the definition. |
796
|
|
|
$element = $this->findField($name, $group); |
797
|
|
|
|
798
|
|
|
// If the element exists remove it from the form definition. |
799
|
|
|
if ($element instanceof \SimpleXMLElement) |
800
|
|
|
{ |
801
|
|
|
$dom = dom_import_simplexml($element); |
802
|
|
|
$dom->parentNode->removeChild($dom); |
803
|
|
|
} |
804
|
|
|
|
805
|
|
|
return true; |
806
|
|
|
} |
807
|
|
|
|
808
|
|
|
/** |
809
|
|
|
* Method to remove a group from the form definition. |
810
|
|
|
* |
811
|
|
|
* @param string $group The dot-separated form group path for the group to remove. |
812
|
|
|
* |
813
|
|
|
* @return boolean True on success. |
814
|
|
|
* |
815
|
|
|
* @since 1.0 |
816
|
|
|
* @throws \UnexpectedValueException |
817
|
|
|
*/ |
818
|
|
|
public function removeGroup($group) |
819
|
|
|
{ |
820
|
|
|
// Make sure there is a valid Form XML document. |
821
|
|
|
if (!($this->xml instanceof \SimpleXMLElement)) |
822
|
|
|
{ |
823
|
|
|
throw new \UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this))); |
824
|
|
|
} |
825
|
|
|
|
826
|
|
|
// Get the fields elements for a given group. |
827
|
|
|
$elements = &$this->findGroup($group); |
828
|
|
|
|
829
|
|
|
foreach ($elements as &$element) |
|
|
|
|
830
|
|
|
{ |
831
|
|
|
$dom = dom_import_simplexml($element); |
832
|
|
|
$dom->parentNode->removeChild($dom); |
833
|
|
|
} |
834
|
|
|
|
835
|
|
|
return true; |
836
|
|
|
} |
837
|
|
|
|
838
|
|
|
/** |
839
|
|
|
* Method to reset the form data store and optionally the form XML definition. |
840
|
|
|
* |
841
|
|
|
* @param boolean $xml True to also reset the XML form definition. |
842
|
|
|
* |
843
|
|
|
* @return boolean True on success. |
844
|
|
|
* |
845
|
|
|
* @since 1.0 |
846
|
|
|
*/ |
847
|
|
|
public function reset($xml = false) |
848
|
|
|
{ |
849
|
|
|
unset($this->data); |
850
|
|
|
$this->data = new Registry; |
851
|
|
|
|
852
|
|
|
if ($xml) |
853
|
|
|
{ |
854
|
|
|
unset($this->xml); |
855
|
|
|
$this->xml = new \SimpleXMLElement('<form></form>'); |
856
|
|
|
} |
857
|
|
|
|
858
|
|
|
return true; |
859
|
|
|
} |
860
|
|
|
|
861
|
|
|
/** |
862
|
|
|
* Method to set a field XML element to the form definition. If the replace flag is set then |
863
|
|
|
* the field will be set whether it already exists or not. If it isn't set, then the field |
864
|
|
|
* will not be replaced if it already exists. |
865
|
|
|
* |
866
|
|
|
* @param \SimpleXMLElement $element The XML element object representation of the form field. |
867
|
|
|
* @param string $group The optional dot-separated form group path on which to set the field. |
868
|
|
|
* @param boolean $replace True to replace an existing field if one already exists. |
869
|
|
|
* |
870
|
|
|
* @return boolean True on success. |
871
|
|
|
* |
872
|
|
|
* @since 1.0 |
873
|
|
|
* @throws \UnexpectedValueException |
874
|
|
|
*/ |
875
|
|
|
public function setField(\SimpleXMLElement $element, $group = null, $replace = true) |
876
|
|
|
{ |
877
|
|
|
// Make sure there is a valid Form XML document. |
878
|
|
|
if (!($this->xml instanceof \SimpleXMLElement)) |
879
|
|
|
{ |
880
|
|
|
throw new \UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this))); |
881
|
|
|
} |
882
|
|
|
|
883
|
|
|
// Find the form field element from the definition. |
884
|
|
|
$old = $this->findField((string) $element['name'], $group); |
885
|
|
|
|
886
|
|
|
// If an existing field is found and replace flag is false do nothing and return true. |
887
|
|
|
if (!$replace && !empty($old)) |
888
|
|
|
{ |
889
|
|
|
return true; |
890
|
|
|
} |
891
|
|
|
|
892
|
|
|
// If an existing field is found and replace flag is true remove the old field. |
893
|
|
|
if ($replace && !empty($old) && ($old instanceof \SimpleXMLElement)) |
894
|
|
|
{ |
895
|
|
|
$dom = dom_import_simplexml($old); |
896
|
|
|
$dom->parentNode->removeChild($dom); |
897
|
|
|
} |
898
|
|
|
|
899
|
|
|
// If no existing field is found find a group element and add the field as a child of it. |
900
|
|
|
if ($group) |
|
|
|
|
901
|
|
|
{ |
902
|
|
|
// Get the fields elements for a given group. |
903
|
|
|
$fields = &$this->findGroup($group); |
904
|
|
|
|
905
|
|
|
// If an appropriate fields element was found for the group, add the element. |
906
|
|
|
if (isset($fields[0]) && ($fields[0] instanceof \SimpleXMLElement)) |
907
|
|
|
{ |
908
|
|
|
self::addNode($fields[0], $element); |
909
|
|
|
} |
910
|
|
|
} |
911
|
|
|
else |
912
|
|
|
{ |
913
|
|
|
// Set the new field to the form. |
914
|
|
|
self::addNode($this->xml, $element); |
915
|
|
|
} |
916
|
|
|
|
917
|
|
|
// Synchronize any paths found in the load. |
918
|
|
|
$this->syncPaths(); |
919
|
|
|
|
920
|
|
|
return true; |
921
|
|
|
} |
922
|
|
|
|
923
|
|
|
/** |
924
|
|
|
* Method to set an attribute value for a field XML element. |
925
|
|
|
* |
926
|
|
|
* @param string $name The name of the form field for which to set the attribute value. |
927
|
|
|
* @param string $attribute The name of the attribute for which to set a value. |
928
|
|
|
* @param mixed $value The value to set for the attribute. |
929
|
|
|
* @param string $group The optional dot-separated form group path on which to find the field. |
930
|
|
|
* |
931
|
|
|
* @return boolean True on success. |
932
|
|
|
* |
933
|
|
|
* @since 1.0 |
934
|
|
|
* @throws \UnexpectedValueException |
935
|
|
|
*/ |
936
|
|
View Code Duplication |
public function setFieldAttribute($name, $attribute, $value, $group = null) |
|
|
|
|
937
|
|
|
{ |
938
|
|
|
// Make sure there is a valid Form XML document. |
939
|
|
|
if (!($this->xml instanceof \SimpleXMLElement)) |
940
|
|
|
{ |
941
|
|
|
throw new \UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this))); |
942
|
|
|
} |
943
|
|
|
|
944
|
|
|
// Find the form field element from the definition. |
945
|
|
|
$element = $this->findField($name, $group); |
946
|
|
|
|
947
|
|
|
// If the element doesn't exist return false. |
948
|
|
|
if (!($element instanceof \SimpleXMLElement)) |
949
|
|
|
{ |
950
|
|
|
return false; |
951
|
|
|
} |
952
|
|
|
else |
953
|
|
|
// Otherwise set the attribute and return true. |
954
|
|
|
{ |
955
|
|
|
$element[$attribute] = $value; |
956
|
|
|
|
957
|
|
|
// Synchronize any paths found in the load. |
958
|
|
|
$this->syncPaths(); |
959
|
|
|
|
960
|
|
|
return true; |
961
|
|
|
} |
962
|
|
|
} |
963
|
|
|
|
964
|
|
|
/** |
965
|
|
|
* Method to set some field XML elements to the form definition. If the replace flag is set then |
966
|
|
|
* the fields will be set whether they already exists or not. If it isn't set, then the fields |
967
|
|
|
* will not be replaced if they already exist. |
968
|
|
|
* |
969
|
|
|
* @param array &$elements The array of XML element object representations of the form fields. |
970
|
|
|
* @param string $group The optional dot-separated form group path on which to set the fields. |
971
|
|
|
* @param boolean $replace True to replace existing fields if they already exist. |
972
|
|
|
* |
973
|
|
|
* @return boolean True on success. |
974
|
|
|
* |
975
|
|
|
* @since 1.0 |
976
|
|
|
* @throws \UnexpectedValueException |
977
|
|
|
*/ |
978
|
|
|
public function setFields(&$elements, $group = null, $replace = true) |
979
|
|
|
{ |
980
|
|
|
// Make sure there is a valid Form XML document. |
981
|
|
|
if (!($this->xml instanceof \SimpleXMLElement)) |
982
|
|
|
{ |
983
|
|
|
throw new \UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this))); |
984
|
|
|
} |
985
|
|
|
|
986
|
|
|
// Make sure the elements to set are valid. |
987
|
|
|
foreach ($elements as $element) |
988
|
|
|
{ |
989
|
|
|
if (!($element instanceof \SimpleXMLElement)) |
990
|
|
|
{ |
991
|
|
|
throw new \UnexpectedValueException(sprintf('$element not SimpleXMLElement in %s::setFields', get_class($this))); |
992
|
|
|
} |
993
|
|
|
} |
994
|
|
|
|
995
|
|
|
// Set the fields. |
996
|
|
|
$return = true; |
997
|
|
|
|
998
|
|
|
foreach ($elements as $element) |
999
|
|
|
{ |
1000
|
|
|
if (!$this->setField($element, $group, $replace)) |
1001
|
|
|
{ |
1002
|
|
|
$return = false; |
1003
|
|
|
} |
1004
|
|
|
} |
1005
|
|
|
|
1006
|
|
|
// Synchronize any paths found in the load. |
1007
|
|
|
$this->syncPaths(); |
1008
|
|
|
|
1009
|
|
|
return $return; |
1010
|
|
|
} |
1011
|
|
|
|
1012
|
|
|
/** |
1013
|
|
|
* Method to set the value of a field. If the field does not exist in the form then the method |
1014
|
|
|
* will return false. |
1015
|
|
|
* |
1016
|
|
|
* @param string $name The name of the field for which to set the value. |
1017
|
|
|
* @param string $group The optional dot-separated form group path on which to find the field. |
1018
|
|
|
* @param mixed $value The value to set for the field. |
1019
|
|
|
* |
1020
|
|
|
* @return boolean True on success. |
1021
|
|
|
* |
1022
|
|
|
* @since 1.0 |
1023
|
|
|
*/ |
1024
|
|
|
public function setValue($name, $group = null, $value = null) |
1025
|
|
|
{ |
1026
|
|
|
// If the field does not exist return false. |
1027
|
|
|
if (!$this->findField($name, $group)) |
1028
|
|
|
{ |
1029
|
|
|
return false; |
1030
|
|
|
} |
1031
|
|
|
|
1032
|
|
|
// If a group is set use it. |
1033
|
|
|
if ($group) |
|
|
|
|
1034
|
|
|
{ |
1035
|
|
|
$this->data->set($group . '.' . $name, $value); |
1036
|
|
|
} |
1037
|
|
|
else |
1038
|
|
|
{ |
1039
|
|
|
$this->data->set($name, $value); |
1040
|
|
|
} |
1041
|
|
|
|
1042
|
|
|
return true; |
1043
|
|
|
} |
1044
|
|
|
|
1045
|
|
|
/** |
1046
|
|
|
* Method to validate form data. |
1047
|
|
|
* |
1048
|
|
|
* Validation warnings will be pushed into Form::errors and should be |
1049
|
|
|
* retrieved with Form::getErrors() when validate returns boolean false. |
1050
|
|
|
* |
1051
|
|
|
* @param array $data An array of field values to validate. |
1052
|
|
|
* @param string $group The optional dot-separated form group path on which to filter the |
1053
|
|
|
* fields to be validated. |
1054
|
|
|
* |
1055
|
|
|
* @return mixed True on success. |
1056
|
|
|
* |
1057
|
|
|
* @since 1.0 |
1058
|
|
|
*/ |
1059
|
|
|
public function validate($data, $group = null) |
1060
|
|
|
{ |
1061
|
|
|
// Make sure there is a valid Form XML document. |
1062
|
|
|
if (!($this->xml instanceof \SimpleXMLElement)) |
1063
|
|
|
{ |
1064
|
|
|
return false; |
1065
|
|
|
} |
1066
|
|
|
|
1067
|
|
|
$return = true; |
1068
|
|
|
|
1069
|
|
|
// Create an input registry object from the data to validate. |
1070
|
|
|
$input = new Registry($data); |
1071
|
|
|
|
1072
|
|
|
// Get the fields for which to validate the data. |
1073
|
|
|
$fields = $this->findFieldsByGroup($group); |
1074
|
|
|
|
1075
|
|
|
if (!$fields) |
1076
|
|
|
{ |
1077
|
|
|
// PANIC! |
1078
|
|
|
return false; |
1079
|
|
|
} |
1080
|
|
|
|
1081
|
|
|
// Validate the fields. |
1082
|
|
|
foreach ($fields as $field) |
1083
|
|
|
{ |
1084
|
|
|
$value = null; |
|
|
|
|
1085
|
|
|
$name = (string) $field['name']; |
1086
|
|
|
|
1087
|
|
|
// Get the group names as strings for ancestor fields elements. |
1088
|
|
|
$attrs = $field->xpath('ancestor::fields[@name]/@name'); |
1089
|
|
|
$groups = array_map('strval', $attrs ? $attrs : array()); |
1090
|
|
|
$group = implode('.', $groups); |
1091
|
|
|
|
1092
|
|
|
// Get the value from the input data. |
1093
|
|
|
if ($group) |
1094
|
|
|
{ |
1095
|
|
|
$value = $input->get($group . '.' . $name); |
1096
|
|
|
} |
1097
|
|
|
else |
1098
|
|
|
{ |
1099
|
|
|
$value = $input->get($name); |
1100
|
|
|
} |
1101
|
|
|
|
1102
|
|
|
// Validate the field. |
1103
|
|
|
$valid = $this->validateField($field, $group, $value, $input); |
1104
|
|
|
|
1105
|
|
|
// Check for an error. |
1106
|
|
|
if ($valid instanceof \Exception) |
1107
|
|
|
{ |
1108
|
|
|
array_push($this->errors, $valid); |
1109
|
|
|
$return = false; |
1110
|
|
|
} |
1111
|
|
|
} |
1112
|
|
|
|
1113
|
|
|
return $return; |
1114
|
|
|
} |
1115
|
|
|
|
1116
|
|
|
/** |
1117
|
|
|
* Method to apply an input filter to a value based on field data. |
1118
|
|
|
* |
1119
|
|
|
* @param string $element The XML element object representation of the form field. |
1120
|
|
|
* @param mixed $value The value to filter for the field. |
1121
|
|
|
* |
1122
|
|
|
* @return mixed The filtered value. |
1123
|
|
|
* |
1124
|
|
|
* @since 1.0 |
1125
|
|
|
*/ |
1126
|
|
|
protected function filterField($element, $value) |
1127
|
|
|
{ |
1128
|
|
|
// Make sure there is a valid SimpleXMLElement. |
1129
|
|
|
if (!($element instanceof \SimpleXMLElement)) |
1130
|
|
|
{ |
1131
|
|
|
return false; |
1132
|
|
|
} |
1133
|
|
|
|
1134
|
|
|
// Get the field filter type. |
1135
|
|
|
$filter = (string) $element['filter']; |
1136
|
|
|
|
1137
|
|
|
// Process the input value based on the filter. |
1138
|
|
|
$return = null; |
1139
|
|
|
|
1140
|
|
|
switch (strtoupper($filter)) |
1141
|
|
|
{ |
1142
|
|
|
// Do nothing, thus leaving the return value as null. |
1143
|
|
|
case 'UNSET': |
1144
|
|
|
break; |
1145
|
|
|
|
1146
|
|
|
// No Filter. |
1147
|
|
|
case 'RAW': |
1148
|
|
|
$return = $value; |
1149
|
|
|
break; |
1150
|
|
|
|
1151
|
|
|
// Filter the input as an array of integers. |
1152
|
|
|
case 'INT_ARRAY': |
1153
|
|
|
// Make sure the input is an array. |
1154
|
|
|
if (is_object($value)) |
1155
|
|
|
{ |
1156
|
|
|
$value = get_object_vars($value); |
1157
|
|
|
} |
1158
|
|
|
|
1159
|
|
|
$value = is_array($value) ? $value : array($value); |
1160
|
|
|
|
1161
|
|
|
$value = ArrayHelper::toInteger($value); |
1162
|
|
|
$return = $value; |
1163
|
|
|
break; |
1164
|
|
|
|
1165
|
|
|
// Filter safe HTML. |
1166
|
|
|
case 'SAFEHTML': |
1167
|
|
|
$filterInput = new Filter\InputFilter(null, null, 1, 1); |
|
|
|
|
1168
|
|
|
$return = $filterInput->clean($value, 'string'); |
1169
|
|
|
break; |
1170
|
|
|
|
1171
|
|
|
// Ensures a protocol is present in the saved field. Only use when |
1172
|
|
|
// the only permitted protocols requre '://'. See Rule\Url for list of these. |
1173
|
|
|
|
1174
|
|
|
case 'URL': |
1175
|
|
|
if (empty($value)) |
1176
|
|
|
{ |
1177
|
|
|
return false; |
1178
|
|
|
} |
1179
|
|
|
|
1180
|
|
|
$filterInput = new Filter\InputFilter; |
1181
|
|
|
$value = $filterInput->clean($value, 'html'); |
1182
|
|
|
$value = trim($value); |
1183
|
|
|
|
1184
|
|
|
// Check for a protocol |
1185
|
|
|
$protocol = parse_url($value, PHP_URL_SCHEME); |
1186
|
|
|
|
1187
|
|
|
// If there is no protocol and the relative option is not specified, |
1188
|
|
|
// we assume that it is an external URL and prepend http://. |
1189
|
|
|
if (($element['type'] == 'url' && !$protocol && !$element['relative']) |
|
|
|
|
1190
|
|
|
|| (!$element['type'] == 'url' && !$protocol)) |
|
|
|
|
1191
|
|
|
{ |
1192
|
|
|
$protocol = 'http'; |
1193
|
|
|
|
1194
|
|
|
// If it looks like an internal link, then add the root. |
1195
|
|
|
if (substr($value, 0) == 'index.php') |
1196
|
|
|
{ |
1197
|
|
|
$value = Uri::root() . $value; |
|
|
|
|
1198
|
|
|
} |
1199
|
|
|
|
1200
|
|
|
// Otherwise we treat it is an external link. |
1201
|
|
|
// Put the url back together. |
1202
|
|
|
$value = $protocol . '://' . $value; |
1203
|
|
|
} |
1204
|
|
|
|
1205
|
|
|
// If relative URLS are allowed we assume that URLs without protocols are internal. |
1206
|
|
|
elseif (!$protocol && $element['relative']) |
|
|
|
|
1207
|
|
|
{ |
1208
|
|
|
$host = Uri::getInstance('SERVER')->gethost(); |
|
|
|
|
1209
|
|
|
|
1210
|
|
|
// If it starts with the host string, just prepend the protocol. |
1211
|
|
|
if (substr($value, 0) == $host) |
1212
|
|
|
{ |
1213
|
|
|
$value = 'http://' . $value; |
1214
|
|
|
} |
1215
|
|
|
else |
1216
|
|
|
// Otherwise prepend the root. |
1217
|
|
|
{ |
1218
|
|
|
$value = Uri::root() . $value; |
|
|
|
|
1219
|
|
|
} |
1220
|
|
|
} |
1221
|
|
|
|
1222
|
|
|
$return = $value; |
1223
|
|
|
break; |
1224
|
|
|
|
1225
|
|
|
case 'TEL': |
1226
|
|
|
$value = trim($value); |
1227
|
|
|
|
1228
|
|
|
// Does it match the NANP pattern? |
1229
|
|
|
if (preg_match('/^(?:\+?1[-. ]?)?\(?([2-9][0-8][0-9])\)?[-. ]?([2-9][0-9]{2})[-. ]?([0-9]{4})$/', $value) == 1) |
1230
|
|
|
{ |
1231
|
|
|
$number = (string) preg_replace('/[^\d]/', '', $value); |
1232
|
|
|
|
1233
|
|
|
if (substr($number, 0, 1) == 1) |
1234
|
|
|
{ |
1235
|
|
|
$number = substr($number, 1); |
1236
|
|
|
} |
1237
|
|
|
|
1238
|
|
|
if (substr($number, 0, 2) == '+1') |
1239
|
|
|
{ |
1240
|
|
|
$number = substr($number, 2); |
1241
|
|
|
} |
1242
|
|
|
|
1243
|
|
|
$result = '1.' . $number; |
1244
|
|
|
} |
1245
|
|
|
elseif (preg_match('/^\+(?:[0-9] ?){6,14}[0-9]$/', $value) == 1) |
1246
|
|
|
// If not, does it match ITU-T? |
1247
|
|
|
{ |
1248
|
|
|
$countrycode = substr($value, 0, strpos($value, ' ')); |
1249
|
|
|
$countrycode = (string) preg_replace('/[^\d]/', '', $countrycode); |
1250
|
|
|
$number = strstr($value, ' '); |
1251
|
|
|
$number = (string) preg_replace('/[^\d]/', '', $number); |
1252
|
|
|
$result = $countrycode . '.' . $number; |
1253
|
|
|
} |
1254
|
|
|
elseif (preg_match('/^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$/', $value) == 1) |
1255
|
|
|
// If not, does it match EPP? |
1256
|
|
|
{ |
1257
|
|
|
if (strstr($value, 'x')) |
1258
|
|
|
{ |
1259
|
|
|
$xpos = strpos($value, 'x'); |
1260
|
|
|
$value = substr($value, 0, $xpos); |
1261
|
|
|
} |
1262
|
|
|
|
1263
|
|
|
$result = str_replace('+', '', $value); |
1264
|
|
|
} |
1265
|
|
|
elseif (preg_match('/[0-9]{1,3}\.[0-9]{4,14}$/', $value) == 1) |
1266
|
|
|
// Maybe it is already ccc.nnnnnnn? |
1267
|
|
|
{ |
1268
|
|
|
$result = $value; |
1269
|
|
|
} |
1270
|
|
|
else |
1271
|
|
|
// If not, can we make it a string of digits? |
1272
|
|
|
{ |
1273
|
|
|
$value = (string) preg_replace('/[^\d]/', '', $value); |
1274
|
|
|
|
1275
|
|
|
if ($value != null && strlen($value) <= 15) |
1276
|
|
|
{ |
1277
|
|
|
$length = strlen($value); |
1278
|
|
|
|
1279
|
|
|
// If it is fewer than 13 digits assume it is a local number |
1280
|
|
|
if ($length <= 12) |
1281
|
|
|
{ |
1282
|
|
|
$result = '.' . $value; |
1283
|
|
|
} |
1284
|
|
|
else |
1285
|
|
|
{ |
1286
|
|
|
// If it has 13 or more digits let's make a country code. |
1287
|
|
|
$cclen = $length - 12; |
1288
|
|
|
$result = substr($value, 0, $cclen) . '.' . substr($value, $cclen); |
1289
|
|
|
} |
1290
|
|
|
} |
1291
|
|
|
else |
1292
|
|
|
// If not let's not save anything. |
1293
|
|
|
{ |
1294
|
|
|
$result = ''; |
1295
|
|
|
} |
1296
|
|
|
} |
1297
|
|
|
|
1298
|
|
|
$return = $result; |
1299
|
|
|
|
1300
|
|
|
break; |
1301
|
|
|
|
1302
|
|
|
default: |
1303
|
|
|
// Check for a callback filter. |
1304
|
|
|
if (strpos($filter, '::') !== false && is_callable(explode('::', $filter))) |
1305
|
|
|
{ |
1306
|
|
|
$return = call_user_func(explode('::', $filter), $value); |
1307
|
|
|
} |
1308
|
|
|
elseif (function_exists($filter)) |
1309
|
|
|
// Filter using a callback function if specified. |
1310
|
|
|
{ |
1311
|
|
|
$return = call_user_func($filter, $value); |
1312
|
|
|
} |
1313
|
|
|
else |
1314
|
|
|
// Filter using InputFilter. All HTML code is filtered by default. |
1315
|
|
|
{ |
1316
|
|
|
$filterInput = new Filter\InputFilter; |
1317
|
|
|
$return = $filterInput->clean($value, $filter); |
1318
|
|
|
} |
1319
|
|
|
break; |
1320
|
|
|
} |
1321
|
|
|
|
1322
|
|
|
return $return; |
1323
|
|
|
} |
1324
|
|
|
|
1325
|
|
|
/** |
1326
|
|
|
* Method to get a form field represented as an XML element object. |
1327
|
|
|
* |
1328
|
|
|
* @param string $name The name of the form field. |
1329
|
|
|
* @param string $group The optional dot-separated form group path on which to find the field. |
1330
|
|
|
* |
1331
|
|
|
* @return mixed The XML element object for the field or boolean false on error. |
1332
|
|
|
* |
1333
|
|
|
* @since 1.0 |
1334
|
|
|
*/ |
1335
|
|
|
protected function findField($name, $group = null) |
1336
|
|
|
{ |
1337
|
|
|
$element = false; |
1338
|
|
|
$fields = array(); |
1339
|
|
|
|
1340
|
|
|
// Make sure there is a valid Form XML document. |
1341
|
|
|
if (!($this->xml instanceof \SimpleXMLElement)) |
1342
|
|
|
{ |
1343
|
|
|
return false; |
1344
|
|
|
} |
1345
|
|
|
|
1346
|
|
|
// Let's get the appropriate field element based on the method arguments. |
1347
|
|
|
if ($group) |
|
|
|
|
1348
|
|
|
{ |
1349
|
|
|
// Get the fields elements for a given group. |
1350
|
|
|
$elements = &$this->findGroup($group); |
1351
|
|
|
|
1352
|
|
|
// Get all of the field elements with the correct name for the fields elements. |
1353
|
|
View Code Duplication |
foreach ($elements as $element) |
|
|
|
|
1354
|
|
|
{ |
1355
|
|
|
// If there are matching field elements add them to the fields array. |
1356
|
|
|
if ($tmp = $element->xpath('descendant::field[@name="' . $name . '"]')) |
1357
|
|
|
{ |
1358
|
|
|
$fields = array_merge($fields, $tmp); |
1359
|
|
|
} |
1360
|
|
|
} |
1361
|
|
|
|
1362
|
|
|
// Make sure something was found. |
1363
|
|
|
if (!$fields) |
|
|
|
|
1364
|
|
|
{ |
1365
|
|
|
return false; |
1366
|
|
|
} |
1367
|
|
|
|
1368
|
|
|
// Use the first correct match in the given group. |
1369
|
|
|
$groupNames = explode('.', $group); |
1370
|
|
|
|
1371
|
|
View Code Duplication |
foreach ($fields as &$field) |
|
|
|
|
1372
|
|
|
{ |
1373
|
|
|
// Get the group names as strings for ancestor fields elements. |
1374
|
|
|
$attrs = $field->xpath('ancestor::fields[@name]/@name'); |
1375
|
|
|
$names = array_map('strval', $attrs ? $attrs : array()); |
1376
|
|
|
|
1377
|
|
|
// If the field is in the exact group use it and break out of the loop. |
1378
|
|
|
if ($names == (array) $groupNames) |
1379
|
|
|
{ |
1380
|
|
|
$element = &$field; |
1381
|
|
|
break; |
1382
|
|
|
} |
1383
|
|
|
} |
1384
|
|
|
} |
1385
|
|
|
else |
1386
|
|
|
{ |
1387
|
|
|
// Get an array of fields with the correct name. |
1388
|
|
|
$fields = $this->xml->xpath('//field[@name="' . $name . '"]'); |
1389
|
|
|
|
1390
|
|
|
// Make sure something was found. |
1391
|
|
|
if (!$fields) |
|
|
|
|
1392
|
|
|
{ |
1393
|
|
|
return false; |
1394
|
|
|
} |
1395
|
|
|
|
1396
|
|
|
// Search through the fields for the right one. |
1397
|
|
|
foreach ($fields as &$field) |
1398
|
|
|
{ |
1399
|
|
|
// If we find an ancestor fields element with a group name then it isn't what we want. |
1400
|
|
|
if ($field->xpath('ancestor::fields[@name]')) |
1401
|
|
|
{ |
1402
|
|
|
continue; |
1403
|
|
|
} |
1404
|
|
|
else |
1405
|
|
|
// Found it! |
1406
|
|
|
{ |
1407
|
|
|
$element = &$field; |
1408
|
|
|
break; |
1409
|
|
|
} |
1410
|
|
|
} |
1411
|
|
|
} |
1412
|
|
|
|
1413
|
|
|
return $element; |
1414
|
|
|
} |
1415
|
|
|
|
1416
|
|
|
/** |
1417
|
|
|
* Method to get an array of <field /> elements from the form XML document which are |
1418
|
|
|
* in a specified fieldset by name. |
1419
|
|
|
* |
1420
|
|
|
* @param string $name The name of the fieldset. |
1421
|
|
|
* |
1422
|
|
|
* @return mixed Boolean false on error or array of SimpleXMLElement objects. |
1423
|
|
|
* |
1424
|
|
|
* @since 1.0 |
1425
|
|
|
*/ |
1426
|
|
|
protected function &findFieldsByFieldset($name) |
1427
|
|
|
{ |
1428
|
|
|
$false = false; |
1429
|
|
|
|
1430
|
|
|
// Make sure there is a valid Form XML document. |
1431
|
|
|
if (!($this->xml instanceof \SimpleXMLElement)) |
1432
|
|
|
{ |
1433
|
|
|
return $false; |
1434
|
|
|
} |
1435
|
|
|
|
1436
|
|
|
/* |
1437
|
|
|
* Get an array of <field /> elements that are underneath a <fieldset /> element |
1438
|
|
|
* with the appropriate name attribute (unless they are the decendents |
1439
|
|
|
* of another <field /> element for some reaon), and also any <field /> elements |
1440
|
|
|
* with the appropriate fieldset attribute. |
1441
|
|
|
*/ |
1442
|
|
|
$fields = $this->xml->xpath('//fieldset[@name="' . $name . '"]//field[not(ancestor::field)] | //field[@fieldset="' . $name . '"]'); |
1443
|
|
|
|
1444
|
|
|
return $fields; |
1445
|
|
|
} |
1446
|
|
|
|
1447
|
|
|
/** |
1448
|
|
|
* Method to get an array of <field /> elements from the form XML document which are |
1449
|
|
|
* in a control group by name. |
1450
|
|
|
* |
1451
|
|
|
* @param mixed $group The optional dot-separated form group path on which to find the fields. |
1452
|
|
|
* Null will return all fields. False will return fields not in a group. |
1453
|
|
|
* @param boolean $nested True to also include fields in nested groups that are inside of the |
1454
|
|
|
* group for which to find fields. |
1455
|
|
|
* |
1456
|
|
|
* @return mixed Boolean false on error or array of SimpleXMLElement objects. |
1457
|
|
|
* |
1458
|
|
|
* @since 1.0 |
1459
|
|
|
*/ |
1460
|
|
|
protected function &findFieldsByGroup($group = null, $nested = false) |
1461
|
|
|
{ |
1462
|
|
|
$false = false; |
1463
|
|
|
$fields = array(); |
1464
|
|
|
|
1465
|
|
|
// Make sure there is a valid Form XML document. |
1466
|
|
|
if (!($this->xml instanceof \SimpleXMLElement)) |
1467
|
|
|
{ |
1468
|
|
|
return $false; |
1469
|
|
|
} |
1470
|
|
|
|
1471
|
|
|
// Get only fields in a specific group? |
1472
|
|
|
if ($group) |
1473
|
|
|
{ |
1474
|
|
|
// Get the fields elements for a given group. |
1475
|
|
|
$elements = &$this->findGroup($group); |
1476
|
|
|
|
1477
|
|
|
// Get all of the field elements for the fields elements. |
1478
|
|
|
foreach ($elements as $element) |
|
|
|
|
1479
|
|
|
{ |
1480
|
|
|
// If there are field elements add them to the return result. |
1481
|
|
|
if ($tmp = $element->xpath('descendant::field')) |
1482
|
|
|
{ |
1483
|
|
|
// If we also want fields in nested groups then just merge the arrays. |
1484
|
|
|
if ($nested) |
1485
|
|
|
{ |
1486
|
|
|
$fields = array_merge($fields, $tmp); |
1487
|
|
|
} |
1488
|
|
|
else |
1489
|
|
|
// If we want to exclude nested groups then we need to check each field. |
1490
|
|
|
{ |
1491
|
|
|
$groupNames = explode('.', $group); |
1492
|
|
|
|
1493
|
|
View Code Duplication |
foreach ($tmp as $field) |
|
|
|
|
1494
|
|
|
{ |
1495
|
|
|
// Get the names of the groups that the field is in. |
1496
|
|
|
$attrs = $field->xpath('ancestor::fields[@name]/@name'); |
1497
|
|
|
$names = array_map('strval', $attrs ? $attrs : array()); |
1498
|
|
|
|
1499
|
|
|
// If the field is in the specific group then add it to the return list. |
1500
|
|
|
if ($names == (array) $groupNames) |
1501
|
|
|
{ |
1502
|
|
|
$fields = array_merge($fields, array($field)); |
1503
|
|
|
} |
1504
|
|
|
} |
1505
|
|
|
} |
1506
|
|
|
} |
1507
|
|
|
} |
1508
|
|
|
} |
1509
|
|
|
elseif ($group === false) |
1510
|
|
|
{ |
1511
|
|
|
// Get only field elements not in a group. |
1512
|
|
|
$fields = $this->xml->xpath('descendant::fields[not(@name)]/field | descendant::fields[not(@name)]/fieldset/field '); |
1513
|
|
|
} |
1514
|
|
|
else |
1515
|
|
|
{ |
1516
|
|
|
// Get an array of all the <field /> elements. |
1517
|
|
|
$fields = $this->xml->xpath('//field'); |
1518
|
|
|
} |
1519
|
|
|
|
1520
|
|
|
return $fields; |
1521
|
|
|
} |
1522
|
|
|
|
1523
|
|
|
/** |
1524
|
|
|
* Method to get a form field group represented as an XML element object. |
1525
|
|
|
* |
1526
|
|
|
* @param string $group The dot-separated form group path on which to find the group. |
1527
|
|
|
* |
1528
|
|
|
* @return mixed An array of XML element objects for the group or boolean false on error. |
1529
|
|
|
* |
1530
|
|
|
* @since 1.0 |
1531
|
|
|
*/ |
1532
|
|
|
protected function &findGroup($group) |
1533
|
|
|
{ |
1534
|
|
|
$false = false; |
1535
|
|
|
$groups = array(); |
1536
|
|
|
$tmp = array(); |
1537
|
|
|
|
1538
|
|
|
// Make sure there is a valid Form XML document. |
1539
|
|
|
if (!($this->xml instanceof \SimpleXMLElement)) |
1540
|
|
|
{ |
1541
|
|
|
return $false; |
1542
|
|
|
} |
1543
|
|
|
|
1544
|
|
|
// Make sure there is actually a group to find. |
1545
|
|
|
$group = explode('.', $group); |
1546
|
|
|
|
1547
|
|
|
if (!empty($group)) |
1548
|
|
|
{ |
1549
|
|
|
// Get any fields elements with the correct group name. |
1550
|
|
|
$elements = $this->xml->xpath('//fields[@name="' . (string) $group[0] . '"]'); |
1551
|
|
|
|
1552
|
|
|
// Check to make sure that there are no parent groups for each element. |
1553
|
|
|
foreach ($elements as $element) |
1554
|
|
|
{ |
1555
|
|
|
if (!$element->xpath('ancestor::fields[@name]')) |
1556
|
|
|
{ |
1557
|
|
|
$tmp[] = $element; |
1558
|
|
|
} |
1559
|
|
|
} |
1560
|
|
|
|
1561
|
|
|
// Iterate through the nested groups to find any matching form field groups. |
1562
|
|
|
for ($i = 1, $n = count($group); $i < $n; $i++) |
1563
|
|
|
{ |
1564
|
|
|
// Initialise some loop variables. |
1565
|
|
|
$validNames = array_slice($group, 0, $i + 1); |
1566
|
|
|
$current = $tmp; |
1567
|
|
|
$tmp = array(); |
1568
|
|
|
|
1569
|
|
|
// Check to make sure that there are no parent groups for each element. |
1570
|
|
|
foreach ($current as $element) |
1571
|
|
|
{ |
1572
|
|
|
// Get any fields elements with the correct group name. |
1573
|
|
|
$children = $element->xpath('descendant::fields[@name="' . (string) $group[$i] . '"]'); |
1574
|
|
|
|
1575
|
|
|
// For the found fields elements validate that they are in the correct groups. |
1576
|
|
View Code Duplication |
foreach ($children as $fields) |
|
|
|
|
1577
|
|
|
{ |
1578
|
|
|
// Get the group names as strings for ancestor fields elements. |
1579
|
|
|
$attrs = $fields->xpath('ancestor-or-self::fields[@name]/@name'); |
1580
|
|
|
$names = array_map('strval', $attrs ? $attrs : array()); |
1581
|
|
|
|
1582
|
|
|
// If the group names for the fields element match the valid names at this |
1583
|
|
|
// level add the fields element. |
1584
|
|
|
if ($validNames == $names) |
1585
|
|
|
{ |
1586
|
|
|
$tmp[] = $fields; |
1587
|
|
|
} |
1588
|
|
|
} |
1589
|
|
|
} |
1590
|
|
|
} |
1591
|
|
|
|
1592
|
|
|
// Only include valid XML objects. |
1593
|
|
|
foreach ($tmp as $element) |
1594
|
|
|
{ |
1595
|
|
|
if ($element instanceof \SimpleXMLElement) |
1596
|
|
|
{ |
1597
|
|
|
$groups[] = $element; |
1598
|
|
|
} |
1599
|
|
|
} |
1600
|
|
|
} |
1601
|
|
|
|
1602
|
|
|
return $groups; |
1603
|
|
|
} |
1604
|
|
|
|
1605
|
|
|
/** |
1606
|
|
|
* Method to load, setup and return a FormField object based on field data. |
1607
|
|
|
* |
1608
|
|
|
* @param string $element The XML element object representation of the form field. |
1609
|
|
|
* @param string $group The optional dot-separated form group path on which to find the field. |
1610
|
|
|
* @param mixed $value The optional value to use as the default for the field. |
1611
|
|
|
* |
1612
|
|
|
* @return mixed The FormField object for the field or boolean false on error. |
1613
|
|
|
* |
1614
|
|
|
* @since 1.0 |
1615
|
|
|
*/ |
1616
|
|
|
protected function loadField($element, $group = null, $value = null) |
1617
|
|
|
{ |
1618
|
|
|
// Make sure there is a valid SimpleXMLElement. |
1619
|
|
|
if (!($element instanceof \SimpleXMLElement)) |
1620
|
|
|
{ |
1621
|
|
|
return false; |
1622
|
|
|
} |
1623
|
|
|
|
1624
|
|
|
// Get the field type. |
1625
|
|
|
$type = $element['type'] ? (string) $element['type'] : 'text'; |
1626
|
|
|
|
1627
|
|
|
// Load the Field object for the field. |
1628
|
|
|
$field = FormHelper::loadFieldType($type); |
|
|
|
|
1629
|
|
|
|
1630
|
|
|
// If the object could not be loaded, get a text field object. |
1631
|
|
|
if ($field === false) |
1632
|
|
|
{ |
1633
|
|
|
$field = FormHelper::loadFieldType('text'); |
|
|
|
|
1634
|
|
|
|
1635
|
|
|
if ($field === false) |
1636
|
|
|
{ |
1637
|
|
|
return false; |
1638
|
|
|
} |
1639
|
|
|
} |
1640
|
|
|
|
1641
|
|
|
/* |
1642
|
|
|
* Get the value for the form field if not set. |
1643
|
|
|
* Default to the translated version of the 'default' attribute |
1644
|
|
|
* if 'translate_default' attribute if set to 'true' or '1' |
1645
|
|
|
* else the value of the 'default' attribute for the field. |
1646
|
|
|
*/ |
1647
|
|
|
if ($value === null) |
1648
|
|
|
{ |
1649
|
|
|
$default = (string) $element['default']; |
1650
|
|
|
|
1651
|
|
|
if (($translate = $element['translate_default']) && ((string) $translate == 'true' || (string) $translate == '1')) |
1652
|
|
|
{ |
1653
|
|
|
$lang = Language::getInstance(); |
|
|
|
|
1654
|
|
|
|
1655
|
|
|
if ($lang->hasKey($default)) |
1656
|
|
|
{ |
1657
|
|
|
$debug = $lang->setDebug(false); |
1658
|
|
|
$default = Text::_($default); |
|
|
|
|
1659
|
|
|
$lang->setDebug($debug); |
1660
|
|
|
} |
1661
|
|
|
else |
1662
|
|
|
{ |
1663
|
|
|
$default = Text::_($default); |
|
|
|
|
1664
|
|
|
} |
1665
|
|
|
} |
1666
|
|
|
|
1667
|
|
|
$value = $this->getValue((string) $element['name'], $group, $default); |
1668
|
|
|
} |
1669
|
|
|
|
1670
|
|
|
// Setup the Field object. |
1671
|
|
|
$field->setForm($this); |
1672
|
|
|
|
1673
|
|
|
if ($field->setup($element, $value, $group)) |
1674
|
|
|
{ |
1675
|
|
|
return $field; |
1676
|
|
|
} |
1677
|
|
|
else |
1678
|
|
|
{ |
1679
|
|
|
return false; |
1680
|
|
|
} |
1681
|
|
|
} |
1682
|
|
|
|
1683
|
|
|
/** |
1684
|
|
|
* Method to synchronize any field, form or rule paths contained in the XML document. |
1685
|
|
|
* |
1686
|
|
|
* @return boolean True on success. |
1687
|
|
|
* |
1688
|
|
|
* @since 1.0 |
1689
|
|
|
* @todo Maybe we should receive all addXXXpaths attributes at once? |
1690
|
|
|
*/ |
1691
|
|
|
protected function syncPaths() |
1692
|
|
|
{ |
1693
|
|
|
// Make sure there is a valid Form XML document. |
1694
|
|
|
if (!($this->xml instanceof \SimpleXMLElement)) |
1695
|
|
|
{ |
1696
|
|
|
return false; |
1697
|
|
|
} |
1698
|
|
|
|
1699
|
|
|
// Get any addfieldpath attributes from the form definition. |
1700
|
|
|
$paths = $this->xml->xpath('//*[@addfieldpath]/@addfieldpath'); |
1701
|
|
|
$paths = array_map('strval', $paths ? $paths : array()); |
1702
|
|
|
|
1703
|
|
|
// Add the field paths. |
1704
|
|
View Code Duplication |
foreach ($paths as $path) |
|
|
|
|
1705
|
|
|
{ |
1706
|
|
|
$path = JPATH_ROOT . '/' . ltrim($path, '/\\'); |
1707
|
|
|
FormHelper::addFieldPath($path); |
|
|
|
|
1708
|
|
|
} |
1709
|
|
|
|
1710
|
|
|
// Get any addformpath attributes from the form definition. |
1711
|
|
|
$paths = $this->xml->xpath('//*[@addformpath]/@addformpath'); |
1712
|
|
|
$paths = array_map('strval', $paths ? $paths : array()); |
1713
|
|
|
|
1714
|
|
|
// Add the form paths. |
1715
|
|
View Code Duplication |
foreach ($paths as $path) |
|
|
|
|
1716
|
|
|
{ |
1717
|
|
|
$path = JPATH_ROOT . '/' . ltrim($path, '/\\'); |
1718
|
|
|
FormHelper::addFormPath($path); |
1719
|
|
|
} |
1720
|
|
|
|
1721
|
|
|
// Get any addrulepath attributes from the form definition. |
1722
|
|
|
$paths = $this->xml->xpath('//*[@addrulepath]/@addrulepath'); |
1723
|
|
|
$paths = array_map('strval', $paths ? $paths : array()); |
1724
|
|
|
|
1725
|
|
|
// Add the rule paths. |
1726
|
|
View Code Duplication |
foreach ($paths as $path) |
|
|
|
|
1727
|
|
|
{ |
1728
|
|
|
$path = JPATH_ROOT . '/' . ltrim($path, '/\\'); |
1729
|
|
|
FormHelper::addRulePath($path); |
|
|
|
|
1730
|
|
|
} |
1731
|
|
|
|
1732
|
|
|
return true; |
1733
|
|
|
} |
1734
|
|
|
|
1735
|
|
|
/** |
1736
|
|
|
* Method to validate a Field object based on field data. |
1737
|
|
|
* |
1738
|
|
|
* @param \SimpleXMLElement $element The XML element object representation of the form field. |
1739
|
|
|
* @param string $group The optional dot-separated form group path on which to find the field. |
1740
|
|
|
* @param mixed $value The optional value to use as the default for the field. |
1741
|
|
|
* @param Registry $input An optional Registry object with the entire data set to validate |
1742
|
|
|
* against the entire form. |
1743
|
|
|
* |
1744
|
|
|
* @return mixed Boolean true if field value is valid, Exception on failure. |
1745
|
|
|
* |
1746
|
|
|
* @since 1.0 |
1747
|
|
|
* @throws \InvalidArgumentException |
1748
|
|
|
* @throws \UnexpectedValueException |
1749
|
|
|
*/ |
1750
|
|
|
protected function validateField(\SimpleXMLElement $element, $group = null, $value = null, Registry $input = null) |
1751
|
|
|
{ |
1752
|
|
|
$valid = true; |
1753
|
|
|
|
1754
|
|
|
// Check if the field is required. |
1755
|
|
|
$required = ((string) $element['required'] == 'true' || (string) $element['required'] == 'required'); |
1756
|
|
|
|
1757
|
|
|
if ($required) |
1758
|
|
|
{ |
1759
|
|
|
// If the field is required and the value is empty return an error message. |
1760
|
|
|
if (($value === '') || ($value === null)) |
1761
|
|
|
{ |
1762
|
|
View Code Duplication |
if ($element['label']) |
|
|
|
|
1763
|
|
|
{ |
1764
|
|
|
$message = Text::_($element['label']); |
|
|
|
|
1765
|
|
|
} |
1766
|
|
|
else |
1767
|
|
|
{ |
1768
|
|
|
$message = Text::_($element['name']); |
|
|
|
|
1769
|
|
|
} |
1770
|
|
|
|
1771
|
|
|
$message = Text::sprintf('JLIB_FORM_VALIDATE_FIELD_REQUIRED', $message); |
1772
|
|
|
|
1773
|
|
|
return new \RuntimeException($message); |
1774
|
|
|
} |
1775
|
|
|
} |
1776
|
|
|
|
1777
|
|
|
// Get the field validation rule. |
1778
|
|
|
if ($type = (string) $element['validate']) |
1779
|
|
|
{ |
1780
|
|
|
// Load the Rule object for the field. |
1781
|
|
|
$rule = FormHelper::loadRuleType($type); |
|
|
|
|
1782
|
|
|
|
1783
|
|
|
// If the object could not be loaded return an error message. |
1784
|
|
|
if ($rule === false) |
1785
|
|
|
{ |
1786
|
|
|
throw new \UnexpectedValueException(sprintf('%s::validateField() rule `%s` missing.', get_class($this), $type)); |
1787
|
|
|
} |
1788
|
|
|
|
1789
|
|
|
// Run the field validation rule test. |
1790
|
|
|
$valid = $rule->test($element, $value, $group, $input, $this); |
1791
|
|
|
|
1792
|
|
|
// Check for an error in the validation test. |
1793
|
|
|
if ($valid instanceof \Exception) |
1794
|
|
|
{ |
1795
|
|
|
return $valid; |
1796
|
|
|
} |
1797
|
|
|
} |
1798
|
|
|
|
1799
|
|
|
// Check if the field is valid. |
1800
|
|
|
if ($valid === false) |
1801
|
|
|
{ |
1802
|
|
|
// Does the field have a defined error message? |
1803
|
|
|
$message = (string) $element['message']; |
1804
|
|
|
|
1805
|
|
|
if ($message) |
1806
|
|
|
{ |
1807
|
|
|
$message = Text::_($element['message']); |
|
|
|
|
1808
|
|
|
|
1809
|
|
|
return new \UnexpectedValueException($message); |
1810
|
|
|
} |
1811
|
|
View Code Duplication |
else |
|
|
|
|
1812
|
|
|
{ |
1813
|
|
|
$message = Text::_($element['label']); |
|
|
|
|
1814
|
|
|
$message = Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', $message); |
1815
|
|
|
|
1816
|
|
|
return new \UnexpectedValueException($message); |
1817
|
|
|
} |
1818
|
|
|
} |
1819
|
|
|
|
1820
|
|
|
return true; |
1821
|
|
|
} |
1822
|
|
|
|
1823
|
|
|
/** |
1824
|
|
|
* Method to get an instance of a form. |
1825
|
|
|
* |
1826
|
|
|
* @param string $name The name of the form. |
1827
|
|
|
* @param string $data The name of an XML file or string to load as the form definition. |
1828
|
|
|
* @param array $options An array of form options. |
1829
|
|
|
* @param string $replace Flag to toggle whether form fields should be replaced if a field |
1830
|
|
|
* already exists with the same group/name. |
1831
|
|
|
* @param string $xpath An optional xpath to search for the fields. |
1832
|
|
|
* |
1833
|
|
|
* @return Form Instance of this class. |
1834
|
|
|
* |
1835
|
|
|
* @since 1.0 |
1836
|
|
|
* @throws \InvalidArgumentException if no data provided. |
1837
|
|
|
* @throws \RuntimeException if the form could not be loaded. |
1838
|
|
|
*/ |
1839
|
|
|
public static function getInstance($name, $data = null, $options = array(), $replace = true, $xpath = false) |
1840
|
|
|
{ |
1841
|
|
|
// Reference to array with form instances |
1842
|
|
|
$forms = &self::$forms; |
1843
|
|
|
|
1844
|
|
|
// Only instantiate the form if it does not already exist. |
1845
|
|
|
if (!isset($forms[$name])) |
1846
|
|
|
{ |
1847
|
|
|
$data = trim($data); |
1848
|
|
|
|
1849
|
|
|
if (empty($data)) |
1850
|
|
|
{ |
1851
|
|
|
throw new \InvalidArgumentException(sprintf('%s(name, *%s*)', __METHOD__, gettype($data))); |
1852
|
|
|
} |
1853
|
|
|
|
1854
|
|
|
// Instantiate the form. |
1855
|
|
|
$forms[$name] = new self($name, $options); |
|
|
|
|
1856
|
|
|
|
1857
|
|
|
// Load the data. |
1858
|
|
|
if (substr(trim($data), 0, 1) == '<') |
1859
|
|
|
{ |
1860
|
|
View Code Duplication |
if ($forms[$name]->load($data, $replace, $xpath) == false) |
|
|
|
|
1861
|
|
|
{ |
1862
|
|
|
throw new \RuntimeException(__METHOD__ . ' could not load form'); |
1863
|
|
|
} |
1864
|
|
|
} |
1865
|
|
View Code Duplication |
else |
|
|
|
|
1866
|
|
|
{ |
1867
|
|
|
if ($forms[$name]->loadFile($data, $replace, $xpath) == false) |
|
|
|
|
1868
|
|
|
{ |
1869
|
|
|
throw new \RuntimeException(__METHOD__ . ' could not load file'); |
1870
|
|
|
} |
1871
|
|
|
} |
1872
|
|
|
} |
1873
|
|
|
|
1874
|
|
|
return $forms[$name]; |
1875
|
|
|
} |
1876
|
|
|
|
1877
|
|
|
/** |
1878
|
|
|
* Adds a new child SimpleXMLElement node to the source. |
1879
|
|
|
* |
1880
|
|
|
* @param \SimpleXMLElement $source The source element on which to append. |
1881
|
|
|
* @param \SimpleXMLElement $new The new element to append. |
1882
|
|
|
* |
1883
|
|
|
* @return void |
1884
|
|
|
* |
1885
|
|
|
* @since 1.0 |
1886
|
|
|
*/ |
1887
|
|
|
protected static function addNode(\SimpleXMLElement $source, \SimpleXMLElement $new) |
1888
|
|
|
{ |
1889
|
|
|
// Add the new child node. |
1890
|
|
|
$node = $source->addChild($new->getName(), trim($new)); |
1891
|
|
|
|
1892
|
|
|
// Add the attributes of the child node. |
1893
|
|
|
foreach ($new->attributes() as $name => $value) |
1894
|
|
|
{ |
1895
|
|
|
$node->addAttribute($name, $value); |
1896
|
|
|
} |
1897
|
|
|
|
1898
|
|
|
// Add any children of the new node. |
1899
|
|
|
foreach ($new->children() as $child) |
1900
|
|
|
{ |
1901
|
|
|
self::addNode($node, $child); |
1902
|
|
|
} |
1903
|
|
|
} |
1904
|
|
|
|
1905
|
|
|
/** |
1906
|
|
|
* Update the attributes of a child node |
1907
|
|
|
* |
1908
|
|
|
* @param \SimpleXMLElement $source The source element on which to append the attributes |
1909
|
|
|
* @param \SimpleXMLElement $new The new element to append |
1910
|
|
|
* |
1911
|
|
|
* @return void |
1912
|
|
|
* |
1913
|
|
|
* @since 1.0 |
1914
|
|
|
*/ |
1915
|
|
|
protected static function mergeNode(\SimpleXMLElement $source, \SimpleXMLElement $new) |
1916
|
|
|
{ |
1917
|
|
|
// Update the attributes of the child node. |
1918
|
|
View Code Duplication |
foreach ($new->attributes() as $name => $value) |
|
|
|
|
1919
|
|
|
{ |
1920
|
|
|
if (isset($source[$name])) |
1921
|
|
|
{ |
1922
|
|
|
$source[$name] = (string) $value; |
1923
|
|
|
} |
1924
|
|
|
else |
1925
|
|
|
{ |
1926
|
|
|
$source->addAttribute($name, $value); |
1927
|
|
|
} |
1928
|
|
|
} |
1929
|
|
|
} |
1930
|
|
|
|
1931
|
|
|
/** |
1932
|
|
|
* Merges new elements into a source <fields> element. |
1933
|
|
|
* |
1934
|
|
|
* @param \SimpleXMLElement $source The source element. |
1935
|
|
|
* @param \SimpleXMLElement $new The new element to merge. |
1936
|
|
|
* |
1937
|
|
|
* @return void |
1938
|
|
|
* |
1939
|
|
|
* @since 1.0 |
1940
|
|
|
*/ |
1941
|
|
|
protected static function mergeNodes(\SimpleXMLElement $source, \SimpleXMLElement $new) |
1942
|
|
|
{ |
1943
|
|
|
// The assumption is that the inputs are at the same relative level. |
1944
|
|
|
// So we just have to scan the children and deal with them. |
1945
|
|
|
|
1946
|
|
|
// Update the attributes of the child node. |
1947
|
|
View Code Duplication |
foreach ($new->attributes() as $name => $value) |
|
|
|
|
1948
|
|
|
{ |
1949
|
|
|
if (isset($source[$name])) |
1950
|
|
|
{ |
1951
|
|
|
$source[$name] = (string) $value; |
1952
|
|
|
} |
1953
|
|
|
else |
1954
|
|
|
{ |
1955
|
|
|
$source->addAttribute($name, $value); |
1956
|
|
|
} |
1957
|
|
|
} |
1958
|
|
|
|
1959
|
|
|
foreach ($new->children() as $child) |
1960
|
|
|
{ |
1961
|
|
|
$type = $child->getName(); |
1962
|
|
|
$name = $child['name']; |
1963
|
|
|
|
1964
|
|
|
// Does this node exist? |
1965
|
|
|
$fields = $source->xpath($type . '[@name="' . $name . '"]'); |
1966
|
|
|
|
1967
|
|
|
if (empty($fields)) |
1968
|
|
|
{ |
1969
|
|
|
// This node does not exist, so add it. |
1970
|
|
|
self::addNode($source, $child); |
1971
|
|
|
} |
1972
|
|
|
else |
1973
|
|
|
{ |
1974
|
|
|
// This node does exist. |
1975
|
|
|
switch ($type) |
1976
|
|
|
{ |
1977
|
|
|
case 'field': |
1978
|
|
|
self::mergeNode($fields[0], $child); |
1979
|
|
|
break; |
1980
|
|
|
|
1981
|
|
|
default: |
1982
|
|
|
self::mergeNodes($fields[0], $child); |
1983
|
|
|
break; |
1984
|
|
|
} |
1985
|
|
|
} |
1986
|
|
|
} |
1987
|
|
|
} |
1988
|
|
|
} |
1989
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.