Form::getFieldset()   B
last analyzed

Complexity

Conditions 6
Paths 8

Size

Total Lines 38

Duplication

Lines 38
Ratio 100 %

Importance

Changes 0
Metric Value
cc 6
nc 8
nop 1
dl 38
loc 38
rs 8.6897
c 0
b 0
f 0
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
336
	{
337
		$fields = array();
338
339
		// Get all of the field elements in the fieldset.
340
		if ($set)
0 ignored issues
show
Bug Best Practice introduced by
The expression $set of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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)
0 ignored issues
show
Bug Best Practice introduced by
The expression $group of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
395
		{
396
			// Get the fields elements for a given group.
397
			$elements = &$this->findGroup($group);
398
399 View Code Duplication
			foreach ($elements as &$element)
0 ignored issues
show
Bug introduced by
The expression $elements of type false|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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)
0 ignored issues
show
Bug Best Practice introduced by
The expression $group of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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'))
0 ignored issues
show
Bug Best Practice introduced by
The expression $xpath of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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)
0 ignored issues
show
Bug Best Practice introduced by
The expression $xpath of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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)
0 ignored issues
show
Bug Best Practice introduced by
The expression $file of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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)
0 ignored issues
show
Bug introduced by
The expression $elements of type false|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
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)
0 ignored issues
show
Bug Best Practice introduced by
The expression $group of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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)
0 ignored issues
show
Bug Best Practice introduced by
The expression $group of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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;
0 ignored issues
show
Unused Code introduced by
$value is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
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);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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'])
0 ignored issues
show
Bug Best Practice introduced by
The expression $protocol of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1190
					|| (!$element['type'] == 'url' && !$protocol))
0 ignored issues
show
Bug Best Practice introduced by
The expression $protocol of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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;
0 ignored issues
show
Bug introduced by
The method root() does not seem to exist on object<Joomla\Uri\Uri>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
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'])
0 ignored issues
show
Bug Best Practice introduced by
The expression $protocol of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1207
				{
1208
					$host = Uri::getInstance('SERVER')->gethost();
0 ignored issues
show
Bug introduced by
The method getInstance() does not seem to exist on object<Joomla\Uri\Uri>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
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;
0 ignored issues
show
Bug introduced by
The method root() does not seem to exist on object<Joomla\Uri\Uri>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
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)
0 ignored issues
show
Bug Best Practice introduced by
The expression $group of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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)
0 ignored issues
show
Bug introduced by
The expression $elements of type false|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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)
0 ignored issues
show
Bug Best Practice introduced by
The expression $fields of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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)
0 ignored issues
show
Bug Best Practice introduced by
The expression $fields of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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)
0 ignored issues
show
Bug introduced by
The expression $elements of type false|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The method Joomla\Form\FormHelper::loadFieldType() has been deprecated with message: 2.0 Field objects should be autoloaded

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1629
1630
		// If the object could not be loaded, get a text field object.
1631
		if ($field === false)
1632
		{
1633
			$field = FormHelper::loadFieldType('text');
0 ignored issues
show
Deprecated Code introduced by
The method Joomla\Form\FormHelper::loadFieldType() has been deprecated with message: 2.0 Field objects should be autoloaded

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
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();
0 ignored issues
show
Deprecated Code introduced by
The method Joomla\Language\Language::getInstance() has been deprecated with message: 2.0 Use LanguageFactory::getLanguage() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1654
1655
				if ($lang->hasKey($default))
1656
				{
1657
					$debug = $lang->setDebug(false);
1658
					$default = Text::_($default);
0 ignored issues
show
Deprecated Code introduced by
The method Joomla\Language\Text::_() has been deprecated with message: 2.0 Will be replaced with a `translate` method.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1659
					$lang->setDebug($debug);
1660
				}
1661
				else
1662
				{
1663
					$default = Text::_($default);
0 ignored issues
show
Deprecated Code introduced by
The method Joomla\Language\Text::_() has been deprecated with message: 2.0 Will be replaced with a `translate` method.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
1705
		{
1706
			$path = JPATH_ROOT . '/' . ltrim($path, '/\\');
1707
			FormHelper::addFieldPath($path);
0 ignored issues
show
Deprecated Code introduced by
The method Joomla\Form\FormHelper::addFieldPath() has been deprecated with message: 2.0 Field objects should be autoloaded

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
1727
		{
1728
			$path = JPATH_ROOT . '/' . ltrim($path, '/\\');
1729
			FormHelper::addRulePath($path);
0 ignored issues
show
Deprecated Code introduced by
The method Joomla\Form\FormHelper::addRulePath() has been deprecated with message: 2.0 Rule objects should be autoloaded

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
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'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
1763
				{
1764
					$message = Text::_($element['label']);
0 ignored issues
show
Deprecated Code introduced by
The method Joomla\Language\Text::_() has been deprecated with message: 2.0 Will be replaced with a `translate` method.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1765
				}
1766
				else
1767
				{
1768
					$message = Text::_($element['name']);
0 ignored issues
show
Deprecated Code introduced by
The method Joomla\Language\Text::_() has been deprecated with message: 2.0 Will be replaced with a `translate` method.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The method Joomla\Form\FormHelper::loadRuleType() has been deprecated with message: 2.0 Rule objects should be autoloaded

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
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']);
0 ignored issues
show
Deprecated Code introduced by
The method Joomla\Language\Text::_() has been deprecated with message: 2.0 Will be replaced with a `translate` method.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1808
1809
				return new \UnexpectedValueException($message);
1810
			}
1811 View Code Duplication
			else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
1812
			{
1813
				$message = Text::_($element['label']);
0 ignored issues
show
Deprecated Code introduced by
The method Joomla\Language\Text::_() has been deprecated with message: 2.0 Will be replaced with a `translate` method.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The class Joomla\Form\Form has been deprecated with message: The joomla/form package is deprecated

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
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)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
1861
				{
1862
					throw new \RuntimeException(__METHOD__ . ' could not load form');
1863
				}
1864
			}
1865 View Code Duplication
			else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
1866
			{
1867
				if ($forms[$name]->loadFile($data, $replace, $xpath) == false)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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