widgetName = JText::translate('VBO_W_REMINDERS_TITLE'); $this->widgetDescr = JText::translate('VBO_W_REMINDERS_DESCR'); $this->widgetId = basename(__FILE__, '.php'); // define widget and icon and style name $this->widgetIcon = ''; $this->widgetStyleName = 'green'; // load widget's settings $this->widgetSettings = $this->loadSettings(); if (!is_object($this->widgetSettings)) { $this->widgetSettings = new stdClass; } // check if VCM is available if (is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'lib.vikchannelmanager.php')) { $this->vcm_exists = true; } } /** * This widget does not actually need to preload data to be watched for * dispatching notifications, and the reminders due are automatically * scheduled for being dispatched as a browser notification. However, * we make use of this method to periodically check if some reminders * should be automatically created for specific reasons, such as the * Airbnb host-to-guest reviews to remind the host to review the guest. * We also preload the necessary CSS/JS assets and schedule the imminent * reminders for notifications, if any. * * @return void */ public function preload() { // declare JS lang vars JText::script('VBO_PLEASE_FILL_FIELDS'); JText::script('VBDELCONFIRM'); JText::script('VBCONFIGCLOSINGDATEADD'); JText::script('VBADMINNOTESUPD'); // load assets $this->vbo_app->loadDatePicker(); // check if VCM is available if ($this->vcm_exists) { $today_dt = date('Y-m-d'); $yesterday_dt = date('Y-m-d', strtotime('yesterday')); // check the last time this check was made $last_check_dt = isset($this->widgetSettings->last_check_dt) ? $this->widgetSettings->last_check_dt : $yesterday_dt; if ($last_check_dt != $today_dt) { // update last check date $this->widgetSettings->last_check_dt = $today_dt; $this->updateSettings(json_encode($this->widgetSettings)); // check for any Channel Manager reservation that requires an auto-reminder $this->scheduleChannelManagerReminders(); } } // load the 10 next (imminent) reminders $overdue = VBORemindersHelper::getInstance()->getImminents(10); if ($overdue) { // schedule browser notifications for the next reminders VBONotificationScheduler::getInstance()->enqueueReminders($overdue); } // nothing should be actually watched, ever return null; } /** * Custom method for this widget only to load the reminder records. * The method is called by the admin controller through an AJAX request. * The visibility should be public, it should not exit the process, and * any content sent to output will be returned to the AJAX response. * In this case we return an array because this method requires "return":1. * * It's the actual rendering of the widget which also allows navigation. * * @return array */ public function loadReminders() { $offset = VikRequest::getInt('offset', 0, 'request'); $length = VikRequest::getInt('length', $this->reminders_per_page, 'request'); $completed = VikRequest::getInt('completed', 0, 'request'); $expired = VikRequest::getInt('expired', 0, 'request'); $bid = VikRequest::getInt('bid', 0, 'request'); $reminder_id = VikRequest::getInt('reminder_id', 0, 'request'); $wrapper = VikRequest::getString('wrapper', '', 'request'); // load reminders starting from today at 00:00:00 $now_info = getdate(); $lim_min_date = date('Y-m-d H:i:s', mktime(0, 0, 0, $now_info['mon'], $now_info['mday'], $now_info['year'])); // prepare fetch options $fetch = [ 'after' => $lim_min_date, 'completed' => $completed, 'expired' => $expired, 'idorder' => $bid, ]; // load reminders $helper = VBORemindersHelper::getInstance(); $reminders = $helper->loadReminders($fetch, $offset, $length); // check if a next page can be available $has_next_page = (count($reminders) >= $length); // compose bookings base URI $bookings_base_uri = VBOFactory::getPlatform()->getUri()->admin('index.php?option=com_vikbooking&task=editorder&cid[]=%d', $xhtml = false); // check if a specific reminder ID was requested if ($reminder_id) { // check if the requested ID was found among the most recent ones $reminder_found = false; foreach ($reminders as $reminder) { if ($reminder->id == $reminder_id) { $reminder_found = true; break; } } if (!$reminder_found) { // fetch the record by ID $reminder_record = $helper->getReminder($reminder_id); if ($reminder_record) { // prepend element to the list array_unshift($reminders, $reminder_record); } } } // start output buffering ob_start(); if (!$reminders) { ?>
widgetSettings->show_completed = $completed; $this->widgetSettings->show_expired = $expired; $this->updateSettings(json_encode($this->widgetSettings)); } foreach ($reminders as $reminder) { $use_icn_status = $reminder->completed ? 'dot-circle' : 'far fa-circle'; $hidden_date = ''; $hidden_hours = ''; $hidden_minutes = ''; if (!empty($reminder->duedate)) { $date_parts = explode(' ', $reminder->duedate); $hidden_date = date($this->df, strtotime($date_parts[0])); if ($reminder->usetime) { $time_parts = explode(':', $date_parts[1]); $hidden_hours = (int)$time_parts[0]; $hidden_minutes = (int)$time_parts[1]; } } $is_past_reminder = false; if (!empty($reminder->duedate)) { // calculate distance to expiration date from today $diff_data = $helper->relativeDatesDiff($reminder->duedate); $is_past_reminder = $diff_data['past']; } // list of extra classes for the reminder $extra_classes = []; if ($is_past_reminder) { $extra_classes[] = 'vbo-reminder-past'; } else { $extra_classes[] = 'vbo-reminder-future'; } if ($reminder->completed) { $extra_classes[] = 'vbo-reminder-ok'; } else { $extra_classes[] = 'vbo-reminder-nok'; } ?> $html_content, 'tot_records' => count($reminders), 'next_page' => (int)$has_next_page, ); } /** * Custom method for this widget only. * Creates a new reminder. * * @return array */ public function saveReminder() { $title = VikRequest::getString('title', '', 'request'); $descr = VikRequest::getString('descr', '', 'request'); $date = VikRequest::getString('date', '', 'request'); $hours = VikRequest::getInt('hours', 0, 'request'); $minutes = VikRequest::getInt('minutes', 0, 'request'); $bid = VikRequest::getInt('bid', 0, 'request'); $important = VikRequest::getInt('important', 0, 'request'); // create reminder object $reminder = new stdClass; $reminder->title = $title; $reminder->descr = $descr; $use_time = 0; if (!empty($date)) { if ($hours >= 0) { $use_time = 1; $minutes = $minutes >= 0 ? $minutes : 0; $due_ts = VikBooking::getDateTimestamp($date, $hours, $minutes); } else { $due_ts = VikBooking::getDateTimestamp($date); } $reminder->duedate = date('Y-m-d H:i:s', $due_ts); } $reminder->usetime = $use_time; if (!empty($bid)) { $reminder->idorder = $bid; } $reminder->important = $important; if (!VBORemindersHelper::getInstance()->saveReminder($reminder)) { VBOHttpDocument::getInstance()->close(400, 'Could not create the reminder'); } // compose the notification payload for the newly created reminder $notif_data = VBONotificationScheduler::getInstance()->getReminderDataObject($reminder, $max_future_hours = 12); return [ 'status' => true, 'notification' => $notif_data, 'debug' => print_r($reminder, true) ]; } /** * Custom method for this widget only. * Updates an existing reminder. * * @return array */ public function updateReminder() { $rid = VikRequest::getInt('rid', 0, 'request'); $title = VikRequest::getString('title', '', 'request'); $descr = VikRequest::getString('descr', '', 'request'); $date = VikRequest::getString('date', '', 'request'); $hours = VikRequest::getInt('hours', 0, 'request'); $minutes = VikRequest::getInt('minutes', 0, 'request'); $bid = VikRequest::getInt('bid', 0, 'request'); $important = VikRequest::getInt('important', 0, 'request'); if (empty($rid)) { VBOHttpDocument::getInstance()->close(400, 'Missing reminder ID to update'); } // create reminder object $reminder = new stdClass; $reminder->id = $rid; $reminder->title = $title; $reminder->descr = $descr; $use_time = 0; if (!empty($date)) { if ($hours >= 0) { $use_time = 1; $minutes = $minutes >= 0 ? $minutes : 0; $due_ts = VikBooking::getDateTimestamp($date, $hours, $minutes); } else { $due_ts = VikBooking::getDateTimestamp($date); } $reminder->duedate = date('Y-m-d H:i:s', $due_ts); } $reminder->usetime = $use_time; $reminder->idorder = !empty($bid) ? $bid : 0; if (!VBORemindersHelper::getInstance()->updateReminder($reminder)) { VBOHttpDocument::getInstance()->close(400, 'Could not update the reminder'); } // compose the notification paylod for the newly created reminder $notif_data = VBONotificationScheduler::getInstance()->getReminderDataObject($reminder, $max_future_hours = 12); return [ 'status' => true, 'notification' => $notif_data, ]; } /** * Custom method for this widget only. * Toggles the completed status for an existing reminder. * * @return array */ public function toggleReminderCompleted() { $rid = VikRequest::getInt('rid', 0, 'request'); if (empty($rid)) { VBOHttpDocument::getInstance()->close(400, 'Missing reminder ID to update'); } $helper = VBORemindersHelper::getInstance(); $reminder = $helper->getReminder($rid); if (!$reminder) { VBOHttpDocument::getInstance()->close(400, 'Record not found'); } // toggle completed status $reminder->completed = (int) !$reminder->completed; if (!$helper->updateReminder($reminder)) { VBOHttpDocument::getInstance()->close(400, 'Could not update the reminder'); } $notif_data = null; if (!$reminder->completed) { // compose the notification paylod for the uncompleted reminder $notif_data = VBONotificationScheduler::getInstance()->getReminderDataObject($reminder, $max_future_hours = 12); } return [ 'status' => $reminder->completed, 'notification' => $notif_data, ]; } /** * Custom method for this widget only. * Deletes an existing reminder. * * @return bool */ public function deleteReminder() { $rid = VikRequest::getInt('rid', 0, 'request'); if (empty($rid)) { VBOHttpDocument::getInstance()->close(400, 'Missing reminder ID to delete'); } $helper = VBORemindersHelper::getInstance(); $reminder = $helper->getReminder($rid); if (!$reminder) { VBOHttpDocument::getInstance()->close(400, 'Record not found'); } if (!$helper->deleteReminder($reminder)) { VBOHttpDocument::getInstance()->close(400, 'Could not delete the reminder'); } return true; } /** * Main method to invoke the widget. Contents will be loaded * through AJAX requests, not via PHP when the page loads. * * @param ?VBOMultitaskData $data multitask data object. * * @return void */ public function render(?VBOMultitaskData $data = null) { // increase widget's instance counter static::$instance_counter++; // check whether the widget is being rendered via AJAX when adding it through the customizer $is_ajax = $this->isAjaxRendering(); // generate a unique ID for the sticky notes wrapper instance $wrapper_instance = !$is_ajax ? static::$instance_counter : rand(); $wrapper_id = 'vbo-widget-reminders-' . $wrapper_instance; // default filters $reminder_id = 0; $def_completed = 0; $def_expired = 0; $def_duedate = time(); $def_duehours = -1; $def_duemins = -1; // check multitask data $page_bid = ''; $auto_action = ''; $js_modal_id = ''; if ($data) { // access Multitask data $page_bid = $data->getBookingID(); if ($data->isModalRendering()) { // get modal JS identifier $js_modal_id = $data->getModalJsIdentifier(); } // we allow the page booking ID to be passed as an option as well $page_bid = !$page_bid ? $this->options()->fetchBookingId() : $page_bid; // check for options $reminder_id = (int) $this->getOption('reminder_id', 0); $auto_action = $this->getOption('action', ''); $def_completed = (int) $this->getOption('completed', $def_completed); $def_expired = (int) $this->getOption('expired', $def_expired); // attempt to define values for this precise booking $booking_info = $page_bid ? VikBooking::getBookingInfoFromID($page_bid) : []; if ($booking_info) { $def_time_info = []; if ($booking_info['checkin'] > $def_duedate) { $def_duedate = $booking_info['checkin']; $def_time_info = getdate($def_duedate); } elseif ($booking_info['checkout'] > $def_duedate) { $def_duedate = $booking_info['checkout']; $def_time_info = getdate($def_duedate); } if ($def_time_info) { $def_duehours = (int)$def_time_info['hours']; $def_duemins = (int)$def_time_info['minutes']; } } } else { // load settings $def_completed = isset($this->widgetSettings->show_completed) ? (int)$this->widgetSettings->show_completed : $def_completed; $def_expired = isset($this->widgetSettings->show_expired) ? (int)$this->widgetSettings->show_expired : $def_expired; } ?> gatherAirbnbReservationsCheckedOut(); // default due-date for the newly added reminders $def_due_date = date('Y-m-d H:i:s', strtotime("+1 minute")); // default reminder payload $payload = [ 'airbnb_host_guest_review' => 1, ]; // parse all eligible reservations foreach ($airbnb_checkouts as $airbnb_res) { // make sure this airbnb reservation requires a host-to-guest review if (!$helper->bookingHasReminder($airbnb_res['id'], $payload) && VikChannelManager::hostToGuestReviewSupported($airbnb_res)) { // build and schedule an automatic reminder $reminder = new stdClass; $reminder->title = JText::translate('VBO_REVIEW_YOUR_GUEST'); $reminder->descr = 'Airbnb - ' . JText::translate('VBDASHBOOKINGID') . ' ' . $airbnb_res['id']; $reminder->duedate = $def_due_date; $reminder->usetime = 1; $reminder->idorder = $airbnb_res['id']; $reminder->payload = $payload; $reminder->important = 1; if ($helper->saveReminder($reminder)) { // increase counter $scheduled++; } } } return $scheduled; } }