PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / 1.2.21
VikAppointments Services Booking Calendar v1.2.21
1.2.21 1.2.20 trunk 1.2.17 1.2.18 1.2.19
vikappointments / site / helpers / libraries / customfields / field.php
vikappointments / site / helpers / libraries / customfields Last commit date
rules 3 days ago types 3 days ago control.php 3 days ago emptyloader.php 3 days ago factory.php 3 days ago field.php 3 days ago index.html 3 days ago loader.php 3 days ago renderer.php 3 days ago requestor.php 3 days ago rule.php 3 days ago
field.php
470 lines
1 <?php
2 /**
3 * @package VikAppointments
4 * @subpackage core
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2021 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 VAPLoader::import('libraries.customfields.control');
15
16 /**
17 * VikAppointments custom field holder.
18 *
19 * @since 1.7
20 */
21 abstract class VAPCustomField extends JObject
22 {
23 /**
24 * Creates a new instance for the specified custom field.
25 *
26 * @param mixed $field Either an array or an object holding the details
27 * of the custom field.
28 *
29 * @return self A new custom field instance.
30 */
31 final public static function getInstance($field)
32 {
33 if (is_string($field))
34 {
35 $field = array('type' => $field);
36 }
37 else
38 {
39 $field = (array) $field;
40 }
41
42 if (empty($field['type']))
43 {
44 // the type is mandatory in order to fetch the correct instance
45 throw new Exception('Missing custom field type', 400);
46 }
47
48 /**
49 * Trigger hook to allow external plugins to include new types of custom
50 * fields that have been implemented out of this project. Plugins must
51 * include here the file holding the class of the field type.
52 *
53 * @param string $type The requested custom field type.
54 *
55 * @param string The classname of the object.
56 *
57 * @since 1.7
58 */
59 $classname = VAPFactory::getEventDispatcher()->triggerOnce('onLoadCustomField', array($field['type']));
60
61 if (!$classname)
62 {
63 // no attached plugins, attempt to load a default type
64 if (!VAPLoader::import('libraries.customfields.types.' . $field['type']))
65 {
66 // unable to find a file for the specified type
67 throw new Exception(sprintf('Custom field [%s] type not found', $field['type']), 404);
68 }
69
70 // create class name
71 $classname = 'VAPCustomField' . ucfirst($field['type']);
72 }
73
74 if (!class_exists($classname))
75 {
76 // unable to find a class for the specified type
77 throw new Exception(sprintf('Custom field [%s] class not found', $classname), 404);
78 }
79
80 // create instance
81 $handler = new $classname($field);
82
83 if (!$handler instanceof VAPCustomField)
84 {
85 // the class handler must inherit this class
86 throw new Exception(sprintf('Custom field [%s] is not a valid instance', $classname), 404);
87 }
88
89 return $handler;
90 }
91
92 /**
93 * Instructs the custom field to work as a custom field for the attendees.
94 *
95 * @param mixed $attendee The attendee number or false.
96 *
97 * @return self This object to support chaining.
98 */
99 public function setAttendee($attendee = false)
100 {
101 $this->set('attendee', $attendee);
102
103 return $this;
104 }
105
106 /**
107 * Returns the form field ID.
108 *
109 * @return string
110 */
111 public function getID()
112 {
113 // build default ID
114 $id = 'vapcf' . $this->get('id', 0);
115
116 if ($attendee = $this->get('attendee'))
117 {
118 // extend the ID in case of attendee field
119 $id .= '_attendee_' . (int) $attendee;
120 }
121
122 return $id;
123 }
124
125 /**
126 * Returns the form field attribute ID.
127 *
128 * @return string
129 */
130 public function getFormID()
131 {
132 // extract suffix from object, useful in case the custom fields have to be
133 // displayed more than once within the same page
134 $suffix = preg_replace("/[^a-zA-Z0-9_\-]+/", '', $this->get('idsuffix', ''));
135
136 // build default ID
137 $id = 'vapcf' . $suffix . $this->get('id', 0);
138
139 if ($attendee = $this->get('attendee'))
140 {
141 // extend the ID in case of attendee field
142 $id .= '_attendee_' . (int) $attendee;
143 }
144
145 return $id;
146 }
147
148 /**
149 * Returns the form field name.
150 *
151 * @return string
152 */
153 public function getName()
154 {
155 if ($this->get('group') == 1)
156 {
157 // return form name in case the custom field
158 // belongs to the employees group
159 return $this->formname;
160 }
161
162 return $this->name;
163 }
164
165 /**
166 * Returns the name of the field type.
167 *
168 * @return string
169 */
170 public function getType()
171 {
172 $type = $this->get('type');
173 $key = 'VAPCUSTOMFTYPEOPTION' . strtoupper($type);
174
175 // try to translate the given language definition
176 $name = JText::translate($key);
177
178 if ($name === $key)
179 {
180 // translation not found, return plain type
181 return $type;
182 }
183
184 return $name;
185 }
186
187 /**
188 * Children classes can override this method to alter the
189 * value to display.
190 *
191 * @param string $value The value stored within the database.
192 *
193 * @param string A readable text.
194 */
195 public function getReadableValue($value)
196 {
197 if (is_array($value))
198 {
199 // join values
200 return implode(', ', $value);
201 }
202
203 // return plain value by default
204 return (string) $value;
205 }
206
207 /**
208 * Loads the input from the request and returns the
209 * manipulated value, ready for saving.
210 *
211 * @param array &$args The array data to fill-in in case of
212 * specific rules (name, e-mail, etc...).
213 *
214 * @return mixed A scalar value of the custom field.
215 */
216 final public function save(&$args)
217 {
218 // extract value from request
219 $value = $this->extract($args);
220
221 // validate field value
222 if (!$this->validate($value))
223 {
224 // raise an error, the custom field is not valid
225 throw new Exception(JText::translate('VAPERRINSUFFCUSTF'));
226 }
227
228 if (!is_null($value) && !is_scalar($value))
229 {
230 // cannot accept non-scalar values, JSON encode them
231 $value = json_encode($value);
232 }
233
234 return $value;
235 }
236
237 /**
238 * Extracts the value of the custom field and applies any
239 * sanitizing according to the settings of the field.
240 *
241 * @param array &$args The array data to fill-in in case of
242 * specific rules (name, e-mail, etc...).
243 *
244 * @return mixed A scalar value of the custom field.
245 */
246 protected function extract(&$args)
247 {
248 $input = JFactory::getApplication()->input;
249
250 // treat by default as string
251 return $input->get($this->getID(), '', 'string');
252 }
253
254 /**
255 * Validates the field value.
256 *
257 * @param mixed $value The field raw value.
258 *
259 * @return boolean True if valid, false otherwise.
260 */
261 protected function validate($value)
262 {
263 if ((int) $this->get('required') == 0)
264 {
265 // always return true in case of optional field
266 return true;
267 }
268
269 // make sure the value is not empty
270 return (is_array($value) && count($value)) || strlen((string) $value);
271 }
272
273 /**
274 * Sets a custom path in which the system should search for
275 * the layout files to display.
276 *
277 * @param string $path The folder path.
278 *
279 * @return self This object to support chaining.
280 */
281 public function setLayoutPath($path = null)
282 {
283 $this->set('layoutpath', $path);
284
285 return $this;
286 }
287
288 /**
289 * Renders the input field.
290 *
291 * @param array $data An array of display data.
292 *
293 * @return string The resulting HTML.
294 */
295 public function render(array $data = array())
296 {
297 // prepare layout data
298 $data = $this->getDisplayData($data);
299
300 // try to dispatch the assigned rule to render a different layout
301 $input = VAPCustomFieldsFactory::renderRule($this, $data);
302
303 if (!$input)
304 {
305 // rules didn't define a specific layout, fallback to the default one
306 $input = $this->getInput($data);
307 }
308
309 /**
310 * Do not display the field control in case of hidden input.
311 *
312 * @since 1.7.2
313 */
314 if ($this->get('type') === 'hidden' || $this->get('hidden'))
315 {
316 return $input;
317 }
318
319 // create field control
320 return $this->getControl($data, $input);
321 }
322
323 /**
324 * Returns the HTML of the field input.
325 *
326 * @param array $data An array of display data.
327 *
328 * @return string The HTML of the input.
329 */
330 protected function getInput($data)
331 {
332 // attempt to use the specified layout, otherwise
333 // use the layout related to the given field type
334 $layout = $this->get('layout', $this->get('type'));
335
336 // create layout file
337 $layoutFile = new JLayoutFile('form.fields.' . $layout);
338
339 if ($path = $this->get('layoutpath'))
340 {
341 // search layout files also within the specified path
342 $layoutFile->addIncludePath($path);
343 }
344
345 // render input field
346 return $layoutFile->render($data);
347 }
348
349 /**
350 * Sets the control handler to allow the style rewriting.
351 *
352 * @param mixed $control The handler or null.
353 *
354 * @return self This instance to support chaining.
355 */
356 public function setControl(?VAPCustomFieldControl $control = null)
357 {
358 $this->set('control', $control);
359
360 return $this;
361 }
362
363 /**
364 * Returns the HTML of the field.
365 *
366 * @param array $data An array of display data.
367 * @param string $input The HTML of the input to wrap.
368 *
369 * @return string The HTML of the input.
370 */
371 protected function getControl($data, $input = null)
372 {
373 // check if we should use a specific wrapper
374 $control = $this->get('control', null);
375
376 // render input if not specified
377 $input = is_null($input) ? $this->getInput($data) : $input;
378
379 if ($control instanceof VAPCustomFieldControl)
380 {
381 // use custom renderer
382 $html = $control->render($data, $input);
383 }
384 else
385 {
386 // create layout to open the control
387 $openControl = new JLayoutFile('form.control.open');
388 // create layout to close the control
389 $closeControl = new JLayoutFile('form.control.close');
390
391 if ($path = $this->get('layoutpath'))
392 {
393 // search layout files also within the specified path
394 $openControl->addIncludePath($path);
395 $closeControl->addIncludePath($path);
396 }
397
398 // use native rendering
399 $html = $openControl->render($data) . $input . $closeControl->render($data);
400 }
401
402 return $html;
403 }
404
405 /**
406 * Returns an array of display data.
407 *
408 * @param array $data An array of display data.
409 *
410 * @return array
411 */
412 protected function getDisplayData(array $data)
413 {
414 $data['value'] = isset($data['value']) ? $data['value'] : '';
415 $data['label'] = $this->get('langname');
416 $data['name'] = $this->getID();
417 $data['id'] = !empty($data['id']) ? $data['id'] : $this->getFormID();
418 $data['description'] = isset($data['description']) ? $data['description'] : $this->get('description');
419 $data['field'] = $this->getProperties();
420 $data['required'] = isset($data['required']) ? (bool) $data['required'] : $this->get('required', false);
421 $data['class'] = isset($data['class']) ? $data['class'] : '';
422
423 // add class to recognize custom fields
424 $data['class'] = trim('custom-field' . ' ' . $data['class']);
425
426 if ($data['required'])
427 {
428 $data['class'] .= ' required';
429 }
430
431 if ($this->get('multiple'))
432 {
433 // set multiple attribute
434 $data['multiple'] = true;
435
436 // normalize name to support arrays
437 if (!preg_match("/\[\]$/", $data['name']))
438 {
439 $data['name'] .= '[]';
440 }
441
442 if (is_string($data['value']) && preg_match("/^\[/", $data['value']))
443 {
444 // JSON decode stored value
445 $data['value'] = json_decode($data['value']);
446 }
447 else
448 {
449 // attempt to cast the value as fallback
450 $data['value'] = $data['value'] ? (array) $data['value'] : array();
451 }
452 }
453
454 // make ID safe
455 $data['id'] = preg_replace("/[^a-zA-Z0-9_\-]+/", '_', $data['id']);
456
457 return $data;
458 }
459
460 /**
461 * Returns an array of field settings.
462 *
463 * @return array
464 */
465 protected function getSettings()
466 {
467 return (array) json_decode($this->get('choose', '{}'), true);
468 }
469 }
470