PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.6.7
VikBooking Hotel Booking Engine & PMS v1.6.7
1.8.16 1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 All 37 releases
vikbooking / admin / helpers / admin_widgets.php

admin_widgets.php in VikBooking Hotel Booking Engine & PMS 1.6.7, at admin/helpers/admin_widgets.php

842 lines 20.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage com_vikbooking
5 * @author Alessio Gaggii - e4j - Extensionsforjoomla.com
6 * @copyright Copyright (C) 2018 e4j - Extensionsforjoomla.com. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 * @link https://vikwp.com
9 */
10
11 defined('ABSPATH') or die('No script kiddies please!');
12
13 /**
14 * Helper class for the administrator widgets.
15 *
16 * @since 1.14 (J) - 1.4.0 (WP)
17 */
18 class VikBookingHelperAdminWidgets
19 {
20 /**
21 * The singleton instance of the class.
22 *
23 * @var VikBookingHelperAdminWidgets
24 */
25 protected static $instance = null;
26
27 /**
28 * An array to store some cached/static values.
29 *
30 * @var array
31 */
32 protected static $helper = null;
33
34 /**
35 * The database handler instance.
36 *
37 * @var object
38 */
39 protected $dbo;
40
41 /**
42 * The list of widget instances loaded.
43 *
44 * @var array
45 */
46 protected $widgets;
47
48 /**
49 * Class constructor is protected.
50 *
51 * @see getInstance()
52 */
53 protected function __construct()
54 {
55 static::$helper = [];
56 $this->dbo = JFactory::getDbo();
57 $this->widgets = [];
58 $this->load();
59 }
60
61 /**
62 * Returns the global object, either
63 * a new instance or the existing instance
64 * if the class was already instantiated.
65 *
66 * @return self A new instance of the class.
67 */
68 public static function getInstance()
69 {
70 if (is_null(static::$instance)) {
71 static::$instance = new static();
72 }
73
74 return static::$instance;
75 }
76
77 /**
78 * Loads a list of all available admin widgets.
79 *
80 * @return self
81 */
82 protected function load()
83 {
84 /** @var VBOPlatformDispatcherInterface */
85 $dispatcher = VBOFactory::getPlatform()->getDispatcher();
86
87 // require main/parent admin-widget class
88 require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'admin_widget.php');
89
90 $widgets_base = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'widgets' . DIRECTORY_SEPARATOR;
91 $widgets_files = glob($widgets_base . '*.php');
92 $widgets_banned = [];
93
94 /**
95 * Trigger event to let other plugins register additional widgets.
96 *
97 * @return array A list of supported widgets.
98 */
99 $list = $dispatcher->filter('onLoadAdminWidgets');
100
101 foreach ($list as $chunk) {
102 // merge default widget files with the returned ones
103 $widgets_files = array_merge($widgets_files, (array)$chunk);
104 }
105
106 /**
107 * Trigger event to let other plugins unregister specific widgets.
108 *
109 * @return array A list of widget identifiers to unload.
110 *
111 * @since 1.16.0 (J) - 1.6.0 (WP)
112 */
113 $unloaded = $dispatcher->filter('onUnloadAdminWidgets');
114
115 foreach ($unloaded as $chunk) {
116 // merge all the the returned ones
117 $widgets_banned = array_merge($widgets_banned, (array)$chunk);
118 }
119
120 // parse all admin widgets
121 foreach ($widgets_files as $wf) {
122 try {
123 /**
124 * Require widget class file only if available, to allow registering classes at
125 * runtime by third-party plugins without needing to have a dedicated PHP file.
126 * The widget class must obviously exist all the times, but the file must not.
127 */
128 if (is_file($wf)) {
129 require_once($wf);
130 }
131
132 // widget identifier
133 $widget_identif = basename($wf, '.php');
134
135 // check if the widget was unloaded
136 if (in_array($widget_identif, $widgets_banned)) {
137 continue;
138 }
139
140 // build widget class name
141 $classname = 'VikBookingAdminWidget' . str_replace(' ', '', ucwords(str_replace('_', ' ', $widget_identif)));
142
143 if (class_exists($classname)) {
144 // instantiate widget object
145 $widget = new $classname();
146
147 // push widget object
148 array_push($this->widgets, $widget);
149 }
150 } catch (Exception $e) {
151 // do nothing
152 }
153 }
154
155 return $this;
156 }
157
158 /**
159 * Gets the default map of admin widgets.
160 *
161 * @return object the associative map of sections,
162 * containers and widgets.
163 */
164 protected function getDefaultWidgetsMap()
165 {
166 // sections container
167 $sections = [];
168
169 // build default sections
170
171 // new section
172 $section = new stdClass;
173 $section->name = 'Top';
174 $section->containers = [];
175 // start container
176 $container = new stdClass;
177 $container->size = 'medium';
178 $container->widgets = [
179 'sticky_notes',
180 'arriving_today',
181 'departing_today',
182 'check_availability',
183 'latest_events',
184 ];
185 // push container
186 array_push($section->containers, $container);
187 // start container
188 $container = new stdClass;
189 $container->size = 'small';
190 $container->widgets = [
191 'guest_messages',
192 'latest_from_guests',
193 'booking_details',
194 'visitors_counter',
195 ];
196 // push container
197 array_push($section->containers, $container);
198 // start container
199 $container = new stdClass;
200 $container->size = 'small';
201 $container->widgets = [
202 'forecast',
203 'bookings_calendar',
204 'reminders',
205 ];
206 // push container
207 array_push($section->containers, $container);
208 // push section
209 array_push($sections, $section);
210
211 // new section
212 $section = new stdClass;
213 $section->name = 'Middle';
214 $section->containers = [];
215 // start container
216 $container = new stdClass;
217 $container->size = 'full';
218 $container->widgets = [
219 'today_rooms_occupancy',
220 'weekly_bookings',
221 ];
222 // push container
223 array_push($section->containers, $container);
224 // push section
225 array_push($sections, $section);
226
227 // new section
228 $section = new stdClass;
229 $section->name = 'Middle 2';
230 $section->containers = [];
231 // start container
232 $container = new stdClass;
233 $container->size = 'small';
234 $container->widgets = [
235 'latest_events',
236 ];
237 // push container
238 array_push($section->containers, $container);
239 // start container
240 $container = new stdClass;
241 $container->size = 'small';
242 $container->widgets = [
243 'rates_flow',
244 ];
245 // push container
246 array_push($section->containers, $container);
247 // start container
248 $container = new stdClass;
249 $container->size = 'medium';
250 $container->widgets = [
251 'finance',
252 'currency_converter',
253 ];
254 // push container
255 array_push($section->containers, $container);
256 // push section
257 array_push($sections, $section);
258
259 // new section
260 $section = new stdClass;
261 $section->name = 'Bottom';
262 $section->containers = [];
263 // start container
264 $container = new stdClass;
265 $container->size = 'medium';
266 $container->widgets = [
267 'last_reservations',
268 ];
269 // push container
270 array_push($section->containers, $container);
271 // start container
272 $container = new stdClass;
273 $container->size = 'medium';
274 $container->widgets = [
275 'next_bookings',
276 ];
277 // push container
278 array_push($section->containers, $container);
279 // push section
280 array_push($sections, $section);
281
282 // new section
283 $section = new stdClass;
284 $section->name = 'Bottom 2';
285 $section->containers = [];
286 // start container
287 $container = new stdClass;
288 $container->size = 'medium';
289 $container->widgets = [
290 'orphan_dates',
291 'rooms_locked',
292 ];
293 // push container
294 array_push($section->containers, $container);
295 // start container
296 $container = new stdClass;
297 $container->size = 'medium';
298 $container->widgets = [
299 'sticky_notes',
300 ];
301 // push container
302 array_push($section->containers, $container);
303 // push section
304 array_push($sections, $section);
305
306 // compose the final map object
307 $map = new stdClass;
308 $map->sections = $sections;
309
310 return $map;
311 }
312
313 /**
314 * Gets the list of admin widgets instantiated.
315 *
316 * @return array list of admin widget objects.
317 */
318 public function getWidgets()
319 {
320 return $this->widgets;
321 }
322
323 /**
324 * Gets a single admin widget instantiated.
325 *
326 * @param string $id the widget identifier.
327 *
328 * @return mixed the admin widget object, false otherwise.
329 */
330 public function getWidget($id)
331 {
332 $id = $this->simplifyId($id);
333
334 foreach ($this->widgets as $widget) {
335 if ($widget->getIdentifier() == $id) {
336 return $widget;
337 }
338 }
339
340 return false;
341 }
342
343 /**
344 * Gets a list of sorted widget names, ids, descriptions, icons and styles.
345 * If called with pre-loading, widgets will be able to load their CSS and JS
346 * assets. Moreover, pre-loading a widget can return a watch-data object to
347 * implement the notifications scheduling of a specific widget.
348 *
349 * @param bool $preload whether to call the widget's preload method.
350 *
351 * @return array associative and sorted widgets list.
352 *
353 * @since 1.15.0 (J) - 1.5.0 (WP) added support for icons and style names.
354 * implemented widgets preloading for assets.
355 */
356 public function getWidgetNames($preload = false)
357 {
358 // containers
359 $names = [];
360 $pool = [];
361 $watch = [];
362
363 foreach ($this->widgets as $widget) {
364 // get widget details
365 $id = $widget->getIdentifier();
366 $name = $widget->getName();
367 $descr = $widget->getDescription();
368
369 if ($preload) {
370 // preload widget's CSS/JS assets (if any)
371 $watch_data = $widget->preload();
372 if ($watch_data) {
373 // assign watching data to this widget
374 $watch[$id] = $watch_data;
375 }
376 }
377
378 // build widget info object
379 $wtdata = new stdClass;
380 $wtdata->id = $id;
381 $wtdata->name = $name;
382 $wtdata->descr = $descr;
383 $wtdata->icon = $widget->getIcon();
384 $wtdata->style = str_replace(array(' ', '_'), '-', $widget->getStyleName());
385
386 // set object for sorting
387 $names[$name] = $wtdata;
388 }
389
390 // apply sorting by name
391 ksort($names);
392
393 // push sorted widgets to pool
394 foreach ($names as $wtdata) {
395 $pool[] = $wtdata;
396 }
397
398 /**
399 * In case some widgets were pre-loaded for watching certain
400 * events, enqueue the watch-data list to the document.
401 */
402 if (count($watch)) {
403 // schedule the browser notifications watching data
404 VBONotificationScheduler::getInstance()->registerWatchData($watch);
405 }
406
407 // return the list of admin widgets
408 return $pool;
409 }
410
411 /**
412 * Gets the current or default map of admin widgets.
413 * If no map currently set, stores the default map.
414 *
415 * @return array the associative map of sections,
416 * containers and widgets.
417 */
418 public function getWidgetsMap()
419 {
420 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='admin_widgets_map';";
421 $this->dbo->setQuery($q);
422 $map = $this->dbo->loadResult();
423 if ($map) {
424 $map = json_decode($map);
425 return is_object($map) && !empty($map->sections) && is_array($map->sections) ? $map : $this->getDefaultWidgetsMap();
426 }
427
428 $default_map = $this->getDefaultWidgetsMap();
429 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('admin_widgets_map', " . $this->dbo->quote(json_encode($default_map)) . ");";
430 $this->dbo->setQuery($q);
431 $this->dbo->execute();
432
433 return $default_map;
434 }
435
436 /**
437 * Updates the map of admin widgets.
438 *
439 * @param array $sections the list of sections for the map.
440 *
441 * @return bool True on success, false otherwise.
442 */
443 public function updateWidgetsMap($sections)
444 {
445 if (!is_array($sections) || !count($sections)) {
446 return false;
447 }
448
449 // prepare new map object
450 $map = new stdClass;
451 $map->sections = $sections;
452
453 $q = "UPDATE `#__vikbooking_config` SET `setting`=" . $this->dbo->quote(json_encode($map)) . " WHERE `param`='admin_widgets_map';";
454 $this->dbo->setQuery($q);
455 $this->dbo->execute();
456
457 return true;
458 }
459
460 /**
461 * Restores the default admin widgets map.
462 * First it resets the settings of each widget.
463 *
464 * @return bool True on success, false otherwise.
465 */
466 public function restoreDefaultWidgetsMap()
467 {
468 foreach ($this->widgets as $widget) {
469 $widget->resetSettings();
470 }
471
472 $default_map = $this->getDefaultWidgetsMap();
473
474 return $this->updateWidgetsMap($default_map->sections);
475 }
476
477 /**
478 * Forces the rendering of a specific widget identifier.
479 *
480 * @param string $id the widget identifier.
481 * @param VBOMultitaskData $data optional multitask data for the widget.
482 *
483 * @return void
484 */
485 public function renderWidget($id, VBOMultitaskData $data = null)
486 {
487 $id = $this->simplifyId($id);
488
489 foreach ($this->widgets as $widget) {
490 if ($widget->getIdentifier() == $id) {
491 // render the requested widget
492 $widget->render($data);
493
494 // return void
495 return;
496 }
497 }
498 }
499
500 /**
501 * Maps the size identifier to a CSS class.
502 *
503 * @param string $size the container size identifier.
504 *
505 * @return string the full CSS class for the container.
506 */
507 public function getContainerCssClass($size)
508 {
509 $css_size_map = [
510 'small' => 'vbo-admin-widgets-container-small',
511 'medium' => 'vbo-admin-widgets-container-medium',
512 'large' => 'vbo-admin-widgets-container-large',
513 'full' => 'vbo-admin-widgets-container-fullwidth',
514 ];
515
516 return isset($css_size_map[$size]) ? $css_size_map[$size] : $css_size_map['full'];
517 }
518
519 /**
520 * Returns an associative array with the class names for the containers.
521 *
522 * @return array a text representation list of all sizes.
523 */
524 public function getContainerClassNames()
525 {
526 return array(
527 'full' => array(
528 'name' => JText::translate('VBO_WIDGETS_CONTFULL'),
529 'css' => $this->getContainerCssClass('full'),
530 ),
531 'large' => array(
532 'name' => JText::translate('VBO_WIDGETS_CONTLARGE'),
533 'css' => $this->getContainerCssClass('large'),
534 ),
535 'medium' => array(
536 'name' => JText::translate('VBO_WIDGETS_CONTMEDIUM'),
537 'css' => $this->getContainerCssClass('medium'),
538 ),
539 'small' => array(
540 'name' => JText::translate('VBO_WIDGETS_CONTSMALL'),
541 'css' => $this->getContainerCssClass('small'),
542 ),
543 );
544 }
545
546 /**
547 * Maps the size identifier to the corresponding name.
548 *
549 * @param string $size the container size identifier.
550 *
551 * @return string the size name for the container.
552 */
553 public function getContainerName($size)
554 {
555 $names = $this->getContainerClassNames();
556
557 return isset($names[$size]) ? $names[$size]['name'] : $names['full']['name'];
558 }
559
560 /**
561 * Many widgets may need to know some values about the rooms.
562 * This method uses the static instance of the class to cache data.
563 *
564 * @return void
565 */
566 protected function loadRoomsData()
567 {
568 if (isset(static::$helper['all_rooms_ids'])) {
569 // do not execute the same queries again
570 return;
571 }
572
573 $all_rooms_ids = [];
574 $all_rooms_units = [];
575 $all_rooms_features = [];
576 $unpublished_rooms = [];
577 $tot_rooms_units = 0;
578
579 $q = "SELECT `id`,`name`,`units`,`params`,`avail` FROM `#__vikbooking_rooms`;";
580 $this->dbo->setQuery($q);
581 $all_rooms = $this->dbo->loadAssocList();
582 if ($all_rooms) {
583 foreach ($all_rooms as $k => $r) {
584 if ($r['avail'] < 1) {
585 $unpublished_rooms[] = $r['id'];
586 } else {
587 $tot_rooms_units += $r['units'];
588 }
589 $all_rooms_ids[$r['id']] = $r['name'];
590 $all_rooms_units[$r['id']] = $r['units'];
591 $rparams = json_decode($r['params'], true);
592 $all_rooms_features[$r['id']] = is_array($rparams) && array_key_exists('features', $rparams) && is_array($rparams['features']) ? $rparams['features'] : [];
593 }
594 }
595
596 // update static values
597 static::$helper['all_rooms_ids'] = $all_rooms_ids;
598 static::$helper['all_rooms_units'] = $all_rooms_units;
599 static::$helper['all_rooms_features'] = $all_rooms_features;
600 static::$helper['unpublished_rooms'] = $unpublished_rooms;
601 static::$helper['tot_rooms_units'] = $tot_rooms_units;
602 }
603
604 /**
605 * Many widgets could use this method to access cached information.
606 *
607 * @param string $key the data key identifier.
608 *
609 * @return mixed array/int on success, false otherwise.
610 */
611 public function getRoomsData($key)
612 {
613 if (empty($key)) {
614 return false;
615 }
616
617 if (!count(static::$helper)) {
618 $this->loadRoomsData();
619 }
620
621 if (isset(static::$helper[$key])) {
622 return static::$helper[$key];
623 }
624
625 return false;
626 }
627
628 /**
629 * Helper method to load all busy real records for all rooms for dates near today.
630 * This is useful to avoid double queries in the various widgets and to get data
631 * about yesterday, today and the next week.
632 *
633 * @return array
634 *
635 * @since 1.15.2 (J) - 1.5.5 (WP) changed the way limit timestamps are calculated.
636 */
637 public function loadBusyRecordsUnclosed()
638 {
639 if (!isset(static::$helper['all_rooms_ids'])) {
640 $this->loadRoomsData();
641 }
642
643 if (isset(static::$helper['busy_records_unclosed'])) {
644 return static::$helper['busy_records_unclosed'];
645 }
646
647 // cache value and return it
648 $base_from_ts = mktime(0, 0, 0, date("n"), (date("j") - 1), date("Y"));
649 $base_end_ts = mktime(23, 59, 59, date("n"), (date("j") + 7), date("Y"));
650 static::$helper['busy_records_unclosed'] = VikBooking::loadBusyRecordsUnclosed(array_keys(static::$helper['all_rooms_ids']), $base_from_ts, $base_end_ts);
651
652 return static::$helper['busy_records_unclosed'];
653 }
654
655 /**
656 * The first time the widget's customizer is open, the welcome is displayed.
657 * Congig value >= 1 means hide the welcome text, 0 or lower means show it.
658 *
659 * @return bool
660 */
661 public function showWelcome()
662 {
663 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='admin_widgets_welcome';";
664 $this->dbo->setQuery($q);
665 $show_welcome = $this->dbo->loadResult();
666 if ($show_welcome !== null) {
667 return ((int)$show_welcome < 1);
668 }
669
670 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('admin_widgets_welcome', '0');";
671 $this->dbo->setQuery($q);
672 $this->dbo->execute();
673
674 return true;
675 }
676
677 /**
678 * Updates the status of the welcome message for the widget's customizer.
679 * Congig value >= 1 means hide the welcome text, 0 or lower means show it.
680 *
681 * @param int $val the new value to set in the configuration.
682 *
683 * @return void
684 */
685 public function updateWelcome($val)
686 {
687 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='admin_widgets_welcome';";
688 $this->dbo->setQuery($q);
689 $this->dbo->execute();
690 if ($this->dbo->getNumRows()) {
691 $q = "UPDATE `#__vikbooking_config` SET `setting`=" . $this->dbo->quote((int)$val) . " WHERE `param`='admin_widgets_welcome';";
692 $this->dbo->setQuery($q);
693 $this->dbo->execute();
694 return;
695 }
696
697 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('admin_widgets_welcome', " . $this->dbo->quote((int)$val) . ");";
698 $this->dbo->setQuery($q);
699 $this->dbo->execute();
700 return;
701 }
702
703 /**
704 * Gets the default map for multitasking pages.
705 *
706 * @return object the associative map of widgets.
707 */
708 protected function getDefaultMultitaskingMap()
709 {
710 $map = new stdClass;
711
712 // booking details page
713 $map->editorder = [
714 'reminders',
715 'check_availability',
716 'bookings_calendar',
717 ];
718
719 // booking modifying page
720 $map->editbusy = [
721 'bookings_calendar',
722 'check_availability',
723 'reminders',
724 ];
725
726 // availability overview
727 $map->overv = [
728 'reminders',
729 'rates_flow',
730 'latest_events',
731 ];
732
733 return $map;
734 }
735
736 /**
737 * Gets the list of widgets assigned to the current page.
738 *
739 * @param string $page the name of the current View/task.
740 * @param bool $whole whether to get the entire map object.
741 *
742 * @return array|object the list of widgets for the given page,
743 * or entire map object if $whole is true.
744 */
745 public function getMultitaskingMap($page = null, $whole = false)
746 {
747 // get saved map
748 $saved_map = VBOFactory::getConfig()->get('multitasking_map');
749 if (!empty($saved_map)) {
750 // try decoding the map data
751 $saved_map = json_decode($saved_map);
752 }
753
754 if (!is_object($saved_map)) {
755 // revert to the default map
756 $saved_map = $this->getDefaultMultitaskingMap();
757 }
758
759 if ($whole) {
760 // do not proceed and return the entire map object
761 return $saved_map;
762 }
763
764 if (empty($page) || !is_string($page)) {
765 return [];
766 }
767
768 return isset($saved_map->$page) && is_array($saved_map->$page) ? $saved_map->$page : [];
769 }
770
771 /**
772 * Updates the map of multitasking widgets.
773 *
774 * @param string $page the name of the current View/task.
775 * @param array|string $widgets the list of or the widget(s) to set.
776 * @param int $action 0 = replace, -1 = prepend, 1 = append.
777 *
778 * @return bool true on success, false otherwise.
779 */
780 public function updateMultitaskingMap($page, $widgets, $action = 0)
781 {
782 if (empty($page) || !is_string($page)) {
783 return false;
784 }
785
786 // check if we have one widget or a list
787 $widgets = is_string($widgets) ? [$widgets] : $widgets;
788 if (!is_array($widgets)) {
789 return false;
790 }
791
792 // get the whole current map
793 $map = $this->getMultitaskingMap($page, true);
794
795 if (!isset($map->$page)) {
796 $map->$page = [];
797 }
798
799 if ($action > 0) {
800 // append widgets
801 foreach ($widgets as $widget_id) {
802 if (empty($widget_id)) {
803 continue;
804 }
805 array_push($map->$page, $widget_id);
806 }
807 } elseif ($action < 0) {
808 // prepend widgets
809 foreach ($widgets as $widget_id) {
810 if (empty($widget_id)) {
811 continue;
812 }
813 array_unshift($map->$page, $widget_id);
814 }
815 } else {
816 // replace current list when $action === 0
817 $map->$page = $widgets;
818 }
819
820 // update configuration record
821 VBOFactory::getConfig()->set('multitasking_map', json_encode($map));
822
823 return true;
824 }
825
826 /**
827 * Admin widgets used to be named as the entire file name,
828 * inclusive of the ".php" extension. For BC we make sure
829 * to evaluate the admin widget identifiers correclty.
830 *
831 * @param string $id the widget identifier to simplify.
832 *
833 * @return string the simplified widget identifier.
834 *
835 * @since 1.15.0 (J) - 1.5.0 (WP)
836 */
837 protected function simplifyId($id)
838 {
839 return str_replace('.php', '', $id);
840 }
841 }
842