PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / 1.2.20
VikAppointments Services Booking Calendar v1.2.20
1.2.21 1.2.20 trunk 1.2.17 1.2.18 1.2.19
vikappointments / libraries / adapter / form / form.php
vikappointments / libraries / adapter / form Last commit date
fields 1 month ago field.php 1 month ago form.php 1 month ago
form.php
550 lines
1 <?php
2 /**
3 * @package VikWP - Libraries
4 * @subpackage adapter.form
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2023 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 JLoader::import('adapter.form.field');
15
16 /**
17 * Form class to handle XML forms.
18 *
19 * This class implements a robust API for constructing, populating, filtering, and validating forms.
20 * It uses XML definitions to construct form fields and a variety of field and rule classes to
21 * render and validate the form.
22 *
23 * @since 10.0
24 */
25 class JForm
26 {
27 /**
28 * A list of JForm instances
29 *
30 * @var array
31 */
32 protected static $forms = array();
33
34 /**
35 * The form XML definition.
36 *
37 * @var SimpleXMLElement
38 */
39 protected $xml;
40
41 /**
42 * The form name.
43 *
44 * @var string
45 * @since 10.1.20
46 */
47 protected $name;
48
49 /**
50 * The form options.
51 *
52 * @var array
53 * @since 10.1.20
54 */
55 protected $options;
56
57 /**
58 * Method to get an instance of a form.
59 *
60 * @param string $name The name of the form.
61 * @param string $data The name of an XML file or string to load as the form definition.
62 * @param array $options An array of form options.
63 *
64 * @return JForm A new JForm instance.
65 *
66 * @throws InvalidArgumentException if no data provided.
67 * @throws RuntimeException if the form could not be loaded.
68 */
69 public static function getInstance($name, $data = null, $options = array())
70 {
71 // only instantiate the form if it does not already exist
72 if (!isset(static::$forms[$name]))
73 {
74 if (is_string($data))
75 {
76 $data = trim($data);
77 }
78
79 if (empty($data))
80 {
81 // no provided data, throw an exception
82 throw new InvalidArgumentException(
83 sprintf('JForm::getInstance(%s, *%s*)',
84 $name,
85 gettype($data)
86 ),
87 400
88 );
89 }
90
91 // instantiate the form.
92 static::$forms[$name] = new static($name, $options);
93
94 // if the string starts with '<' load the XML as string
95 if ($data instanceof SimpleXMLElement || substr($data, 0, 1) == '<')
96 {
97 if (static::$forms[$name]->load($data) == false)
98 {
99 throw new RuntimeException('JForm::getInstance() could not load form.', 500);
100 }
101 }
102 else
103 {
104 if (static::$forms[$name]->loadFile($data) == false)
105 {
106 throw new RuntimeException(sprintf('JForm::getInstance() could not load file [%s].', $data), 500);
107 }
108 }
109 }
110
111 return static::$forms[$name];
112 }
113
114 /**
115 * Class constructor.
116 *
117 * @param string $name The name of the form.
118 * @param array $options An array of form options.
119 *
120 * @since 10.1.20
121 */
122 public function __construct($name, $options = array())
123 {
124 $this->name = $name;
125 $this->options = (array) $options;
126 }
127
128 /**
129 * Returns a list of fieldsets.
130 * If the name is provided, returns only the match.
131 *
132 * @param string $set The fieldset name.
133 *
134 * @return array A list of fieldsets.
135 */
136 public function getFieldset($set = null)
137 {
138 if (is_null($set))
139 {
140 // return all fieldsets
141 return $this->xml->xpath('//fieldset');
142 }
143
144 return $this->xml->xpath('//fieldset[@name="' . $set . '"]');
145 }
146
147 /**
148 * Returns a list of fields that match the query.
149 *
150 * @param string $val The field key value.
151 * @param string $key The field key in which to search (name by default).
152 *
153 * @return array The matching XML elements.
154 */
155 public function getFields($val = null, $key = 'name')
156 {
157 if (is_null($val))
158 {
159 // do not filter fields
160 return $this->xml->xpath('//field');
161 }
162
163 return $this->xml->xpath('//field[@' . $key . '="' . $val . '"]');
164 }
165
166 /**
167 * Returns the specified field.
168 *
169 * @param string $val The field key value.
170 * @param string $key The field key in which to search (name by default).
171 *
172 * @return mixed The field XML element on success, otherwise null.
173 *
174 * @uses getFields()
175 */
176 public function getField($val, $key = 'name')
177 {
178 $fields = $this->getFields($val, $key);
179
180 // return first element if any, otherwise null
181 return array_shift($fields);
182 }
183
184 /**
185 * Returns the loaded XML object.
186 *
187 * @return SimpleXMLElement
188 */
189 public function getXml()
190 {
191 return $this->xml;
192 }
193
194 /**
195 * Method to load the form description from an XML string or object.
196 *
197 * @param string $data The name of an XML string or object.
198 *
199 * @return boolean True on success, otherwise false.
200 */
201 public function load($data)
202 {
203 // if the data to load isn't already an XML element or string return false
204 if (!($data instanceof SimpleXMLElement) && !is_string($data))
205 {
206 return false;
207 }
208
209 // attempt to load the XML if a string
210 if (is_string($data))
211 {
212 $data = new SimpleXMLElement($data);
213
214 // make sure the XML loaded correctly
215 if (!$data)
216 {
217 return false;
218 }
219 }
220
221 // if we have no XML definition at this point let's make sure we get one
222 if (empty($this->xml))
223 {
224 $this->xml = $data;
225 }
226
227 // search for any fieldset that mentions "addfieldpath"
228 $nodes = $this->xml->xpath('//fieldset[@addfieldpath!=""]');
229
230 // iterate the nodes found
231 foreach ($nodes as $node)
232 {
233 $path = (string) $node->attributes()->addfieldpath;
234
235 /**
236 * Check if the [addfieldpath] attribute is defined using a Joomla path.
237 * For example:
238 * - /administrator/components/com_[option]/[path]
239 * - /components/com_[option]/[path]
240 *
241 * @since 10.1.16
242 */
243 if (preg_match("/^\/(administrator)?\/components\/com_([a-z0-9_]+)\/?(.*)/i", $path, $parts))
244 {
245 // starts with option name
246 $path = $parts[2] . '/';
247
248 if ($parts[1] === 'administrator')
249 {
250 // use admin folder
251 $path .= 'admin';
252 }
253 else
254 {
255 // use site folder
256 $path .= 'site';
257 }
258
259 // concat remaining path
260 $path .= '/' . $parts[3];
261 }
262 else if (!empty($this->options['client']))
263 {
264 /**
265 * Try to check if we have a caller within the options array.
266 *
267 * @since 10.1.20
268 */
269 $path = $this->options['client'] . '/' . ltrim($path, '/');
270 }
271
272 $path = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, ltrim($path, '/'));
273
274 // update form fields paths with a new path in which to search for custom handlers
275 JFormField::addIncludePath($path);
276 }
277
278 return true;
279 }
280
281 /**
282 * Method to load the form description from an XML file.
283 *
284 * @param string $file The filesystem path of an XML file.
285 *
286 * @return boolean True on success, otherwise false.
287 *
288 * @uses load()
289 */
290 public function loadFile($file)
291 {
292 // make sure the file exists
293 if (!is_file($file))
294 {
295 return false;
296 }
297
298 // attempt to load the XML file
299 $xml = simplexml_load_file($file);
300
301 return $this->load($xml);
302 }
303
304 /**
305 * Renders the form layout.
306 *
307 * @param object $data The object data to bind:
308 * Property name = Field name;
309 * Property value = Field value.
310 *
311 * @return string The HTML form layout.
312 *
313 * @uses renderFieldset()
314 */
315 public function renderForm($data = null)
316 {
317 return $this->renderFieldset(null, $data);
318 }
319
320 /**
321 * Renders the layout of the given form fieldset.
322 * If fieldset is not given, renders all the fieldsets.
323 *
324 * @param string $set The fielset name.
325 * @param object $data The object data to bind:
326 * Property name = Field name;
327 * Property value = Field value.
328 *
329 * @return string The HTML fieldset(s) layout.
330 *
331 * @uses getFieldset()
332 * @uses renderField()
333 */
334 public function renderFieldset($set = null, $data = null)
335 {
336 // get the fieldsets
337 $fieldsets = $this->getFieldset($set);
338
339 $html = '';
340
341 // iterate the fieldsets
342 foreach ($fieldsets as $fieldset)
343 {
344 $setname = (string) $fieldset->attributes()->name;
345 $setname = 'COM_MENUS_' . strtoupper($setname) . '_FIELDSET_LABEL';
346
347 /**
348 * Do not use a fieldset name in case the title should not be
349 * displayed or in case the translation is missing.
350 *
351 * @since 10.1.29
352 */
353 if ($fieldset->attributes()->hidden || JText::translate($setname) == $setname)
354 {
355 $setname = '';
356 }
357
358 // render fieldset opening
359 $html .= JHtml::fetch('layoutfile', 'html.form.fieldset.open')->render(array('name' => $setname));
360
361 // iterate the fieldset children
362 foreach ($fieldset->field as $field)
363 {
364 $attrs = $field->attributes();
365 $name = (string) $attrs->name;
366
367 $args = array();
368 $args['id'] = (string) $attrs->id;
369 $args['label'] = (string) $attrs->label;
370 $args['description'] = (string) $attrs->description;
371 $args['required'] = ((string) $attrs->required) === 'true';
372
373 /**
374 * Open control only in case the input shouldn't be hidden.
375 *
376 * @since 10.1.21
377 */
378 if ($attrs->type != 'hidden' && $attrs->type != 'spacer' && empty($attrs->hidden))
379 {
380 // open control
381 $html .= JHtml::fetch('layoutfile', 'html.form.control.open')->render($args);
382 }
383
384 // try to check if the value should be bound
385 $val = isset($data->{$name}) ? $data->{$name} : null;
386
387 // render field
388 $html .= $this->renderField($field, array('value' => $val));
389
390 /**
391 * Close control only in case the input shouldn't be hidden.
392 *
393 * @since 10.1.21
394 */
395 if ($attrs->type != 'hidden' && $attrs->type != 'spacer' && empty($attrs->hidden))
396 {
397 // close control
398 $html .= JHtml::fetch('layoutfile', 'html.form.control.close')->render($args);
399 }
400 }
401
402 // render fieldset closing
403 $html .= JHtml::fetch('layoutfile', 'html.form.fieldset.close')->render();
404 }
405
406 return $html;
407 }
408
409 /**
410 * Renders the specified field.
411 *
412 * @param mixed $field The field (or its name) to render.
413 * @param mixed $data The value (or a list of data) to bind.
414 *
415 * @return string The rendered field.
416 *
417 * @uses getField()
418 */
419 public function renderField($field, $data = null)
420 {
421 // get field XML element if the name was provided
422 if (is_string($field))
423 {
424 $field = $this->getField($field);
425 }
426
427 // get form field
428 $field = JFormField::getInstance($field);
429
430 /**
431 * Assign field to this form.
432 *
433 * @since 10.1.31
434 */
435 $field->setForm($this);
436
437 // bind data if set
438 if ($data)
439 {
440 // if scalar value, setup value array
441 if (is_scalar($data))
442 {
443 $data = array('value' => $data);
444 }
445
446 // iterate the data to bind
447 foreach ($data as $k => $v)
448 {
449 // bind attribute only if NOT NULL
450 if (!is_null($v))
451 {
452 $field->bind($v, $k);
453 }
454 }
455 }
456
457 /**
458 * When specified, instruct the field that the layout
459 * should be drawn from the given client (plugin name).
460 *
461 * @since 10.1.31
462 */
463 if (isset($this->options['client']))
464 {
465 $field->modowner = $this->options['client'];
466 }
467
468 // get the field class and do the rendering
469 return $field->render();
470 }
471
472 /**
473 * Method used to bind data to the form.
474 *
475 * @param mixed $data An array or object of data to bind to the form.
476 *
477 * @return boolean True on success.
478 *
479 * @since 10.1.27
480 */
481 public function bind($data)
482 {
483 // Make sure there is a valid JForm XML document.
484 if (!($this->xml instanceof \SimpleXMLElement))
485 {
486 return false;
487 }
488
489 // The data must be an object or array.
490 if (!is_object($data) && !is_array($data))
491 {
492 return false;
493 }
494
495 // iterate field by field
496 foreach ($data as $name => $value)
497 {
498 /**
499 * Update only in case the value is NOT NULL.
500 *
501 * @since 10.1.29
502 */
503 if (!is_null($value))
504 {
505 // find field by name
506 $field = $this->getField($name);
507
508 if ($field)
509 {
510 // field found, update XML element by injecting
511 // the specified value
512 $field['value'] = $value;
513 }
514 }
515 }
516
517 return true;
518 }
519
520 /**
521 * Method to get the form control. This string serves as a container for all form fields. For
522 * example, if there is a field named 'foo' and a field named 'bar' and the form control is
523 * empty the fields will be rendered like: `<input name="foo" />` and `<input name="bar" />`. If
524 * the form control is set to 'jform' however, the fields would be rendered like:
525 * `<input name="jform[foo]" />` and `<input name="jform[bar]" />`.
526 *
527 * @return string The form control string.
528 *
529 * @since 10.1.31
530 */
531 public function getFormControl()
532 {
533 return (string) isset($this->options['control']) ? $this->options['control'] : '';
534 }
535
536 /**
537 * Method to set the form control.
538 *
539 * @param string $control The form control.
540 *
541 * @return void
542 *
543 * @since 10.1.31
544 */
545 public function setFormControl($control)
546 {
547 $this->options['control'] = $control;
548 }
549 }
550