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 / mvc / view.php
vikappointments / site / helpers / libraries / mvc Last commit date
controllers 2 days ago controller.php 2 days ago index.html 2 days ago model.php 2 days ago table.php 2 days ago view.php 2 days ago
view.php
683 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 /**
15 * This class implements helpful methods for view instances.
16 * JViewBaseUI is a placeholder used to support both JView and JViewLegacy.
17 *
18 * @since 1.6
19 * @since 1.7 Renamed from JViewUI
20 */
21 class JViewVAP extends JViewBaseUI
22 {
23 /**
24 * The current signature of the filters.
25 *
26 * @var array
27 */
28 protected $signatureId = '';
29
30 /**
31 * This method returns the correct limit start to use.
32 * In case the filters changes, the limit is always reset.
33 *
34 * @param array $args The filters associative array.
35 * @param mixed $id An optional value used to restrict
36 * the states only to a specific ID/page.
37 * @param string $prefix An optional prefix to use in case a page
38 * supports more than one pagination (@since 1.7).
39 *
40 * @return integer The list start limit.
41 *
42 * @uses getPoolName()
43 * @uses registerSignature()
44 * @uses checkSignature()
45 * @uses resetLimit()
46 */
47 protected function getListLimitStart(array $args, $id = null, $prefix = '')
48 {
49 $app = JFactory::getApplication();
50
51 // calculate pool name
52 $name = $this->getPoolName($id);
53
54 // get list limit
55 $start = $app->getUserStateFromRequest($name . '.' . $prefix . 'limitstart', $prefix . 'limitstart', 0, 'uint');
56
57 // register new filters signature
58 $this->registerSignature($args, $id);
59
60 if ($start > 0 && !$this->checkSignature($id))
61 {
62 // filters are changed, reset limit
63 $this->resetLimit($start, $id, $prefix);
64 }
65
66 return $start;
67 }
68
69 /**
70 * Calculates the signature of the given filters and register it in the user state.
71 *
72 * @param array $args The filters associative array.
73 * @param mixed $id An optional value used to restrict
74 * the states only to a specific ID/page.
75 *
76 * @return string The old signature.
77 *
78 * @uses getPoolName()
79 */
80 protected function registerSignature(array $args, $id = null)
81 {
82 $app = JFactory::getApplication();
83
84 // calculate new signature
85 $sign = array();
86
87 foreach ($args as $k => $v)
88 {
89 if (is_null($v))
90 {
91 continue;
92 }
93
94 if (is_array($v))
95 {
96 // implode elements in the list to have a string
97 $v = implode(',', $v);
98 }
99
100 if (strlen((string) $v))
101 {
102 $sign[$k] = $v;
103 }
104 }
105
106 $sign = $sign ? serialize($sign) : '';
107
108 // calculate signature name
109 $name = $this->getPoolName($id);
110
111 // get old signature because `setUserState` owns a bug for returning the old state
112 $this->signatureId = $app->getUserState($name . '.signature', '');
113
114 // register new signature
115 $app->setUserState($name . '.signature', $sign);
116
117 // return old signature
118 return $this->signatureId;
119 }
120
121 /**
122 * Checks if the new signature matches the previous one.
123 *
124 * @param mixed $id An optional value used to restrict
125 * the states only to a specific ID/page.
126 * @param string $token The token to check against the new one.
127 * If not provided, the internal one will be used.
128 *
129 * @return boolean True if the tokens are equal.
130 *
131 * @uses getPoolName()
132 */
133 protected function checkSignature($id = null, $token = null)
134 {
135 if (!$token)
136 {
137 // use property in case the argument is empty
138 $token = $this->signatureId;
139 }
140
141 // calculate signature name
142 $name = $this->getPoolName($id);
143
144 // get current signature
145 $sign = JFactory::getApplication()->getUserState($name . '.signature', '');
146
147 // check if the 2 signatures are equal
148 return !strcasecmp($sign, $token);
149 }
150
151 /**
152 * Resets the list limit and save it in the user state.
153 *
154 * @param integer &$start The start list limit.
155 * @param mixed $id An optional value used to restrict
156 * the states only to a specific ID/page.
157 * @param string $prefix An optional prefix to use in case a page
158 * supports more than one pagination (@since 1.7).
159 *
160 * @return void
161 *
162 * @uses getPoolName();
163 */
164 protected function resetLimit(&$start, $id = null, $prefix = '')
165 {
166 // limit start passed by reference, reset it
167 $start = 0;
168
169 // calculate limit name
170 $name = $this->getPoolName($id);
171
172 // register the new limit within the user state
173 JFactory::getApplication()->setUserState($name . '.' . $prefix . 'limitstart', $start);
174 }
175
176 /**
177 * Returns the pool base name in which is stored the user state.
178 *
179 * @param mixed $id An optional value used to restrict
180 * the states only to a specific ID/page.
181 *
182 * @return string The pool name.
183 */
184 public function getPoolName($id = null)
185 {
186 /**
187 * Calculate pool name.
188 *
189 * @since 1.7 Prepend vap before view name.
190 */
191 $name = 'vap' . $this->getName();
192
193 if (!is_null($id))
194 {
195 // access the user state of a specific ID/page
196 $name .= "[$id]";
197 }
198
199 return $name;
200 }
201
202 /**
203 * Validates the list query to ensure that the specified limit
204 * doesn't exceed the total number of records. This might happen
205 * while erasing all the records from the last page.
206 *
207 * The query is always retrieved from the database object and
208 * must be invoked only once it has been set and executed.
209 *
210 * @param mixed &$offset The offset to use.
211 * @param mixed &$limit The limit to use.
212 * @param mixed $id An optional value used to restrict
213 * the states only to a specific ID/page.
214 * @param string $prefix An optional prefix to use in case a page
215 * supports more than one pagination (@since 1.7).
216 *
217 * @return void
218 *
219 * @uses getPoolName()
220 *
221 * @since 1.6.2
222 */
223 protected function assertListQuery(&$offset, &$limit, $id = null, $prefix = '')
224 {
225 $dbo = JFactory::getDbo();
226
227 // retrieve current query
228 $query = $dbo->getQuery();
229
230 if (!$offset || $dbo->getNumRows())
231 {
232 // we don't need to proceed as we are already fetching the first page
233 // or we found at least one record
234 return;
235 }
236
237 // No record found on the page we are (not the first one)!
238 // Try shifting by the offset found.
239 $limit = $limit ? $limit : 20;
240 $offset = max(array(0, $offset - (int) $limit));
241
242 // execute query again with updated limit
243 $dbo->setQuery($query, $offset, $limit);
244 $dbo->execute();
245
246 if (!$dbo->getNumRows())
247 {
248 $offset = 0;
249
250 // check if we are handling a limitable query object
251 if (interface_exists('JDatabaseQueryLimitable') && $query instanceof JDatabaseQueryLimitable)
252 {
253 // Update limit on query builder too because database might ignore it when offset
254 // is equals to 0. Note that offset and limit are specified in the opposite way.
255 $query->setLimit($limit, $offset);
256 }
257
258 // Still no rows found! Reset to the first page.
259 $dbo->setQuery($query, $offset, $limit);
260 $dbo->execute();
261 }
262
263 // calculate limit name
264 $name = $this->getPoolName($id);
265
266 // register the new limit within the user state
267 JFactory::getApplication()->setUserState($name . '.' . $prefix . 'limitstart', $offset);
268 }
269
270 /**
271 * Creates an event that triggers before executing the query used
272 * to retrieve a standard list of records.
273 * This is useful to manipulate the response that the query should return,
274 * such as adding additional columns and/or restrictions.
275 *
276 * @param mixed &$query The query string or a query builder object.
277 *
278 * @return void
279 *
280 * @since 1.6.2
281 */
282 protected function onBeforeListQuery(&$query)
283 {
284 // create event name based on the view name (e.g. onBeforeListQueryReservations)
285 $event = 'onBeforeListQuery' . ucfirst($this->getName());
286
287 /**
288 * Trigger event to allow the plugins to manipulate the query used to retrieve
289 * a standard list of records.
290 *
291 * @param mixed &$query The query string or a query builder object.
292 * @param mixed $view The current view instance.
293 *
294 * @return void
295 *
296 * @since 1.6.2
297 */
298 VAPFactory::getEventDispatcher()->trigger($event, array(&$query, $this));
299 }
300
301 /**
302 * Creates an event that triggers when displaying a management view.
303 * This is useful to include custom HTML in specific positions
304 * of the management view.
305 *
306 * Any specified arguments will be used when triggering the event.
307 *
308 * @param string $suffix An optional suffix to use for the event.
309 *
310 * @return string The HTML to display.
311 *
312 * @since 1.6.4
313 */
314 protected function onDisplayManageView()
315 {
316 // get received arguments
317 $args = func_get_args();
318
319 // get grouped HTML forms
320 $html = call_user_func_array(array($this, 'onDisplayView'), $args);
321
322 // join all HTML strings by ignoring the groups
323 return implode('', array_values($html));
324 }
325
326 /**
327 * Creates an event that triggers when displaying a list view.
328 * This is useful to include custom filters in specific positions
329 * of the search bar.
330 *
331 * @param boolean &$searching True in case of active filters.
332 * @param array $config An optional configuration array.
333 *
334 * @return array An array of forms.
335 *
336 * @since 1.6.6
337 */
338 protected function onDisplayListView(&$searching = false, array $config = array())
339 {
340 // get view name and trim ending "list" string, if any
341 $viewname = preg_replace("/list$/i", '', $this->getName());
342
343 // create event name based on the view name (e.g. onDisplayViewReservationsList)
344 $event = 'onDisplayView' . ucfirst($viewname) . 'List';
345
346 $dispatcher = VAPFactory::getEventDispatcher();
347
348 $forms = array();
349
350 /**
351 * Trigger event to allow the plugins to include custom HTML within the view.
352 * It is possible to return an associative array to group the HTML strings
353 * under different fieldsets. Plain/html string will be always pushed within
354 * the "custom" fieldset instead.
355 *
356 * @param mixed $view The current view instance.
357 * @param boolean &$searching Set it to TRUE to open the Search Tools.
358 * @param array $config A configuration array.
359 *
360 * @return mixed The HTML to display.
361 */
362 $values = $dispatcher->trigger($event, array($this, &$searching, $config));
363
364 // iterate all the returned values
365 foreach ($values as $value)
366 {
367 if (!is_array($value))
368 {
369 // use "search" group in case the returned value is a string
370 $value = array('search' => $value);
371 }
372
373 // iterate groups
374 foreach ($value as $key => $html)
375 {
376 // check if the fieldset already exists
377 if (!isset($forms[$key]))
378 {
379 $forms[$key] = '';
380 }
381
382 // push form within the specified fieldset
383 $forms[$key] .= $html;
384 }
385 }
386
387 // return array of forms
388 return $forms;
389 }
390
391 /**
392 * Creates an event that triggers when displaying a view.
393 * This is useful to include custom HTML in specific positions
394 * of the current view.
395 *
396 * Any specified arguments will be used when triggering the event.
397 *
398 * @param string $suffix An optional suffix to use for the event.
399 *
400 * @return array An array of forms.
401 *
402 * @since 1.6.6
403 */
404 protected function onDisplayView()
405 {
406 $events = array();
407
408 // get all specified arguments
409 $args = func_get_args();
410 // extract suffix from arguments
411 $suffix = array_shift($args);
412
413 // create event name based on the view name (e.g. onDisplayViewManagereservation)
414 $events[] = 'onDisplayView' . ucfirst($this->getName()) . (string) $suffix;
415 // use also a different alias by trimming the initial "manage", "edit", "new" strings
416 // from the view name, such as "onDisplayViewReservation"
417 $events[] = 'onDisplayView' . ucfirst(preg_replace("/^(manage|new|edit)/i", '', $this->getName())) . (string) $suffix;
418
419 $dispatcher = VAPFactory::getEventDispatcher();
420
421 // merge default arguments with the given ones
422 $args = array_merge(
423 array($this),
424 $args
425 );
426
427 $forms = array();
428
429 // iterate events and make sure the same event name is not going to be used twice
430 foreach (array_unique($events) as $event)
431 {
432 /**
433 * Trigger event to allow the plugins to include custom HTML within the view.
434 * It is possible to return an associative array to group the HTML strings
435 * under different fieldsets. Plain/html string will be always pushed within
436 * the "custom" fieldset instead.
437 *
438 * @param mixed $view The current view instance.
439 *
440 * @return mixed The HTML to display.
441 *
442 * @since 1.6.4
443 */
444 $values = $dispatcher->trigger($event, $args);
445
446 // iterate all the returned values
447 foreach ($values as $value)
448 {
449 if (!is_array($value))
450 {
451 // use "custom" group in case the returned value is a string
452 $value = array('VAP_CUSTOM_FIELDSET' => $value);
453 }
454
455 // iterate groups
456 foreach ($value as $key => $html)
457 {
458 // check if the fieldset already exists
459 if (!isset($forms[$key]))
460 {
461 $forms[$key] = '';
462 }
463
464 if (is_array($html))
465 {
466 // create layout file to render form fields (use back-end layout)
467 $layout = new JLayoutFile('form.fields', null, [
468 'component' => 'com_vikappointments',
469 'client' => 'admin',
470 ]);
471
472 // render fields
473 $html = $layout->render([
474 'fields' => $html,
475 ]);
476 }
477
478 // push form within the specified fieldset
479 $forms[$key] .= $html;
480 }
481 }
482 }
483
484 // return array of forms
485 return $forms;
486 }
487
488 /**
489 * Handles the events needed to introduce some new custom columns
490 * with a list table.
491 *
492 * @param string $property The name of the property holding the rows.
493 * @param array $config An optional configuration array.
494 *
495 * @return array An array of columns.
496 *
497 * @since 1.7
498 */
499 protected function onDisplayTableColumns($property = 'rows', array $config = array())
500 {
501 // try to access the property holding the rows
502 if (!isset($this->{$property}))
503 {
504 // unable to access the rows
505 return array();
506 }
507
508 $viewname = $this->getName();
509
510 $dispatcher = VAPFactory::getEventDispatcher();
511
512 // create event name based on the view name (e.g. onDisplayReservationsTableTH)
513 $event = 'onDisplay' . ucfirst($viewname) . 'TableTH';
514
515 $th_list = array();
516
517 /**
518 * Trigger event to allow the plugins to include custom <TH> within the table.
519 * The event must return an associative array where the key is the identifier
520 * of the column and the value is the HTML to use.
521 *
522 * DO NOT include <th> tag because it is automatically added by the system.
523 * Lean on "data-id" attribute for individual styling.
524 *
525 * @param mixed $view The current view instance.
526 * @param array $config A configuration array.
527 *
528 * @return array An array of TH.
529 *
530 * @since 1.7
531 */
532 $values = $dispatcher->trigger($event, array($this, $config));
533
534 // merge results at the same level
535 foreach ($values as $result)
536 {
537 $th_list = array_merge($th_list, $result);
538 }
539
540 // create event name based on the view name (e.g. onDisplayReservationsTableTD)
541 $event = 'onDisplay' . ucfirst($viewname) . 'TableTD';
542
543 $td_list = array();
544
545 /**
546 * Trigger event to allow the plugins to include custom <TD> within the table.
547 * The event must return an associative array where the key is the identifier
548 * of the column and the value is the HTML to use.
549 *
550 * DO NOT include <td> tag because it is automatically added by the system.
551 * Lean on "data-id" attribute for individual styling.
552 *
553 * Each value of the resulting array must declare an array containing the HTML
554 * for each column within the list.
555 *
556 * @param array $rows The elements to scan.
557 * @param mixed $view The current view instance.
558 * @param array $config A configuration array.
559 *
560 * @return array An array of TD.
561 *
562 * @since 1.7
563 */
564 $values = $dispatcher->trigger($event, array($this->{$property}, $this, $config));
565
566 // merge results at the same level
567 foreach ($values as $result)
568 {
569 $td_list = array_merge($td_list, $result);
570 }
571
572 $columns = array();
573
574 // Iterate results to join both <th> and <td> according to their IDs.
575 // All orphans TDs will be ignored.
576 foreach ($th_list as $k => $th)
577 {
578 $result = new stdClass;
579 $result->th = $th;
580 $result->td = (array) (isset($td_list[$k]) ? $td_list[$k] : array());
581
582 $columns[$k] = $result;
583 }
584
585 // return array of columns
586 return $columns;
587 }
588
589 /**
590 * In case the user state owns a pending record, its properties will be injected within the
591 * specified data object. This usually occurs after a saving failure.
592 *
593 * @param object &$data The data object.
594 * @param mixed $key Either the user state key or a data object/array
595 * (changed from string @since 1.7).
596 *
597 * @return void
598 *
599 * @since 1.6.4
600 */
601 public function injectUserStateData(&$data, $key)
602 {
603 if (is_string($key))
604 {
605 $app = JFactory::getApplication();
606
607 // use room data stored in user state
608 $state = $app->getUserState($key, array());
609 }
610 else
611 {
612 $state = $key;
613 }
614
615 // inject data stored in user state
616 foreach ($state as $property => $value)
617 {
618 $data->{$property} = $value;
619 }
620 }
621
622 /**
623 * Returns the active tab set in the user state/cookie.
624 *
625 * @param string $def The default tab in case it is missing.
626 * @param mixed $id An optional value used to restrict
627 * the states only to a specific ID/page.
628 *
629 * @return string The active tab.
630 *
631 * @uses getCookieTab()
632 *
633 * @since 1.6.4
634 */
635 public function getActiveTab($def = '', $id = null)
636 {
637 // get tab from cookie
638 $value = $this->getCookieTab($id)->value;
639
640 if (!$value)
641 {
642 // return default value if empty
643 return $def;
644 }
645
646 return $value;
647 }
648
649 /**
650 * Returns the active tab set in the user state/cookie.
651 *
652 * @param string $def The default tab in case it is missing.
653 * @param mixed $id An optional value used to restrict
654 * the states only to a specific ID/page.
655 *
656 * @return object An object containing the cookie details.
657 *
658 * @since 1.6.4
659 */
660 public function getCookieTab($id = null)
661 {
662 $cookie = new stdClass;
663 $cookie->name = preg_replace("/[^a-zA-Z0-9_]+/", '_', $this->getPoolName($id));
664 $cookie->name = preg_replace("/_*$/", '', $cookie->name);
665 $cookie->value = JFactory::getApplication()->input->cookie->getString($cookie->name, null);
666
667 return $cookie;
668 }
669
670 /**
671 * Placeholder used to check whether the system should
672 * display the filters bar.
673 *
674 * @return boolean
675 *
676 * @since 1.7
677 */
678 protected function hasFilters()
679 {
680 return false;
681 }
682 }
683