PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / 1.2.20
VikAppointments Services Booking Calendar v1.2.20
1.2.21 1.2.20 trunk 1.2.17 1.2.18 1.2.19
vikappointments / site / controller.php
vikappointments / site Last commit date
assets 1 month ago controllers 1 month ago helpers 1 month ago layouts 1 month ago models 1 month ago views 1 month ago controller.php 1 month ago
controller.php
697 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 * General front-end controller of VikAppointments component.
16 *
17 * @since 1.0
18 */
19 class VikAppointmentsController extends JControllerVAP
20 {
21 /**
22 * Typical view method for MVC based architecture.
23 *
24 * @param boolean $cachable If true, the view output will be cached.
25 * @param array $urlparams An array of safe URL parameters and their variable types.
26 *
27 * @return void
28 */
29 function display($cachable = false, $urlparams = false)
30 {
31 $input = JFactory::getApplication()->input;
32
33 $view = strtolower($input->get('view', ''));
34
35 switch ($view)
36 {
37 case 'confirmapp':
38 $this->confirmapp();
39 break;
40
41 case 'packagesconfirm':
42 $this->packagesconfirm();
43 break;
44
45 case 'employeeslist':
46 case 'serviceslist':
47 case 'servicesearch':
48 case 'employeesearch':
49 case 'order':
50 case 'allorders':
51 case 'packages':
52 case 'packorders':
53 case 'packagesorder':
54 case 'subscriptions':
55 case 'subscrpayment':
56 case 'subscrhistory':
57 case 'userprofile':
58 case 'pushwl':
59 // do nothing
60 break;
61
62 case 'unsubscr_waiting_list':
63 $input->set('view', 'unsubscrwl');
64 break;
65
66 case 'empaccountstat':
67 case 'empattachser':
68 case 'empcoupons':
69 case 'empcustfields':
70 case 'empeditcoupon':
71 case 'empeditcustfield':
72 case 'empeditlocation':
73 case 'empeditpay':
74 case 'empeditprofile':
75 case 'empeditservice':
76 case 'empeditwdays':
77 case 'emplocations':
78 case 'emplocwdays':
79 case 'emplogin':
80 case 'empmakerecur':
81 case 'empmanres':
82 case 'emppaylist':
83 case 'empserviceslist':
84 case 'empsettings':
85 case 'empsubscr':
86 case 'empsubscrorder':
87 case 'empwdays':
88 VAPApplication::getInstance()->loadEmployeeAreaAssets();
89 break;
90
91 default:
92 $input->set('view', 'serviceslist');
93 }
94
95 parent::display();
96 }
97
98 /**
99 * Task used to access the confirmapp view.
100 *
101 * @return void
102 */
103 public function confirmapp()
104 {
105 $app = JFactory::getApplication();
106 $input = $app->input;
107
108 $args = array();
109 $args['id_employee'] = $input->getUint('id_employee', 0);
110 $args['id_service'] = $input->getUint('id_service', 0);
111 $args['date'] = $input->getString('date', '');
112 $args['hour'] = $input->getUint('hour', 0);
113 $args['min'] = $input->getUint('min', 0);
114 $args['people'] = $input->getUint('people', 1);
115 $args['factor'] = $input->getUint('duration_factor', 1);
116 $args['from'] = $input->getUint('from', 1);
117
118 // get cart model
119 $model = $this->getModel('cart');
120
121 // get cart handler
122 $cart = $model->getCart();
123
124 /**
125 * If the cart is empty and there are no data to add a new appointment, redirect
126 * the users to the services list without displaying any error message.
127 *
128 * @since 1.6
129 */
130 if ($cart->isEmpty() && $args['id_employee'] <= 0 && $args['id_service'] <= 0)
131 {
132 // invalid request, back to services list
133 $app->redirect(JRoute::rewrite('index.php?option=com_vikappointments&view=serviceslist', false));
134 return false;
135 }
136
137 /**
138 * Check if the time has been selected before clicking the Book Now button.
139 * In this way, the button can be used to add the services into the cart
140 * more than once.
141 *
142 * It is also needed to check if the appointment is already in the cart,
143 * so that we can assume that a refresh has been performed.
144 *
145 * @note Refreshing the page after deleting the appointment that has
146 * been added will cause a loop that will re-add automatically
147 * that appointment (only if the cart contains 2 or more items).
148 *
149 * @since 1.6
150 */
151 $time_is_selected = (bool) strlen($input->getString('hour', ''));
152
153 if ($time_is_selected)
154 {
155 // get the employee timezone, if set
156 $tz = JModelVAP::getInstance('employee')->getTimezone($args['id_employee']);
157 // create check-in date according to the employee timezone, only if the time was specified
158 $args['checkin'] = JFactory::getDate("{$args['date']} {$args['hour']}:{$args['min']}", $tz)->format('Y-m-d H:i:s');
159
160 // check if the specified appointment is already in the cart
161 if ($cart->indexOf($args['id_service'], $args['id_employee'], $args['checkin']) != -1)
162 {
163 // the appointment is already in the cart, we don't need to add it
164 $time_is_selected = false;
165 }
166 }
167
168 // add to cart [appoint.] from request when the cart is disabled or empty or the time is selected
169 if ((!VAPFactory::getConfig()->getBool('enablecart') && !empty($args['id_service'])) || $cart->isEmpty() || $time_is_selected)
170 {
171 // get recurrence roles
172 $rules = $input->getString('recurrence', '');
173
174 $args['options'] = array();
175
176 $opt_id = $input->get('opt_id', array(), 'uint');
177 $opt_qty = $input->get('opt_quantity', array(), 'uint');
178 $opt_var = $input->get('opt_var', array(), 'uint');
179
180 // load selected options
181 for ($i = 0; $i < count($opt_id); $i++)
182 {
183 $args['options'][] = array(
184 'id' => $opt_id[$i],
185 'quantity' => $opt_qty[$i],
186 'variation' => $opt_var[$i],
187 );
188 }
189
190 if ($rules)
191 {
192 // extract recurrence roles
193 list($recurrence['by'], $recurrence['for'], $recurrence['amount']) = explode(',', $rules);
194 // add item with recurrence
195 $res = $model->addRecurringItem($args, $recurrence);
196 }
197 else
198 {
199 // insert new item
200 $res = $model->addItem($args);
201 }
202
203 if (!$res)
204 {
205 // get error from model
206 $error = $model->getError($index = null, $string = true);
207
208 if ($error)
209 {
210 // enqueue error message only if set
211 $app->enqueueMessage($error, 'error');
212 }
213
214 if ($args['from'] == 1 || $args['id_employee'] <= 0)
215 {
216 // coming from service search details
217 $app->redirect(JRoute::rewrite("index.php?option=com_vikappointments&view=servicesearch&id_service={$args['id_service']}&id_employee={$args['id_employee']}&date={$args['date']}", false));
218 }
219 else
220 {
221 // coming from employee search details
222 $app->redirect(JRoute::rewrite("index.php?option=com_vikappointments&view=employeesearch&id_employee={$args['id_employee']}&id_service={$args['id_service']}&date={$args['date']}", false));
223 }
224
225 return false;
226 }
227 }
228
229 if ($cart->isEmpty())
230 {
231 // cart is still empty... back to the services list
232 $app->enqueueMessage(JText::translate('VAPCARTEMPTYERR'), 'error');
233 $app->redirect(JRoute::rewrite('index.php?option=com_vikappointments&view=serviceslist', false));
234 return false;
235 }
236
237 // validates the appointments contained within the cart
238 if (!$model->checkIntegrity($errors))
239 {
240 // there's at least an invalid item...
241 foreach ($errors as $error)
242 {
243 $name = $error['item']->getServiceName();
244 $at = JText::translate('VAP_AT_DATE_SEPARATOR');
245 $checkin = $error['item']->getCheckinDate(JText::translate('DATE_FORMAT_LC2'), VikAppointments::getUserTimezone());
246
247 // build item identifier string
248 $item_id = sprintf('%s %s %s', $name, $at, $checkin);
249
250 // register error message
251 $reason = JText::sprintf('VAPCARTITEMNOTAVERR', $item_id, $error['reason']);
252 $app->enqueueMessage($reason, 'error');
253 }
254 }
255
256 if ($cart->isEmpty())
257 {
258 // cart has been emptied due to invalid appointments, back to the services list
259 $app->enqueueMessage(JText::translate('VAPCARTEMPTYERR'), 'error');
260 $app->redirect(JRoute::rewrite('index.php?option=com_vikappointments&view=serviceslist', false));
261 return false;
262 }
263
264 // try to redeem the packages before accessing the view
265 VikAppointments::usePackagesForServicesInCart($cart);
266
267 /**
268 * Get rid of the options that are not available any longer.
269 *
270 * @since 1.7.7
271 */
272 if ($model->normalizeStockAvailability($cart))
273 {
274 // commit changes
275 $cart->store();
276 }
277 }
278
279 /**
280 * Task used to access the packagesconfirm view.
281 *
282 * @return void
283 */
284 function packagesconfirm()
285 {
286 $app = JFactory::getApplication();
287
288 // get cart model
289 $model = $this->getModel('packagescart');
290
291 // get cart handler
292 $cart = $model->getCart();
293
294 if ($cart->isEmpty())
295 {
296 // cart empty, back to packages view
297 $app->redirect(JRoute::rewrite('index.php?option=com_vikappointments&view=packages', false));
298 return false;
299 }
300 }
301
302 /**
303 * End-point used to export the appointments in ICS format.
304 * This task can be used by external applications to syncronize
305 * the appointments within their calendars (e.g Apple iCal or Google Calendar).
306 *
307 * This method expects the following parameters to be sent
308 * via POST or GET.
309 *
310 * @param integer employee An ID to obtain only the appointments
311 * assigned to the specified employee.
312 * If not provided, this filter will be ignored.
313 * @param string key The secure key to access the appointments.
314 *
315 * @return void
316 */
317 function appsync()
318 {
319 $app = JFactory::getApplication();
320
321 $id_emp = $app->input->getUint('employee', 0);
322 $key = $app->input->getString('key', '');
323 $imported = $app->input->getBool('imported', true);
324
325 if ($id_emp <= 0)
326 {
327 // no specified employee, use system sync password
328 $match = VAPFactory::getConfig()->get('synckey');
329 }
330 else
331 {
332 // specified employee, fetch record data
333 $employee = JModelVAP::getInstance('employee')->getItem($id_emp);
334
335 if (!$employee)
336 {
337 UIErrorFactory::raiseError(404, 'Employee not found');
338 }
339
340 // use employee sync password
341 $match = $employee->synckey;
342 }
343
344 // compare the specified key with the correct one
345 if (!$match || strcmp($key, $match))
346 {
347 // invalid key, abort
348 UIErrorFactory::raiseError(403, JText::translate('JERROR_ALERTNOAUTHOR'));
349 }
350
351 VAPLoader::import('libraries.order.export.factory');
352
353 try
354 {
355 // get ICS export driver
356 $driver = VAPOrderExportFactory::getInstance('ics', 'appointment', [
357 'id_employee' => $id_emp,
358 'admin' => true,
359 'imported' => $imported,
360 ]);
361 }
362 catch (Exception $e)
363 {
364 $code = $e->getCode();
365
366 // an error occurred...
367 UIErrorFactory::raiseError($code ? $code : 500, $e->getMessage());
368 }
369
370 // download ICS file
371 $driver->download();
372
373 // terminate execution
374 $app->close();
375 }
376
377 /**
378 * End-point used to dispatch the specified CRON JOB.
379 * This method expects the following parameters to be sent
380 * via POST or GET.
381 *
382 * @param integer id_cron The ID of the cron to launch.
383 * @param string secure_key The secure key to execute the command.
384 *
385 * @return void
386 */
387 function cronjob_listener_rq()
388 {
389 $app = JFactory::getApplication();
390 $input = $app->input;
391
392 // get request params
393 $id_cron = $input->getUint('id_cron', 0);
394 $secure_key = $input->getString('secure_key', '');
395
396 if (empty($secure_key))
397 {
398 UIErrorFactory::raiseError(400, 'Missing secure key');
399 }
400
401 // get CRON JOB model
402 $model = JModelVAP::getInstance('cronjob');
403 // dispatch cron job
404 $status = $model->dispatch($id_cron, $secure_key);
405
406 if (!$status)
407 {
408 // get error from model
409 $error = $model->getError();
410
411 if ($error instanceof Exception)
412 {
413 UIErrorFactory::raiseError($error->getCode(), $error->getMessage());
414 }
415 else
416 {
417 UIErrorFactory::raiseError(500, $error ? $error : JText::translate('ERROR'));
418 }
419 }
420
421 // terminate with success
422 echo 1;
423 $app->close();
424 }
425
426 /**
427 * Task used to allow the plugins to self-render their contents
428 * within a stand-alone page of VikAppointments.
429 *
430 * Any arguments to be passed to the plugins can be specified
431 * within the `args` property in query string, such as:
432 * &args[action]=xxx&args[id]=123
433 *
434 * @return void
435 *
436 * @since 1.7.3
437 */
438 public function plugin_action()
439 {
440 $input = JFactory::getApplication()->input;
441 $dispatcher = VAPFactory::getEventDispatcher();
442
443 // a configuration array to be passed to the plugins
444 $state = new VAPActionState($input->get('args', [], 'array'));
445
446 // extract action from request and also from args array
447 $action = $input->get('action', $state->get('action'));
448
449 if (!$action)
450 {
451 // action was not specified in request
452 throw new InvalidArgumentException('Missing action.', 400);
453 }
454
455 // create actions container
456 $container = new VAPActionContainerMap();
457
458 /**
459 * Trigger event to let the plugins be able to render their contents on a
460 * stand-alone page or to execute some tasks as a controller.
461 *
462 * @param VAPActionContainer $container A container used to attach the task.
463 *
464 * @return void
465 *
466 * @since 1.7.3
467 */
468 $dispatcher->trigger('onDispatchVikAppointmentsPluginAction', array($container));
469
470 // notify elements subscribed to the requested event/action
471 $result = new VAPActionResultHtml($container->notify($action, $state));
472
473 if ($result->has())
474 {
475 // display HTML
476 echo $result->display();
477 }
478 }
479
480 /**
481 * Task used to allow the plugins to self-render their contents
482 * within a stand-alone page of VikAppointments.
483 *
484 * Any arguments to be passed to the plugins can be specified
485 * within the `args` property in query string, such as:
486 * &args[action]=xxx&args[id]=123
487 *
488 * @return void
489 *
490 * @since 1.6.6
491 * @deprecated 1.8 Use plugin_action() instead.
492 */
493 public function plugin_view()
494 {
495 $input = JFactory::getApplication()->input;
496 $dispatcher = VAPFactory::getEventDispatcher();
497
498 // a configuration array to be passed to the plugins
499 $args = $input->get('args', array(), 'array');
500
501 // wrap arguments in a registry for a better ease of use
502 $args = new JRegistry($args);
503
504 /**
505 * Trigger event to let the plugins be able to
506 * render their contents on a stand-alone page.
507 *
508 * @param Registry $args A registry of arguments set in request.
509 *
510 * @return string The HTML to display.
511 *
512 * @since 1.6.6
513 */
514 $html = $dispatcher->trigger('onDisplayVikAppointmentsPluginView', array($args));
515
516 // strip empty values from resulting array
517 $html = array_filter($html);
518
519 // join HTML responses and echo string
520 echo implode("\n", $html);
521 }
522
523 /**
524 * #########################
525 * # API End-Point #
526 * #########################
527 *
528 * This function is the end-point to dispatch events requested from external connections.
529 * It is required to specify all the following values:
530 *
531 * @param string username The username for login.
532 * @param string password The password for login.
533 * @param string event The name of the event to dispatch.
534 *
535 * It is also possible to pre-send certain arguments to dispatch within the event:
536 *
537 * @param array args The arguments of the event (optional).
538 * All the specified values are cleansed with string filtering.
539 *
540 * @return string In case of error it is returned a JSON string with the code (errcode)
541 * and the message of the error (error).
542 * In case of success the result may vary on the dispatched event.
543 *
544 * @since 1.7
545 */
546 public function api()
547 {
548 $app = JFactory::getApplication();
549 $input = $app->input;
550
551 // instantiate API Framework
552 // leave constructor empty to select default plugins folder:
553 // .../helpers/libraries/apislib/apis/plugins/
554 $api = VAPFactory::getApi();
555
556 // check if API connections are allowed, otherwise disable all
557 if (!$api->isEnabled())
558 {
559 // use a HTTP response in place of the JSON one
560 UIErrorFactory::raiseError(403, 'API Framework is disabled');
561 }
562
563 // flush stored API logs
564 JModelVAP::getInstance('apilog')->flush();
565
566 // get credentials
567 $username = $input->getString('username');
568 $password = $input->getString('password');
569
570 // get event to dispatch
571 $event = $input->get('event');
572
573 // try to retrieve the plugin arguments from JSON body
574 $args = $input->json->getArray();
575
576 if (!$args)
577 {
578 // arguments not found, try to retrieve them from the request
579 $args = $input->get('args', array(), 'array');
580 }
581
582 // create a Login for this user
583 $login = new VAPApiLogin($username, $password, $input->server->get('REMOTE_ADDR'));
584
585 // do login
586 if (!$api->connect($login))
587 {
588 // user is not authorized to login
589 $api->output($api->getError());
590
591 // terminate the request
592 $app->close();
593 }
594
595 // user correctly logged in, dispatch the event
596 $result = $api->trigger($event, $args);
597
598 // always disconnect the user
599 $api->disconnect();
600
601 if (!$result)
602 {
603 // event error thrown
604 $api->output($api->getError());
605 }
606
607 // terminate the request
608 $app->close();
609 }
610
611 /**
612 * This task is used by the administrators when they want to temporarily pause
613 * the e-mail notifications sent whenever a specific cron job faces an error.
614 *
615 * It is required to specify all the following values:
616 *
617 * @param int id The ID of the cron job to pause.
618 * @param string key The cron secure key to prevent a public access.
619 *
620 * It is also possible to specify the following parameter to choose how long
621 * to pause the e-mail notifications.
622 *
623 * @param int days The notifications will be stopped for the number of
624 * specified days. If not provided, they will be stopped
625 * for one week.
626 *
627 * @return void
628 */
629 public function cron_pause_notif()
630 {
631 $app = JFactory::getApplication();
632
633 $key = $app->input->get->get('key');
634
635 // first of all, make sure the user is authorized to access this resource
636 if (!$key || strcmp($key, VAPFactory::getConfig()->getString('cron_secure_key', '')))
637 {
638 // wrong secure key or missing
639 throw new Exception(JText::translate('JERROR_ALERTNOAUTHOR'), 403);
640 }
641
642 // get selected cron job
643 $id = $app->input->get->getUint('id', 0);
644
645 if (!$id)
646 {
647 // wrong secure key or missing
648 throw new Exception('Missing cron job ID', 403);
649 }
650
651 // how long the notification should be stopped?
652 $days = $app->input->get->getUint('days', 0);
653
654 if (!$days)
655 {
656 $lookup = [
657 1 => JText::plural('VAP_N_DAYS', 1),
658 7 => JText::plural('VAP_N_DAYS', 7),
659 14 => JText::plural('VAP_N_WEEKS', 2),
660 30 => JText::plural('VAP_N_MONTHS', 1),
661 60 => JText::plural('VAP_N_MONTHS', 2),
662 365 => JText::plural('VAP_N_YEARS', 1),
663 ];
664
665 $li = [];
666
667 foreach ($lookup as $days => $label)
668 {
669 $href = JRoute::rewrite('index.php?option=com_vikappointments&task=cron_pause_notif&id=' . $id . '&key=' . $key . '&days=' . $days . '&Itemid=' . $app->input->getUint('Itemid'));
670 $li[] = '<li><a href="' . $href . '">' . $label . '</a></li>';
671 }
672
673 // prompt the selection of the pause interval
674 echo '<div class="vap-confirmpage order-notice"><p>' . JText::translate('VAPCRONJOBPAUSENOTIF') . '</p><ul style="list-style: none; padding: 0; margin: 10px 0;">' . implode("\n", $li) . '</ul></div>';
675 return;
676 }
677
678 $model = JModelVAP::getInstance('cronjob');
679
680 // attempt to pause the notifications
681 $date = $model->pauseNotifications($id, $days);
682
683 if ($date)
684 {
685 // pause successful
686 $date = JHtml::fetch('date', $date, JText::translate('DATE_FORMAT_LC2'));
687 echo '<div class="vap-confirmpage order-good">' . JText::sprintf('VAP_CRON_NOTIF_PAUSE_SUCCESS', $date) . '</div>';
688 }
689 else
690 {
691 // an error has occurred
692 $error = $model->getError(null, true) ?: 'Unknown.';
693 echo '<div class="vap-confirmpage order-error">' . JText::sprintf('VAP_CRON_NOTIF_PAUSE_ERROR', $error) . '</div>';
694 }
695 }
696 }
697