| @@ -31,8 +31,17 @@ | ||
| 31 | 31 | */ |
| 32 | 32 | protected static $helper = null; |
| 33 | 33 | |
| 34 | 34 | /** |
| 35 | + * List of preloaded widgets. | |
| 36 | + * | |
| 37 | + * @var array | |
| 38 | + * | |
| 39 | + * @since 1.18.3 (J) - 1.8.3 (WP) | |
| 40 | + */ | |
| 41 | + protected static $preloaded = []; | |
| 42 | + | |
| 43 | + /** | |
| 35 | 44 | * The database handler instance. |
| 36 | 45 | * |
| 37 | 46 | * @var object |
| 38 | 47 | */ |
| @@ -45,8 +54,17 @@ | ||
| 45 | 54 | */ |
| 46 | 55 | protected $widgets; |
| 47 | 56 | |
| 48 | 57 | /** |
| 58 | + * The list of widget positions in the multitask panel. | |
| 59 | + * | |
| 60 | + * @var array | |
| 61 | + * | |
| 62 | + * @since 1.16.10 (J) - 1.6.10 (WP) | |
| 63 | + */ | |
| 64 | + protected $widgets_pos_list; | |
| 65 | + | |
| 66 | + /** | |
| 49 | 67 | * Class constructor is protected. |
| 50 | 68 | * |
| 51 | 69 | * @see getInstance() |
| 52 | 70 | */ |
| @@ -54,8 +72,9 @@ | ||
| 54 | 72 | { |
| 55 | 73 | static::$helper = []; |
| 56 | 74 | $this->dbo = JFactory::getDbo(); |
| 57 | 75 | $this->widgets = []; |
| 76 | + $this->widgets_pos_list = (array) VBOFactory::getConfig()->getArray('multitasking_wpos', []); | |
| 58 | 77 | $this->load(); |
| 59 | 78 | } |
| 60 | 79 | |
| 61 | 80 | /** |
| @@ -143,10 +162,19 @@ | ||
| 143 | 162 | if (class_exists($classname)) { |
| 144 | 163 | // instantiate widget object |
| 145 | 164 | $widget = new $classname(); |
| 146 | 165 | |
| 147 | - // push widget object | |
| 148 | - array_push($this->widgets, $widget); | |
| 166 | + /** | |
| 167 | + * Call the widget's preflight method to see if it's compatible with the current environment. | |
| 168 | + * | |
| 169 | + * @since 1.16.10 (J) - 1.6.10 (WP) | |
| 170 | + */ | |
| 171 | + if (!$widget->preflight()) { | |
| 172 | + continue; | |
| 173 | + } | |
| 174 | + | |
| 175 | + // push the installed widget object | |
| 176 | + $this->widgets[] = $widget; | |
| 149 | 177 | } |
| 150 | 178 | } catch (Exception $e) { |
| 151 | 179 | // do nothing |
| 152 | 180 | } |
| @@ -189,8 +217,9 @@ | ||
| 189 | 217 | $container->size = 'small'; |
| 190 | 218 | $container->widgets = [ |
| 191 | 219 | 'guest_messages', |
| 192 | 220 | 'latest_from_guests', |
| 221 | + 'booking_details', | |
| 193 | 222 | 'visitors_counter', |
| 194 | 223 | ]; |
| 195 | 224 | // push container |
| 196 | 225 | array_push($section->containers, $container); |
| @@ -197,8 +226,9 @@ | ||
| 197 | 226 | // start container |
| 198 | 227 | $container = new stdClass; |
| 199 | 228 | $container->size = 'small'; |
| 200 | 229 | $container->widgets = [ |
| 230 | + 'aitools', | |
| 201 | 231 | 'forecast', |
| 202 | 232 | 'bookings_calendar', |
| 203 | 233 | 'reminders', |
| 204 | 234 | ]; |
| @@ -247,8 +277,9 @@ | ||
| 247 | 277 | $container = new stdClass; |
| 248 | 278 | $container->size = 'medium'; |
| 249 | 279 | $container->widgets = [ |
| 250 | 280 | 'finance', |
| 281 | + 'inquiries_chat', | |
| 251 | 282 | 'currency_converter', |
| 252 | 283 | ]; |
| 253 | 284 | // push container |
| 254 | 285 | array_push($section->containers, $container); |
| @@ -330,12 +361,11 @@ | ||
| 330 | 361 | { |
| 331 | 362 | $id = $this->simplifyId($id); |
| 332 | 363 | |
| 333 | 364 | foreach ($this->widgets as $widget) { |
| 334 | - if ($widget->getIdentifier() != $id) { | |
| 335 | - continue; | |
| 365 | + if ($widget->getIdentifier() == $id) { | |
| 366 | + return $widget; | |
| 336 | 367 | } |
| 337 | - return $widget; | |
| 338 | 368 | } |
| 339 | 369 | |
| 340 | 370 | return false; |
| 341 | 371 | } |
| @@ -367,30 +397,58 @@ | ||
| 367 | 397 | $descr = $widget->getDescription(); |
| 368 | 398 | |
| 369 | 399 | if ($preload) { |
| 370 | 400 | // preload widget's CSS/JS assets (if any) |
| 371 | - $watch_data = $widget->preload(); | |
| 401 | + $watch_data = array_key_exists($id, static::$preloaded) ? static::$preloaded[$id] : $widget->preload(); | |
| 402 | + | |
| 372 | 403 | if ($watch_data) { |
| 373 | 404 | // assign watching data to this widget |
| 374 | 405 | $watch[$id] = $watch_data; |
| 375 | 406 | } |
| 407 | + | |
| 408 | + // overwrite cache for the preloaded widget result | |
| 409 | + static::$preloaded[$id] = $watch_data; | |
| 376 | 410 | } |
| 377 | 411 | |
| 378 | 412 | // build widget info object |
| 379 | 413 | $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()); | |
| 414 | + $wtdata->id = $id; | |
| 415 | + $wtdata->name = $name; | |
| 416 | + $wtdata->descr = $descr; | |
| 417 | + $wtdata->icon = $widget->getIcon(); | |
| 418 | + $wtdata->style = str_replace(array(' ', '_'), '-', $widget->getStyleName()); | |
| 419 | + $wtdata->priority = $widget->getPriority(); | |
| 385 | 420 | |
| 386 | 421 | // set object for sorting |
| 387 | - $names[$name] = $wtdata; | |
| 422 | + $names[$name] = $wtdata; | |
| 388 | 423 | } |
| 389 | 424 | |
| 390 | 425 | // apply sorting by name |
| 391 | 426 | ksort($names); |
| 392 | 427 | |
| 428 | + // apply sorting by priority | |
| 429 | + usort($names, function($a, $b) { | |
| 430 | + // the higher the priority is, the higher the position will be | |
| 431 | + return $b->priority - $a->priority; | |
| 432 | + }); | |
| 433 | + | |
| 434 | + if ($this->widgets_pos_list) { | |
| 435 | + // get the custom positions list | |
| 436 | + $widgets_pos_list = $this->widgets_pos_list; | |
| 437 | + // apply custom sorting | |
| 438 | + usort($names, function($a, $b) use ($widgets_pos_list) { | |
| 439 | + $wa_id = $a->id; | |
| 440 | + $wb_id = $b->id; | |
| 441 | + | |
| 442 | + if (($widgets_pos_list[$wa_id] ?? null) && ($widgets_pos_list[$wb_id] ?? null)) { | |
| 443 | + // the higher the custom position is, the lower the sorting position will be | |
| 444 | + return $widgets_pos_list[$wa_id] > $widgets_pos_list[$wb_id] ? 1 : -1; | |
| 445 | + } | |
| 446 | + | |
| 447 | + return 0; | |
| 448 | + }); | |
| 449 | + } | |
| 450 | + | |
| 393 | 451 | // push sorted widgets to pool |
| 394 | 452 | foreach ($names as $wtdata) { |
| 395 | 453 | $pool[] = $wtdata; |
| 396 | 454 | } |
| @@ -398,9 +456,9 @@ | ||
| 398 | 456 | /** |
| 399 | 457 | * In case some widgets were pre-loaded for watching certain |
| 400 | 458 | * events, enqueue the watch-data list to the document. |
| 401 | 459 | */ |
| 402 | - if (count($watch)) { | |
| 460 | + if ($watch) { | |
| 403 | 461 | // schedule the browser notifications watching data |
| 404 | 462 | VBONotificationScheduler::getInstance()->registerWatchData($watch); |
| 405 | 463 | } |
| 406 | 464 | |
| @@ -416,21 +474,21 @@ | ||
| 416 | 474 | * containers and widgets. |
| 417 | 475 | */ |
| 418 | 476 | public function getWidgetsMap() |
| 419 | 477 | { |
| 420 | - $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='admin_widgets_map';"; | |
| 421 | - $this->dbo->setQuery($q); | |
| 422 | - $this->dbo->execute(); | |
| 423 | - if ($this->dbo->getNumRows()) { | |
| 424 | - $map = json_decode($this->dbo->loadResult()); | |
| 478 | + $config = VBOFactory::getConfig(); | |
| 479 | + | |
| 480 | + $map = $config->get('admin_widgets_map', null); | |
| 481 | + | |
| 482 | + if ($map) { | |
| 483 | + $map = json_decode($map); | |
| 425 | 484 | return is_object($map) && !empty($map->sections) && is_array($map->sections) ? $map : $this->getDefaultWidgetsMap(); |
| 426 | 485 | } |
| 427 | 486 | |
| 428 | 487 | $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 | 488 | |
| 489 | + $config->set('admin_widgets_map', $default_map); | |
| 490 | + | |
| 433 | 491 | return $default_map; |
| 434 | 492 | } |
| 435 | 493 | |
| 436 | 494 | /** |
| @@ -441,9 +499,9 @@ | ||
| 441 | 499 | * @return bool True on success, false otherwise. |
| 442 | 500 | */ |
| 443 | 501 | public function updateWidgetsMap($sections) |
| 444 | 502 | { |
| 445 | - if (!is_array($sections) || !count($sections)) { | |
| 503 | + if (!is_array($sections) || !$sections) { | |
| 446 | 504 | return false; |
| 447 | 505 | } |
| 448 | 506 | |
| 449 | 507 | // prepare new map object |
| @@ -449,11 +507,9 @@ | ||
| 449 | 507 | // prepare new map object |
| 450 | 508 | $map = new stdClass; |
| 451 | 509 | $map->sections = $sections; |
| 452 | 510 | |
| 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(); | |
| 511 | + VBOFactory::getConfig()->set('admin_widgets_map', $map); | |
| 456 | 512 | |
| 457 | 513 | return true; |
| 458 | 514 | } |
| 459 | 515 | |
| @@ -476,25 +532,26 @@ | ||
| 476 | 532 | |
| 477 | 533 | /** |
| 478 | 534 | * Forces the rendering of a specific widget identifier. |
| 479 | 535 | * |
| 480 | - * @param string $id the widget identifier. | |
| 481 | - * @param mixed $data anything to pass to the widget. | |
| 536 | + * @param string $id the widget identifier. | |
| 537 | + * @param ?VBOMultitaskData $data optional multitask data for the widget. | |
| 482 | 538 | * |
| 483 | - * @return mixed void on success, false otherwise. | |
| 539 | + * @return void | |
| 484 | 540 | */ |
| 485 | - public function renderWidget($id, $data = null) | |
| 541 | + public function renderWidget($id, ?VBOMultitaskData $data = null) | |
| 486 | 542 | { |
| 487 | 543 | $id = $this->simplifyId($id); |
| 488 | 544 | |
| 489 | 545 | foreach ($this->widgets as $widget) { |
| 490 | - if ($widget->getIdentifier() != $id) { | |
| 491 | - continue; | |
| 546 | + if ($widget->getIdentifier() == $id) { | |
| 547 | + // render the requested widget | |
| 548 | + $widget->render($data); | |
| 549 | + | |
| 550 | + // return void | |
| 551 | + return; | |
| 492 | 552 | } |
| 493 | - return $widget->render($data); | |
| 494 | 553 | } |
| 495 | - | |
| 496 | - return false; | |
| 497 | 554 | } |
| 498 | 555 | |
| 499 | 556 | /** |
| 500 | 557 | * Maps the size identifier to a CSS class. |
| @@ -505,12 +562,12 @@ | ||
| 505 | 562 | */ |
| 506 | 563 | public function getContainerCssClass($size) |
| 507 | 564 | { |
| 508 | 565 | $css_size_map = [ |
| 509 | - 'small' => 'vbo-admin-widgets-container-small', | |
| 566 | + 'small' => 'vbo-admin-widgets-container-small', | |
| 510 | 567 | 'medium' => 'vbo-admin-widgets-container-medium', |
| 511 | - 'large' => 'vbo-admin-widgets-container-large', | |
| 512 | - 'full' => 'vbo-admin-widgets-container-fullwidth', | |
| 568 | + 'large' => 'vbo-admin-widgets-container-large', | |
| 569 | + 'full' => 'vbo-admin-widgets-container-fullwidth', | |
| 513 | 570 | ]; |
| 514 | 571 | |
| 515 | 572 | return isset($css_size_map[$size]) ? $css_size_map[$size] : $css_size_map['full']; |
| 516 | 573 | } |
| @@ -576,11 +633,10 @@ | ||
| 576 | 633 | $tot_rooms_units = 0; |
| 577 | 634 | |
| 578 | 635 | $q = "SELECT `id`,`name`,`units`,`params`,`avail` FROM `#__vikbooking_rooms`;"; |
| 579 | 636 | $this->dbo->setQuery($q); |
| 580 | - $this->dbo->execute(); | |
| 581 | - if ($this->dbo->getNumRows() > 0) { | |
| 582 | - $all_rooms = $this->dbo->loadAssocList(); | |
| 637 | + $all_rooms = $this->dbo->loadAssocList(); | |
| 638 | + if ($all_rooms) { | |
| 583 | 639 | foreach ($all_rooms as $k => $r) { |
| 584 | 640 | if ($r['avail'] < 1) { |
| 585 | 641 | $unpublished_rooms[] = $r['id']; |
| 586 | 642 | } else { |
| @@ -587,9 +643,9 @@ | ||
| 587 | 643 | $tot_rooms_units += $r['units']; |
| 588 | 644 | } |
| 589 | 645 | $all_rooms_ids[$r['id']] = $r['name']; |
| 590 | 646 | $all_rooms_units[$r['id']] = $r['units']; |
| 591 | - $rparams = json_decode($r['params'], true); | |
| 647 | + $rparams = json_decode((string) $r['params'], true); | |
| 592 | 648 | $all_rooms_features[$r['id']] = is_array($rparams) && array_key_exists('features', $rparams) && is_array($rparams['features']) ? $rparams['features'] : []; |
| 593 | 649 | } |
| 594 | 650 | } |
| 595 | 651 | |
| @@ -613,9 +669,9 @@ | ||
| 613 | 669 | if (empty($key)) { |
| 614 | 670 | return false; |
| 615 | 671 | } |
| 616 | 672 | |
| 617 | - if (!count(static::$helper)) { | |
| 673 | + if (!static::$helper) { | |
| 618 | 674 | $this->loadRoomsData(); |
| 619 | 675 | } |
| 620 | 676 | |
| 621 | 677 | if (isset(static::$helper[$key])) { |
| @@ -661,11 +717,11 @@ | ||
| 661 | 717 | public function showWelcome() |
| 662 | 718 | { |
| 663 | 719 | $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='admin_widgets_welcome';"; |
| 664 | 720 | $this->dbo->setQuery($q); |
| 665 | - $this->dbo->execute(); | |
| 666 | - if ($this->dbo->getNumRows()) { | |
| 667 | - return ((int)$this->dbo->loadResult() < 1); | |
| 721 | + $show_welcome = $this->dbo->loadResult(); | |
| 722 | + if ($show_welcome !== null) { | |
| 723 | + return ((int)$show_welcome < 1); | |
| 668 | 724 | } |
| 669 | 725 | |
| 670 | 726 | $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('admin_widgets_welcome', '0');"; |
| 671 | 727 | $this->dbo->setQuery($q); |
| @@ -820,8 +876,34 @@ | ||
| 820 | 876 | // update configuration record |
| 821 | 877 | VBOFactory::getConfig()->set('multitasking_map', json_encode($map)); |
| 822 | 878 | |
| 823 | 879 | return true; |
| 880 | + } | |
| 881 | + | |
| 882 | + /** | |
| 883 | + * Sets the position for an admin widget in the multitask panel. | |
| 884 | + * | |
| 885 | + * @param array $pos_list Associative list of widget positions. | |
| 886 | + * | |
| 887 | + * @return array The updated widgets position list. | |
| 888 | + * | |
| 889 | + * @since 1.16.10 (J) - 1.6.10 (WP) | |
| 890 | + */ | |
| 891 | + public function setMultitaskingWidgetPos($pos_list) | |
| 892 | + { | |
| 893 | + if (!is_array($pos_list)) { | |
| 894 | + return false; | |
| 895 | + } | |
| 896 | + | |
| 897 | + // turn all position values into integers | |
| 898 | + $pos_list = array_map(function($pos) { | |
| 899 | + return (int) $pos; | |
| 900 | + }, $pos_list); | |
| 901 | + | |
| 902 | + // update configuration record | |
| 903 | + VBOFactory::getConfig()->set('multitasking_wpos', $pos_list); | |
| 904 | + | |
| 905 | + return $pos_list; | |
| 824 | 906 | } |
| 825 | 907 | |
| 826 | 908 | /** |
| 827 | 909 | * Admin widgets used to be named as the entire file name, |