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 / lib.vikappointments.php
vikappointments / site / helpers Last commit date
libraries 2 days ago mail_attach 2 days ago mail_tmpls 2 days ago pdf 2 days ago index.html 2 days ago lib.vikappointments.php 2 days ago
lib.vikappointments.php
10103 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 * VikAppointments component helper class.
16 *
17 * @since 1.0
18 */
19 abstract class VikAppointments
20 {
21 /**
22 * Checks if the system supports multi-lingual contents.
23 *
24 * @return integer
25 *
26 * @deprecated 1.8 Use VAPConfig instead.
27 */
28 public static function isMultilanguage()
29 {
30 return VAPFactory::getConfig()->getBool('ismultilang');
31 }
32
33 /**
34 * Returns a list of admin e-mails.
35 *
36 * @return array
37 */
38 public static function getAdminMailList()
39 {
40 // get all e-mails
41 $admin_mail_list = VAPFactory::getConfig()->getString('adminemail');
42
43 if (!strlen($admin_mail_list))
44 {
45 return array();
46 }
47
48 return array_map('trim', explode(',', $admin_mail_list));
49 }
50
51 /**
52 * Returns the admin e-mail. If not specified, the one set in the global
53 * configuration of the CMS will be used.
54 *
55 * @return string
56 */
57 public static function getAdminMail()
58 {
59 // get all e-mails
60 $mails = self::getAdminMailList();
61
62 if ($mails)
63 {
64 // returns first e-mail available
65 return $mails[0];
66 }
67
68 // use owner e-mail
69 return JFactory::getApplication()->get('mailfrom');
70 }
71
72 /**
73 * Returns the sender e-mail. If not provided, the first one
74 * specified for the admin e-mail field will be used.
75 *
76 * @return string
77 */
78 public static function getSenderMail()
79 {
80 // get sender from config
81 $sender = VAPFactory::getConfig()->getString('senderemail');
82
83 if (empty($sender))
84 {
85 // missing sender, use the default one
86 $sender = self::getAdminMail();
87 }
88
89 return $sender;
90 }
91
92 /**
93 * Returns the file path to attach within the e-mail for customers (if any).
94 *
95 * @return string
96 *
97 * @deprecated 1.8 Use VikAppointments::getMailAttachmentsURL() instead.
98 */
99 public static function getMailAttachmentURL()
100 {
101 $attachments = static::getMailAttachmentsURL();
102
103 return array_shift($attachments);
104 }
105
106 /**
107 * Returns a list of file paths to attach within the e-mail for customers (if any).
108 *
109 * @return array
110 *
111 * @since 1.7
112 */
113 public static function getMailAttachmentsURL()
114 {
115 // get attachments list
116 $attachments = VAPFactory::getConfig()->getArray('mailattach');
117
118 // map the attachments to have a full path
119 return array_map(function($attachment)
120 {
121 return VAPMAIL_ATTACHMENTS . DIRECTORY_SEPARATOR . $attachment;
122 }, $attachments);
123 }
124
125 /**
126 * Returns an array containing the e-mail sending rules.
127 * The array contains the rules for these entities: customer, employee, admin.
128 *
129 * @return array
130 */
131 public static function getSendMailWhen()
132 {
133 $config = VAPFactory::getConfig();
134
135 return array(
136 'customer' => $config->getUint('mailcustwhen'),
137 'employee' => $config->getUint('mailempwhen'),
138 'admin' => $config->getUint('mailadminwhen'),
139 );
140 }
141
142 /**
143 * Returns an array containing the rules to attach the ICS file within the e-mail.
144 * The array contains the rules for these entities: customer, employee, admin.
145 *
146 * @param mixed $client When provided, the method will return a boolean meaning
147 * whether the attachments should be included for that client.
148 *
149 * @return array|boolean
150 */
151 public static function getAttachmentPropertiesICS($client = null)
152 {
153 $ics = explode(';', VAPFactory::getConfig()->get('icsattach'));
154
155 $prop = array(
156 'customer' => $ics[0],
157 'employee' => $ics[1],
158 'admin' => $ics[2],
159 );
160
161 if ($client)
162 {
163 // immediately check whether the client supports ICS as attachment
164 return isset($prop[$client]) ? (bool) $prop[$client] : false;
165 }
166
167 return $prop;
168 }
169
170 /**
171 * Returns an array containing the rules to attach the CSV file within the e-mail.
172 * The array contains the rules for these entities: customer, employee, admin.
173 *
174 * @param mixed $client When provided, the method will return a boolean meaning
175 * whether the attachments should be included for that client.
176 *
177 * @return array|boolean
178 */
179 public static function getAttachmentPropertiesCSV($client = null)
180 {
181 $csv = explode(';', VAPFactory::getConfig()->get('csvattach'));
182
183 $prop = array(
184 'customer' => $csv[0],
185 'employee' => $csv[1],
186 'admin' => $csv[2],
187 );
188
189 if ($client)
190 {
191 // immediately check whether the client supports CSV as attachment
192 return isset($prop[$client]) ? (bool) $prop[$client] : false;
193 }
194
195 return $prop;
196 }
197
198 /**
199 * Returns an array containing the opening hours and minutes.
200 *
201 * @return array
202 */
203 public static function getOpeningTime()
204 {
205 $op = explode(':', VAPFactory::getConfig()->get('openingtime'));
206
207 return array(
208 'hour' => (int) $op[0],
209 'min' => (int) $op[1],
210 );
211 }
212
213 /**
214 * Returns an array containing the closing hours and minutes.
215 *
216 * @return array
217 */
218 public static function getClosingTime()
219 {
220 $cl = explode(':', VAPFactory::getConfig()->get('closingtime'));
221
222 return array(
223 'hour' => (int) $cl[0],
224 'min' => (int) $cl[1],
225 );
226 }
227
228 /**
229 * Returns an array containing all the available modes to sort
230 * the employees.
231 *
232 * @return array
233 */
234 public static function getEmployeesAvailableOrderings()
235 {
236 $modes = VAPFactory::getConfig()->getJSON('emplistmode');
237
238 $arr = array();
239
240 foreach ($modes as $i => $v)
241 {
242 if ($v == 1)
243 {
244 $arr[] = $i;
245 }
246 }
247
248 if (!count($arr))
249 {
250 // always allow the default ordering (a..Z)
251 $arr[0] = 1;
252 }
253
254 return $arr;
255 }
256
257 /**
258 * Returns the default ordering to use to list the employees.
259 *
260 * @return string
261 */
262 public static function getEmployeesListingMode()
263 {
264 $arr = self::getEmployeesAvailableOrderings();
265
266 return $arr[0];
267 }
268
269 /**
270 * Returns the configuration array containing the listing details
271 * of the employees. The array will contain the following keys:
272 *
273 * @property integer desclength The maximum number of characters.
274 * @property integer linkhref The event to use when clicking the image.
275 * @property integer filtergroups Whether the group filtering is enabled or not.
276 * @property integer filterordering Whether the ordering selection is enabled or not.
277 * @property integer ajaxsearch The type of AJAX search.
278 *
279 * @return array An associative array.
280 *
281 * @deprecated 1.8 Use VAPConfig instead.
282 */
283 public static function getEmployeesListingDetails()
284 {
285 $config = VAPFactory::getConfig();
286
287 return array(
288 'desclength' => $config->getInt('empdesclength'),
289 'linkhref' => $config->getInt('emplinkhref'),
290 'filtergroups' => $config->getInt('empgroupfilter'),
291 'filterordering' => $config->getInt('empordfilter'),
292 'ajaxsearch' => $config->getInt('empajaxsearch'),
293 );
294 }
295
296 /**
297 * Returns the configuration array containing the listing details
298 * of the services. The array will contain the following keys:
299 *
300 * @property integer desclength The maximum number of characters.
301 * @property integer linkhref The event to use when clicking the image.
302 *
303 * @return array An associative array.
304 *
305 * @deprecated 1.8 Use VAPConfig instead.
306 */
307 public static function getServicesListingDetails()
308 {
309 $config = VAPFactory::getConfig();
310
311 return array(
312 'desclength' => $config->getInt('serdesclength'),
313 'linkhref' => $config->getInt('serlinkhref'),
314 );
315 }
316
317 /**
318 * Returns the first month of the calendar to display (used only if the month is not in the past).
319 *
320 * @return integer
321 *
322 * @see getCalendarFirstYear()
323 *
324 * @deprecated 1.8 Use VAPConfig instead.
325 */
326 public static function getCalendarFirstMonth()
327 {
328 return VAPFactory::getConfig()->getInt('calsfrom');
329 }
330
331 /**
332 * Returns the year to which the first month is referring to.
333 *
334 * @return integer
335 *
336 * @see getCalendarFirstMonth()
337 *
338 * @deprecated 1.8 Use VAPConfig instead.
339 */
340 public static function getCalendarFirstYear()
341 {
342 $year = VAPFactory::getConfig()->getInt('calsfromyear');
343
344 if (!$year)
345 {
346 // return current year
347 $arr = getdate();
348 $year = $arr['year'];
349 }
350
351 return $year;
352 }
353
354 /**
355 * Checks if the customer can add an item into its cart.
356 *
357 * @param integer $cart_size The current number of items.
358 *
359 * @return boolean True if allowed, false otherwise.
360 */
361 public static function canAddItemToCart($cart_size)
362 {
363 $config = VAPFactory::getConfig();
364
365 $max_cart_size = $config->getInt('maxcartsize');
366
367 return ($max_cart_size == -1 || $cart_size < $max_cart_size || !$config->getBool('enablecart'));
368 }
369
370 /**
371 * Checks if the packages system is enabled.
372 *
373 * @return integer
374 *
375 * @deprecated 1.8 Use VAPConfig instead.
376 */
377 public static function isPackagesEnabled()
378 {
379 return VAPFactory::getConfig()->getBool('enablepackages');
380 }
381
382 /**
383 * Returns the confirmation message that will be asked while deleting an item.
384 * In case the confirmation message is disabled, an empty string will be returned.
385 *
386 * @return string
387 */
388 public static function getConfirmSystemMessage()
389 {
390 if (VAPFactory::getConfig()->getBool('askconfirm', true))
391 {
392 return JText::translate('VAPSYSTEMCONFIRMATIONMSG');
393 }
394
395 return '';
396 }
397
398 /**
399 * Returns a list containing the closing days.
400 *
401 * Each element of the list is an associative array with the following properties:
402 * @property integer ts The unix timestamp.
403 * @property string date The formatted date.
404 * @property integer freq The closing frequency (0 = single day, 1 = weekly, 2 = monthly, 3 = yearly).
405 *
406 * @param integer $id_ser If specified, filters the closing days for this service.
407 *
408 * @return array
409 */
410 public static function getClosingDays($id_ser = null)
411 {
412 $config = VAPFactory::getConfig();
413 $_str = $config->get('closingdays');
414
415 if (!$_str)
416 {
417 return array();
418 }
419
420 static $pool = array();
421
422 // check if the closing days were already fetched
423 if (isset($pool[$id_ser]))
424 {
425 // return cached list
426 return $pool[$id_ser];
427 }
428
429 $cd = explode(';;', $_str);
430
431 $list = array();
432
433 for ($i = 0, $n = count($cd); $i < $n; $i++)
434 {
435 $_app = explode(':', $cd[$i]);
436
437 /**
438 * Fetch services assigned to the closing day.
439 *
440 * @since 1.6.3
441 */
442 $_app[2] = empty($_app[2]) || $_app[2] == '*' ? array() : explode(',', $_app[2]);
443
444 // copy closing day only if it can be used for the specified service, if any
445 if (!$id_ser || !$_app[2] || in_array($id_ser, $_app[2]))
446 {
447 /**
448 * The closing days are now saved in military format
449 * as UTC dates and, since they are globally considered,
450 * they do not have to be adjusted to the local timezone.
451 *
452 * @since 1.7
453 */
454 $list[] = array(
455 'ts' => $_app[0],
456 'date' => JFactory::getDate($_app[0])->format($config->get('dateformat')),
457 'freq' => $_app[1],
458 'services' => $_app[2],
459 );
460 }
461 }
462
463 // cache closing days
464 $pool[$id_ser] = $list;
465
466 return $list;
467 }
468
469 /**
470 * Returns a list containing the closing periods.
471 *
472 * Each element of the list is an associative array with the following properties:
473 * @property start start The starting closing period (UNIX timestamp).
474 * @property end end The ending closing period (UNIX timestamp).
475 *
476 * @param integer $id_ser If specified, filters the closing days for this service.
477 *
478 * @return array
479 */
480 public static function getClosingPeriods($id_ser = null)
481 {
482 $config = VAPFactory::getConfig();
483 $_str = $config->get('closingperiods');
484
485 if (!$_str)
486 {
487 return array();
488 }
489
490 static $pool = array();
491
492 // check if the closing periods were already fetched
493 if (isset($pool[$id_ser]))
494 {
495 // return cached list
496 return $pool[$id_ser];
497 }
498
499 $cp = explode(';;', $_str);
500
501 $list = array();
502
503 for ($i = 0, $n = count($cp); $i < $n; $i++)
504 {
505 $_app = explode(':', $cp[$i]);
506
507 /**
508 * Fetch services assigned to the closing day.
509 *
510 * @since 1.6.3
511 */
512 $_app[2] = empty($_app[2]) || $_app[2] == '*' ? array() : explode(',', $_app[2]);
513
514 // copy closing day only if it can be used for the specified service, if any
515 if (!$id_ser || !$_app[2] || in_array($id_ser, $_app[2]))
516 {
517 /**
518 * The closing periods are now saved in military format
519 * as UTC dates and, since they are globally considered,
520 * they do not have to be adjusted to the local timezone.
521 *
522 * @since 1.7
523 */
524 $list[] = array(
525 'start' => $_app[0],
526 'end' => $_app[1],
527 'datestart' => JFactory::getDate($_app[0])->format($config->get('dateformat')),
528 'dateend' => JFactory::getDate($_app[1])->format($config->get('dateformat')),
529 'services' => $_app[2],
530 );
531 }
532 }
533
534 // cache closing periods
535 $pool[$id_ser] = $list;
536
537 return $list;
538 }
539
540 /**
541 * Returns the configuration array containing the recurrence parameters.
542 * The array will contain the following keys:
543 *
544 * @property array repeat The allowed repeat options.
545 * @property integer min The minimum number of elements that can be selected.
546 * @property integer max The maximum number of elements that can be selected.
547 * @property array for The allowed for options.
548 *
549 * @return array
550 */
551 public static function getRecurrenceParams()
552 {
553 $config = VAPFactory::getConfig();
554
555 return array(
556 'repeat' => explode(';', $config->get('repeatbyrecur')),
557 'min' => $config->getUint('minamountrecur'),
558 'max' => $config->getUint('maxamountrecur'),
559 'for' => explode(';', $config->get('fornextrecur')),
560 );
561 }
562
563 /**
564 * Checks if the reviews for the services are enabled.
565 *
566 * @return boolean
567 */
568 public static function isServicesReviewsEnabled()
569 {
570 $config = VAPFactory::getConfig();
571
572 return $config->getBool('enablereviews') && $config->getBool('revservices');
573 }
574
575 /**
576 * Checks if the reviews for the employees are enabled.
577 *
578 * @return boolean
579 */
580 public static function isEmployeesReviewsEnabled()
581 {
582 $config = VAPFactory::getConfig();
583
584 return $config->getBool('enablereviews') && $config->getBool('revemployees');
585 }
586
587 /**
588 * Checks if the waiting list system is enabled.
589 *
590 * @return integer
591 *
592 * @deprecated 1.8 Use VAPConfig instead.
593 */
594 public static function isWaitingList()
595 {
596 return VAPFactory::getConfig()->getBool('enablewaitlist');
597 }
598
599 /**
600 * Returns an array containing the fields to display within the
601 * reservations list (back-end).
602 *
603 * @param boolean $custom True to return the custom fields in place
604 * of the default ones (@since 1.7).
605 *
606 * @return array
607 */
608 public static function getListableFields($custom = false)
609 {
610 $config = VAPFactory::getConfig();
611
612 // get custom fields
613 $str = $config->get($custom ? 'listablecf' : 'listablecols');
614
615 if (empty($str))
616 {
617 return array();
618 }
619
620 return explode(',', $str);
621 }
622
623 /**
624 * Checks if the SMS notifications should be send to the customers.
625 *
626 * @return integer
627 */
628 public static function getSmsApiToCustomer()
629 {
630 $str = explode(',', VAPFactory::getConfig()->get('smsapito'));
631 return intval($str[0]);
632 }
633
634 /**
635 * Checks if the SMS notifications should be send to the employees.
636 *
637 * @return integer
638 */
639 public static function getSmsApiToEmployee()
640 {
641 $str = explode(',', VAPFactory::getConfig()->get('smsapito'));
642 return intval($str[1]);
643 }
644
645 /**
646 * Checks if the SMS notifications should be send to the administrator.
647 *
648 * @return integer
649 */
650 public static function getSmsApiToAdmin()
651 {
652 $str = explode(',', VAPFactory::getConfig()->get('smsapito'));
653 return intval($str[2]);
654 }
655
656 /**
657 * Returns the configuration array of the selected SMS driver.
658 *
659 * @return array
660 *
661 * @deprecated 1.8 Use VAPConfig instead.
662 */
663 public static function getSmsApiFields()
664 {
665 return VAPFactory::getConfig()->getArray('smsapifields');
666 }
667
668 /**
669 * Returns the value of the specified configuration setting.
670 *
671 * @param string $param The setting name.
672 *
673 * @return string The configuration value.
674 *
675 * @deprecated 1.8 Use VAPConfig instead.
676 */
677 private static function getFieldFromConfig($param)
678 {
679 return VAPFactory::getConfig()->getString($param, '');
680 }
681
682 /**
683 * Returns the audio file that will be used to play a
684 * notification sound every time a new order comes in.
685 *
686 * It is possible to use a different audio simply by uploading
687 * that file within the admin/assets/audio/ folder. The most
688 * recent file will be always used.
689 *
690 * @return string The file URI.
691 *
692 * @since 1.7
693 */
694 public static function getNotificationSound()
695 {
696 // get all files placed within audio folder
697 $files = glob(VAPADMIN . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . 'audio' . DIRECTORY_SEPARATOR . '*');
698
699 // take only audio files (exclude default one too)
700 $files = array_values(array_filter($files, function($f)
701 {
702 if (preg_match("/[\/\\\\]notification\.mp3$/i", $f))
703 {
704 // ignore default file
705 return false;
706 }
707
708 // keep only the most common audio files
709 return preg_match("/\.(mp3|mp4|wav|ogg|aac|flac)$/i", $f);
710 }));
711
712 if (!$files)
713 {
714 // no additional audio files, use the default one
715 return VAPASSETS_ADMIN_URI . 'audio/notification.mp3';
716 }
717
718 // sort files from the most recent to the oldest
719 usort($files, function($a, $b)
720 {
721 // sort by descending creation date
722 return filemtime($b) - filemtime($a);
723 });
724
725 // return most recent file
726 return VAPASSETS_ADMIN_URI . 'audio/' . basename($files[0]);
727 }
728
729 /**
730 * Loads the cart framework.
731 *
732 * @return void
733 */
734 public static function loadCartLibrary()
735 {
736 VAPLoader::import('libraries.cart.cart');
737 VAPLoader::import('libraries.cart.utils');
738 VAPLoader::import('libraries.cart.core');
739 }
740
741 /**
742 * Loads the cart packages framework.
743 *
744 * @return void
745 */
746 public static function loadCartPackagesLibrary()
747 {
748 VAPLoader::import('libraries.cartpack.cart');
749 VAPLoader::import('libraries.cartpack.core');
750 }
751
752 /**
753 * Loads the cron framework.
754 *
755 * @return boolean True if the framework was loaded, false otherwise.
756 */
757 public static function loadCronLibrary()
758 {
759 static $loaded = 0;
760
761 if (!$loaded)
762 {
763 // include base framework
764 VAPLoader::import('libraries.cron.core');
765 // include system overrides
766 VAPLoader::import('libraries.cron.overrides.formbuilder');
767
768 // register folder containing the supported cron jobs
769 VAPCronDispatcher::addIncludePath(VAPADMIN . DIRECTORY_SEPARATOR . 'cronjobs');
770 }
771
772 // do not load more than once
773 $loaded = 1;
774 }
775
776 /**
777 * Checks whether the user can cancel an appointment.
778 *
779 * @param object $appointment The appointment details.
780 *
781 * @return boolean True if allowed, false otherwise.
782 */
783 public static function canUserCancelOrder($appointment)
784 {
785 // make sure the appointment is confirmed
786 if ($appointment->statusRole != 'APPROVED')
787 {
788 // appointment not confirmed
789 return false;
790 }
791
792 $config = VAPFactory::getConfig();
793
794 if (!$config->getBool('enablecanc'))
795 {
796 // do not go ahead in case the cancellation is disabled
797 return false;
798 }
799
800 // get current time (UTC)
801 $threshold = JFactory::getDate();
802
803 // get minimum required days
804 $mindays = $config->getUint('canctime');
805
806 // sum minimum required days to current date and time
807 $threshold->modify('+' . $mindays . ' days');
808
809 if ($threshold >= $appointment->checkin->utc)
810 {
811 // not enough time to complete the cancellation, the check-in
812 // is too close to the current date and time
813 return false;
814 }
815
816 /**
817 * This event can be used to apply additional conditions to the
818 * cancellation restrictions. When this event is triggered, the
819 * system already validated the standard conditions and the
820 * cancellation has been approved for the usage.
821 *
822 * @param mixed $appointment The appointment to check.
823 *
824 * @return boolean Return false to deny the cancellation.
825 *
826 * @since 1.7
827 */
828 if (VAPFactory::getEventDispatcher()->false('onCheckAppointmentCancellation', array($appointment)))
829 {
830 // a plugin prevented the cancellation
831 return false;
832 }
833
834 // cancellation allowed
835 return true;
836 }
837
838 /**
839 * Checks whether the user can approve its own appointment.
840 *
841 * @param object $appointment The appointment details.
842 *
843 * @return boolean True if possible, false otherwise.
844 *
845 * @since 1.7.1
846 */
847 public static function canUserApproveOrder($appointment)
848 {
849 // make sure the order is pending
850 if ($appointment->statusRole != 'PENDING')
851 {
852 // order not pending
853 return false;
854 }
855
856 // check if the order has been assigned to a payment
857 if ($appointment->payment)
858 {
859 // get payment details
860 $payment = JModelVAP::getInstance('payment')->getItem($appointment->payment->id);
861
862 // check if the payment allows the self-confirmation
863 $enabled = $payment && $payment->selfconfirm;
864 }
865 else
866 {
867 // otherwise check global parameter
868 $enabled = VAPFactory::getConfig()->getBool('selfconfirm');
869 }
870
871 if (!$enabled)
872 {
873 // do not go ahead in case the self-confirmation is disabled
874 return false;
875 }
876
877 /**
878 * This event can be used to apply additional conditions to the
879 * self-confirmation restrictions. When this event is triggered, the
880 * system already validated the standard conditions and the
881 * confirmation has been approved for the usage.
882 *
883 * @param mixed $appointment The appointment to check.
884 *
885 * @return boolean Return false to deny the confirmation.
886 *
887 * @since 1.7.1
888 */
889 $res = VAPFactory::getEventDispatcher()->trigger('onCheckAppointmentSelfConfirmation', array($appointment));
890
891 // check if at least a plugin returned FALSE to prevent the confirmation
892 return !in_array(false, $res, true);
893 }
894
895 /**
896 * Returns all the custom fields that can be actually edited for the specified order and page.
897 *
898 * @param mixed $order The order to check.
899 *
900 * @return VAPCustomFieldsLoader
901 *
902 * @since 1.7.7
903 */
904 public static function getEditableOrderFields($order)
905 {
906 if (!static::canUserUpdateFields($order))
907 {
908 // return a null pointer
909 VAPLoader::import('libraries.customfields.emptyloader');
910 return new VAPCustomFieldsEmptyLoader;
911 }
912
913 VAPLoader::import('libraries.customfields.loader');
914
915 // get all custom fields
916 $fieldsLoader = VAPCustomFieldsLoader::getInstance()
917 ->translate()
918 ->setLanguageFilter()
919 ->onPage('order');
920
921 // check if we have an order of appointments
922 if (!empty($order->appointments))
923 {
924 // filter by booked service
925 foreach ($order->appointments as $appointment)
926 {
927 $fieldsLoader->forService($appointment->service->id);
928 }
929
930 if ($order->sameEmp)
931 {
932 // obtain custom fields of the specified employee
933 $fieldsLoader->ofEmployee($order->appointments[0]->employee->id ?? 0);
934 }
935 }
936
937 return $fieldsLoader;
938 }
939
940 /**
941 * Checks whether the user can update the fields of the specified order.
942 *
943 * @param object $order The order details.
944 *
945 * @return bool True if allowed, false otherwise.
946 *
947 * @since 1.7.7
948 */
949 public static function canUserUpdateFields($order)
950 {
951 $flag = VAPFactory::getConfig()->get('editablefields');
952
953 $checkout = null;
954
955 // fetch the highest checkout
956 foreach ($order->appointments ?? [] as $appointment)
957 {
958 $checkout = max($checkout, $appointment->checkout->iso8601);
959 }
960
961 if ($checkout && $checkout < JFactory::getDate('now')->toISO8601())
962 {
963 // never allow the users to edit fields after the appointment check-out
964 return false;
965 }
966
967 if ($flag === '*')
968 {
969 // always allowed
970 return true;
971 }
972
973 if ($flag === 'confirmed')
974 {
975 // only if the order status is approved
976 return ($order->statusRole ?? '') === 'APPROVED';
977 }
978
979 if ($flag === 'pending')
980 {
981 // only if the order status is pending
982 return ($order->statusRole ?? '') === 'PENDING';
983 }
984
985 // never allowed
986 return false;
987 }
988
989 /**
990 * Checks whether the system should display the price of the selected service.
991 *
992 * @param mixed $service Either an object holding the service details
993 * or its identifier.
994 *
995 * @return boolean True to display the price, false otherwise.
996 *
997 * @since 1.7
998 */
999 public static function shouldDisplayServicePrice($service)
1000 {
1001 if (!$service)
1002 {
1003 // invalid argument, auto-hide price
1004 return false;
1005 }
1006
1007 if (is_int($service))
1008 {
1009 // load service details
1010 $service = JModelVAP::getInstance('service')->getItem((int) $service);
1011
1012 // service not found...
1013 if ($service)
1014 {
1015 return false;
1016 }
1017 }
1018 else
1019 {
1020 // cast service to array
1021 $service = (object) $service;
1022 }
1023
1024 if ($service->price <= 0)
1025 {
1026 // the service has not cost, do not display the price
1027 return false;
1028 }
1029
1030 // get details of the currently logged-in user
1031 $customer = VikAppointments::getCustomer();
1032
1033 // in case the customer exists and it is subscribed to the
1034 // specified service, hide the cost per appointment
1035 if ($customer && $customer->isSubscribed($service->id))
1036 {
1037 return false;
1038 }
1039
1040 /**
1041 * This event can be used to apply additional conditions to the
1042 * visibility restrictions. When this event is triggered, the
1043 * system already validated all the standard conditions.
1044 *
1045 * @param object $service The service details.
1046 *
1047 * @return boolean Return false to hide the price.
1048 *
1049 * @since 1.7
1050 */
1051 if (VAPFactory::getEventDispatcher()->false('onChooseDisplayServicePrice', array($service)))
1052 {
1053 // a plugin decided to hide the price
1054 return false;
1055 }
1056
1057 // display the price
1058 return true;
1059 }
1060
1061 /**
1062 * Returns the media upload settings.
1063 *
1064 * @return array
1065 *
1066 * @since 1.7
1067 */
1068 public static function getMediaProperties()
1069 {
1070 $config = VAPFactory::getConfig();
1071
1072 $prop = array();
1073 $prop['oriwres'] = $config->getUint('oriwres', 512);
1074 $prop['orihres'] = $config->getUint('orihres', 512);
1075 $prop['smallwres'] = $config->getUint('smallwres', 256);
1076 $prop['smallhres'] = $config->getUint('smallhres', 256);
1077 $prop['isresize'] = $config->getUint('isresize', 0);
1078
1079 return $prop;
1080 }
1081
1082 /**
1083 * Updates the media upload settings.
1084 *
1085 * @param array &$prop
1086 *
1087 * @return void
1088 *
1089 * @since 1.7
1090 */
1091 public static function storeMediaProperties(&$prop)
1092 {
1093 $config = VAPFactory::getConfig();
1094
1095 $lookup = array(
1096 'oriwres',
1097 'orihres',
1098 'smallwres',
1099 'smallhres',
1100 'isresize',
1101 );
1102
1103 foreach ($lookup as $k)
1104 {
1105 if (isset($prop[$k]))
1106 {
1107 $config->set($k, $prop[$k]);
1108 }
1109 }
1110
1111 $config->set('isconfig', 1);
1112 }
1113
1114 /**
1115 * Helper method used to upload the given image (retrieved from $_FILES)
1116 * into the specified destination.
1117 *
1118 * @param array $img An associative array with the file details.
1119 * @param string $dest The destination path.
1120 *
1121 * @return object The uploading result.
1122 *
1123 * @uses uploadFile()
1124 */
1125 public static function uploadImage($img, $dest)
1126 {
1127 // upload as a normal file
1128 return self::uploadFile($img, $dest, 'jpeg,jpg,png,gif,bmp', $overwrite = false);
1129 }
1130
1131 /**
1132 * Uploads a media file.
1133 *
1134 * @param string $name The media name.
1135 * @param mixed $prop The upload settings.
1136 * @param boolean $overwrite True to overwrite the existing media.
1137 *
1138 * @return array A response.
1139 *
1140 * @since 1.7
1141 *
1142 * @uses uploadFile()
1143 */
1144 public static function uploadMedia($name, $prop = null, $overwrite = false)
1145 {
1146 $model = JModelVAP::getInstance('media');
1147
1148 // upload as a normal file
1149 $resp = self::uploadFile($name, VAPMEDIA . DIRECTORY_SEPARATOR, $model->getFileAllowedRegex('image'), $overwrite);
1150
1151 // import image cropper
1152 VAPLoader::import('libraries.image.resizer');
1153
1154 if ($resp->status)
1155 {
1156 if ($prop === null)
1157 {
1158 // get media settings if not specified
1159 $prop = self::getMediaProperties();
1160 }
1161
1162 if ($prop['isresize'] == 1)
1163 {
1164 // crop original image
1165 $crop_dest = str_replace($resp->name, '$_' . $resp->name, $resp->path);
1166
1167 VAPImageResizer::proportionalImage($resp->path, $crop_dest, $prop['oriwres'], $prop['orihres']);
1168 copy($crop_dest, $resp->path);
1169 unlink($crop_dest);
1170 }
1171
1172 // generate thumbnail
1173 $thumb_dest = VAPMEDIA_SMALL . DIRECTORY_SEPARATOR . $resp->name;
1174 VAPImageResizer::proportionalImage($resp->path, $thumb_dest, $prop['smallwres'], $prop['smallhres']);
1175 }
1176
1177 return $resp;
1178 }
1179
1180 /**
1181 * Moves the given file within the specified destination.
1182 *
1183 * @param mixed $name Either the file object or the $_FILES name in
1184 * which the file is located.
1185 * @param string $dest The path (including filename) in which to move the uploaded file.
1186 * @param string $filters Either a regex or a comma-separated list of supported extensions.
1187 * @param boolean $overwrite True to overwrite the file if the destination is already occupied.
1188 * Otherwise a progressive file name will be used.
1189 *
1190 * @return object An object containing the information of the uploaded file. It is possible to
1191 * check whether the file was uploaded by looking the "status" property. In case of
1192 * errors, the "errno" property will return an error code to understand why the error
1193 * occurred (1: unsupported file, 2: generic upload error).
1194 */
1195 public static function uploadFile($name, $dest, $filters = '*', $overwrite = false)
1196 {
1197 if (is_string($name))
1198 {
1199 $file = JFactory::getApplication()->input->files->get($name, null, 'array');
1200 }
1201 else
1202 {
1203 $file = (array) $name;
1204 }
1205
1206 /**
1207 * Check whether the destination path includes the file name or
1208 * just the upload directory.
1209 *
1210 * @since 1.7
1211 */
1212 if (preg_match("/\.[a-zA-Z0-9]+$/", $dest) && !is_dir($dest))
1213 {
1214 // We found a path ending with a probable extension and
1215 // the destination path is not a directory.
1216 // Extract the filename from the destination path.
1217 $filename = basename($dest);
1218 // remove file name from destination
1219 $dest = dirname($dest);
1220 }
1221 else
1222 {
1223 // otherwise use the file name of the uploaded file
1224 $filename = isset($file['name']) ? $file['name'] : null;
1225 }
1226
1227 $dest = rtrim($dest, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
1228
1229 /**
1230 * Added support for status property.
1231 * The [esit] property will be temporarily
1232 * left for backward compatibility.
1233 *
1234 * @since 1.7
1235 *
1236 * @deprecated 1.8 [esit] property will be removed.
1237 */
1238 $obj = new stdClass;
1239 $obj->status = 0;
1240 $obj->esit = 0;
1241 $obj->errno = null;
1242 $obj->path = '';
1243
1244 if (isset($file) && strlen(trim($file['name'])) > 0)
1245 {
1246 jimport('joomla.filesystem.file');
1247
1248 $filename = JFile::makeSafe(str_replace(' ', '-', $filename));
1249 $src = $file['tmp_name'];
1250
1251 // use a different name if the file path is already occupied
1252 if (!$overwrite && file_exists($dest . $filename))
1253 {
1254 $j = 2;
1255
1256 // split file name and file extension
1257 if (preg_match("/(.*?)(\.[a-z0-9]+)$/i", $filename, $match))
1258 {
1259 $basename = $match[1];
1260 $file_ext = $match[2];
1261 }
1262 else
1263 {
1264 $basename = $filename;
1265 $file_ext = '';
1266 }
1267
1268 // increase counter as long as the path is occupied
1269 while (file_exists($dest . $basename . '-' . $j . $file_ext))
1270 {
1271 $j++;
1272 }
1273
1274 // construct file name
1275 $filename = $basename . '-' . $j . $file_ext;
1276 }
1277
1278 // create file object
1279 $obj->path = $dest . $filename;
1280 $obj->src = $src;
1281 $obj->name = $filename;
1282
1283 // make sure the file is compatible
1284 if (self::isFileTypeCompatible($filename, $filters))
1285 {
1286 // complete file upload
1287 if (JFile::upload($src, $obj->path, $use_streams = false, $allow_unsafe = true))
1288 {
1289 $obj->status = 1;
1290 $obj->esit = 1;
1291 }
1292 else
1293 {
1294 // unable to upload the file
1295 $obj->errno = 2;
1296 }
1297 }
1298 else
1299 {
1300 // file not supported
1301 $obj->errno = 1;
1302 // include fetched MIME type
1303 $obj->mimeType = $file['type'];
1304 }
1305 }
1306
1307 return $obj;
1308 }
1309
1310 /**
1311 * Helper method used to print formatted prices according to the global configuration.
1312 *
1313 * @param float $price The price to format.
1314 * @param string $symb The currency symbol. If not provided the default one will be used.
1315 * @param integer $pos The currency position (1 = after price, 2 = before price).
1316 * If not provided, the default one will be used.
1317 *
1318 * @return string The formatted price.
1319 *
1320 * @deprecated 1.8 Use VAPCurrency::format() instead.
1321 */
1322 public static function printPriceCurrencySymb($price, $symb = null, $pos = null)
1323 {
1324 $options = array();
1325
1326 if ($symb)
1327 {
1328 $options['symbol'] = $symb;
1329 }
1330
1331 if ($pos)
1332 {
1333 $options['position'] = (int) $pos;
1334 }
1335
1336 return VAPFactory::getCurrency()->format($price, $options);
1337 }
1338
1339 /**
1340 * Checks if the value for the specified custom field is valid.
1341 *
1342 * @param array $cf The custom field details.
1343 * @param mixed $val The given value.
1344 *
1345 * @return boolean True if valid, false otherwise.
1346 */
1347 public static function isCustomFieldValid($cf, $val)
1348 {
1349 return $cf['required'] == 0
1350 || ($cf['type'] != 'file' && strlen($val))
1351 || ($cf['type'] == 'file' && !empty($val['name']));
1352 }
1353
1354 /**
1355 * Helper method used to check whether the given file name
1356 * supports one of the given filters.
1357 *
1358 * @param mixed $file Either the file name or the uploaded file.
1359 * @param string $filters Either a regex or a comma-separated list of supported extensions.
1360 * The regex must be inclusive of
1361 *
1362 * @return boolean True if supported, false otherwise.
1363 */
1364 public static function isFileTypeCompatible($file, $filters)
1365 {
1366 // make sure the filters query is not empty
1367 if (strlen($filters) == 0)
1368 {
1369 // cannot assert whether the file could be accepted or not
1370 return false;
1371 }
1372
1373 // check whether all the files are accepted
1374 if ($filters == '*')
1375 {
1376 return true;
1377 }
1378
1379 // use the file MIME TYPE in case of array
1380 if (is_array($file))
1381 {
1382 $file = $file['type'];
1383 }
1384
1385 /**
1386 * Check if we are handling a regex.
1387 *
1388 * @since 1.7
1389 */
1390 if (static::isRegex($filters))
1391 {
1392 return (bool) preg_match($filters, $file);
1393 }
1394
1395 // fallback to comma-separated list
1396 $types = array_filter(preg_split("/\s*,\s*/", $filters));
1397
1398 foreach ($types as $t)
1399 {
1400 // remove initial dot if specified
1401 $t = ltrim($t, '.');
1402 // escape slashes to avoid breaking the regex
1403 $t = preg_replace("/\//", '\/', $t);
1404
1405 // check if the file ends with the given extension
1406 if (preg_match("/{$t}$/", $file))
1407 {
1408 return true;
1409 }
1410 }
1411
1412 return false;
1413 }
1414
1415 /**
1416 * Checks whether the given string is a structured PCRE regex.
1417 * It simply makes sure that the string owns valid delimiters.
1418 * A delimiter can be any non-alphanumeric, non-backslash,
1419 * non-whitespace character.
1420 *
1421 * @param string $str The string to check.
1422 *
1423 * @return boolean True if a regex, false otherwise.
1424 *
1425 * @since 1.7
1426 */
1427 public static function isRegex($str)
1428 {
1429 // first of all make sure the first character is a supported delimiter
1430 if (!preg_match("/^([!#$%&'*+,.\/:;=?@^_`|~\-(\[{<\"])/", $str, $match))
1431 {
1432 // no valid delimiter
1433 return false;
1434 }
1435
1436 // get delimiter
1437 $d = $match[1];
1438
1439 // lookup used to check if we should take a different ending delimiter
1440 $lookup = array(
1441 '{' => '}',
1442 '[' => ']',
1443 '(' => ')',
1444 '<' => '>',
1445 );
1446
1447 if (isset($lookup[$d]))
1448 {
1449 $d = $lookup[$d];
1450 }
1451
1452 // make sure the regex ends with the delimiter found
1453 return (bool) preg_match("/\\{$d}[gimsxU]*$/", $str);
1454 }
1455
1456 /**
1457 * Helper method used to check whether the system supports the coupon codes, simply
1458 * by checking whether the coupons database table contains at least a record.
1459 *
1460 * @param string $applicable The section to which the coupon should be
1461 * applied (@since 1.7).
1462 *
1463 * @return boolean
1464 */
1465 public static function hasCoupon($applicable = null)
1466 {
1467 $dbo = JFactory::getDbo();
1468
1469 // Check if there is at least a coupon stored in the system.
1470 // It is not needed to check if it is valid because we have just
1471 // to know if the owner used them, so that the system can display
1472 // a form to redeem the coupons or not.
1473 $q = $dbo->getQuery(true)
1474 ->select(1)
1475 ->from($dbo->qn('#__vikappointments_coupon'));
1476
1477 if ($applicable)
1478 {
1479 $q->where(array(
1480 $dbo->qn('applicable') . ' IS NULL',
1481 $dbo->qn('applicable') . ' = ' . $dbo->q(''),
1482 $dbo->qn('applicable') . ' = ' . $dbo->q($applicable),
1483 ), 'OR');
1484 }
1485
1486 $dbo->setQuery($q, 0, 1);
1487 $dbo->execute();
1488
1489 return (bool) $dbo->getNumRows();
1490 }
1491
1492 /**
1493 * Validates the given coupon code.
1494 *
1495 * @param array $coupon The coupon details.
1496 * @param mixed $cart The cart instance. When this method is called
1497 * from the back-end, this argument will be empty.
1498 *
1499 * @return boolean True if the coupon can be redeemed, false otherwise.
1500 */
1501 public static function validateCoupon($coupon, $cart = null)
1502 {
1503 // always treat as array
1504 $coupon = (array) $coupon;
1505
1506 /**
1507 * Check whether the coupon code is applicable for the appointments.
1508 *
1509 * @since 1.7
1510 */
1511 if (!empty($coupon['applicable']) && $coupon['applicable'] != 'appointments')
1512 {
1513 return false;
1514 }
1515
1516 /**
1517 * Treat dates in UTC.
1518 *
1519 * @since 1.7
1520 */
1521 $now = JFactory::getDate()->toSql();
1522
1523 if ($cart)
1524 {
1525 $items = $cart->getItemsList();
1526 }
1527 else
1528 {
1529 $items = array();
1530 }
1531
1532 if ($coupon['type'] == 2 && $coupon['max_quantity'] - $coupon['used_quantity'] <= 0)
1533 {
1534 // reached the maximum number of usages
1535 return false;
1536 }
1537
1538 /**
1539 * Check whether the current user should be able to redeem the coupon one more time.
1540 * Go ahead only in case the cart argument is set, in order to bypass this restriction
1541 * while validating the coupon code from the back-end.
1542 *
1543 * @since 1.7
1544 */
1545 if ($coupon['maxperuser'] > 0 && $cart)
1546 {
1547 // get current user
1548 $user = JFactory::getUser();
1549
1550 if ($user->guest)
1551 {
1552 // when a maximum amount is specified, the coupon can be redeemed only by logged-in users
1553 return false;
1554 }
1555
1556 $dbo = JFactory::getDbo();
1557
1558 $q = $dbo->getQuery(true)
1559 ->select('COUNT(1)')
1560 ->from($dbo->qn('#__vikappointments_reservation', 'r'))
1561 ->leftjoin($dbo->qn('#__vikappointments_users', 'u') . ' ON ' . $dbo->qn('r.id_user') . ' = ' . $dbo->qn('u.id'))
1562 ->where($dbo->qn('r.coupon_str') . ' LIKE ' . $dbo->q($coupon['code'] . ';;%'))
1563 ->andWhere(array(
1564 $dbo->qn('r.createdby') . ' = ' . $user->id,
1565 $dbo->qn('u.jid') . ' = ' . $user->id,
1566 ), 'OR');
1567
1568 $dbo->setQuery($q);
1569
1570 // compare the number of usages against the maximum limit
1571 if ((int) $dbo->loadResult() >= $coupon['maxperuser'])
1572 {
1573 // the user already redeemed the coupon all the allowed times
1574 return false;
1575 }
1576 }
1577
1578 /**
1579 * Validate publishing dates using specified mode.
1580 *
1581 * @since 1.6.3
1582 */
1583 if (!VAPDateHelper::isNull($coupon['dstart']) || !VAPDateHelper::isNull($coupon['dend']))
1584 {
1585 if ($coupon['pubmode'] == 1 || !$items)
1586 {
1587 // compare current day with starting date (if specified)
1588 if (!VAPDateHelper::isNull($coupon['dstart']) && $coupon['dstart'] > $now)
1589 {
1590 // the coupon is not yet valid
1591 return false;
1592 }
1593
1594 // compare current day with ending date (if specified)
1595 if (!VAPDateHelper::isNull($coupon['dend']) && $coupon['dend'] < $now)
1596 {
1597 // the coupon is expired
1598 return false;
1599 }
1600 }
1601 else
1602 {
1603 // all items must match the specified dates
1604 foreach ($items as $i)
1605 {
1606 // get appointment check-in
1607 $checkin = $i->getCheckinDate();
1608
1609 // compare check-in with starting date (if specified)
1610 if (!VAPDateHelper::isNull($coupon['dstart']) && $coupon['dstart'] > $checkin)
1611 {
1612 // the coupon is not yet valid
1613 return false;
1614 }
1615
1616 // compare check-in with ending date (if specified)
1617 if (!VAPDateHelper::isNull($coupon['dend']) && $coupon['dend'] < $checkin)
1618 {
1619 // the coupon is expired
1620 return false;
1621 }
1622 }
1623 }
1624 }
1625
1626 /**
1627 * Re-added the minimum cost condition that had
1628 * been accidentally removed.
1629 *
1630 * @since 1.6.5
1631 */
1632 if ($cart && $cart->getTotalCost() < $coupon['mincost'])
1633 {
1634 // total cost is too low
1635 return false;
1636 }
1637
1638 if ($items)
1639 {
1640 $coupon_services = self::getAllCouponServices($coupon['id']);
1641 $coupon_employees = self::getAllCouponEmployees($coupon['id']);
1642
1643 $ok_coupon_service = true;
1644 $ok_coupon_employee = true;
1645
1646 foreach ($items as $i)
1647 {
1648 $ok_coupon_service = $ok_coupon_service && (count($coupon_services) == 0 || in_array($i->getServiceID() , $coupon_services));
1649 $ok_coupon_employee = $ok_coupon_employee && (count($coupon_employees) == 0 || in_array($i->getEmployeeID(), $coupon_employees));
1650 }
1651
1652 if (!$ok_coupon_service || !$ok_coupon_employee)
1653 {
1654 return false;
1655 }
1656 }
1657
1658 if ($coupon['lastminute'])
1659 {
1660 // get last minute threshold
1661 $threshold = JFactory::getDate('+' . $coupon['lastminute'] . ' hours')->toSql();
1662
1663 foreach ($items as $i)
1664 {
1665 // get appointment check-in
1666 $checkin = $i->getCheckinDate();
1667
1668 if ($threshold < $checkin)
1669 {
1670 return false;
1671 }
1672 }
1673 }
1674
1675 /**
1676 * This event can be used to apply additional conditions to the coupon validation.
1677 * When this event is triggered, the system already validated the standard conditions
1678 * and the coupon has been approved for the usage.
1679 *
1680 * @param string $scope For which entity we are redeeming the coupon.
1681 * @param array $coupon The coupon code to check.
1682 * @param mixed $cart The cart instance.
1683 *
1684 * @return boolean Return false to deny the coupon activation.
1685 *
1686 * @since 1.7
1687 */
1688 if (VAPFactory::getEventDispatcher()->false('onBeforeActivateCoupon', array('appointment', $coupon, $cart)))
1689 {
1690 // a plugin decided to deny the coupon activation
1691 return false;
1692 }
1693
1694 return true;
1695 }
1696
1697 /**
1698 * Validates the given coupon code (for packages purchase).
1699 *
1700 * @param array $coupon The coupon details.
1701 * @param mixed $cart The cart instance. When this method is called
1702 * from the back-end, this argument will be empty.
1703 *
1704 * @return boolean True if the coupon can be redeemed, false otherwise.
1705 *
1706 * @since 1.7
1707 */
1708 public static function validatePackagesCoupon($coupon, $cart = null)
1709 {
1710 // always treat as array
1711 $coupon = (array) $coupon;
1712
1713 /**
1714 * Check whether the coupon code is applicable for the packages.
1715 *
1716 * @since 1.7
1717 */
1718 if (!empty($coupon['applicable']) && $coupon['applicable'] != 'packages')
1719 {
1720 return false;
1721 }
1722
1723 $now = JFactory::getDate()->toSql();
1724
1725 if ($coupon['type'] == 2 && $coupon['max_quantity'] - $coupon['used_quantity'] <= 0)
1726 {
1727 // reached the maximum number of usages
1728 return false;
1729 }
1730
1731 // Check whether the current user should be able to redeem the coupon one more time.
1732 // Go ahead only in case the cart argument is set, in order to bypass this restriction
1733 // while validating the coupon code from the back-end.
1734 if ($coupon['maxperuser'] > 0 && $cart)
1735 {
1736 // get current user
1737 $user = JFactory::getUser();
1738
1739 if ($user->guest)
1740 {
1741 // when a maximum amount is specified, the coupon can be redeemed only by logged-in users
1742 return false;
1743 }
1744
1745 $dbo = JFactory::getDbo();
1746
1747 $q = $dbo->getQuery(true)
1748 ->select('COUNT(1)')
1749 ->from($dbo->qn('#__vikappointments_package_order', 'o'))
1750 ->leftjoin($dbo->qn('#__vikappointments_users', 'u') . ' ON ' . $dbo->qn('o.id_user') . ' = ' . $dbo->qn('u.id'))
1751 ->where($dbo->qn('o.coupon') . ' LIKE ' . $dbo->q($coupon['code'] . ';;%'))
1752 ->andWhere(array(
1753 $dbo->qn('o.createdby') . ' = ' . $user->id,
1754 $dbo->qn('u.jid') . ' = ' . $user->id,
1755 ), 'OR');
1756
1757 $dbo->setQuery($q);
1758
1759 // compare the number of usages against the maximum limit
1760 if ((int) $dbo->loadResult() >= $coupon['maxperuser'])
1761 {
1762 // the user already redeemed the coupon all the allowed times
1763 return false;
1764 }
1765 }
1766
1767 // validate publishing dates using specified mode
1768 if (!VAPDateHelper::isNull($coupon['dstart']) || !VAPDateHelper::isNull($coupon['dend']))
1769 {
1770 // compare current day with starting date (if specified)
1771 if (!VAPDateHelper::isNull($coupon['dstart']) && $coupon['dstart'] > $now)
1772 {
1773 // the coupon is not yet valid
1774 return false;
1775 }
1776
1777 // compare current day with ending date (if specified)
1778 if (!VAPDateHelper::isNull($coupon['dend']) && $coupon['dend'] < $now)
1779 {
1780 // the coupon is expired
1781 return false;
1782 }
1783 }
1784
1785 if ($cart && $cart->getTotalCost() < $coupon['mincost'])
1786 {
1787 // total cost is too low
1788 return false;
1789 }
1790
1791 /**
1792 * This event can be used to apply additional conditions to the coupon validation.
1793 * When this event is triggered, the system already validated the standard conditions
1794 * and the coupon has been approved for the usage.
1795 *
1796 * @param string $scope For which entity we are redeeming the coupon.
1797 * @param array $coupon The coupon code to check.
1798 * @param mixed $cart The cart instance.
1799 *
1800 * @return boolean Return false to deny the coupon activation.
1801 *
1802 * @since 1.7
1803 */
1804 if (VAPFactory::getEventDispatcher()->false('onBeforeActivateCoupon', array('package', $coupon, $cart)))
1805 {
1806 // a plugin decided to deny the coupon activation
1807 return false;
1808 }
1809
1810 return true;
1811 }
1812
1813 /**
1814 * Validates the given coupon code (for subscriptions purchase).
1815 *
1816 * @param array $coupon The coupon details.
1817 * @param JModel $model The cart model instance. When this method is called
1818 * from the back-end, this argument will be empty.
1819 *
1820 * @return boolean True if the coupon can be redeemed, false otherwise.
1821 *
1822 * @since 1.7
1823 */
1824 public static function validateSubscriptionsCoupon($coupon, $cart = null)
1825 {
1826 // always treat as array
1827 $coupon = (array) $coupon;
1828
1829 /**
1830 * Check whether the coupon code is applicable for the subscriptions.
1831 *
1832 * @since 1.7
1833 */
1834 if (!empty($coupon['applicable']) && $coupon['applicable'] != 'subscriptions')
1835 {
1836 return false;
1837 }
1838
1839 $now = JFactory::getDate()->toSql();
1840
1841 if ($coupon['type'] == 2 && $coupon['max_quantity'] - $coupon['used_quantity'] <= 0)
1842 {
1843 // reached the maximum number of usages
1844 return false;
1845 }
1846
1847 // Check whether the current user should be able to redeem the coupon one more time.
1848 // Go ahead only in case the cart argument is set, in order to bypass this restriction
1849 // while validating the coupon code from the back-end.
1850 if ($coupon['maxperuser'] > 0 && $cart)
1851 {
1852 // get current user
1853 $user = JFactory::getUser();
1854
1855 if ($user->guest)
1856 {
1857 // when a maximum amount is specified, the coupon can be redeemed only by logged-in users
1858 return false;
1859 }
1860
1861 $dbo = JFactory::getDbo();
1862
1863 $q = $dbo->getQuery(true)
1864 ->select('COUNT(1)')
1865 ->from($dbo->qn('#__vikappointments_package_order', 'o'))
1866 ->leftjoin($dbo->qn('#__vikappointments_users', 'u') . ' ON ' . $dbo->qn('o.id_user') . ' = ' . $dbo->qn('u.id'))
1867 ->where($dbo->qn('o.coupon') . ' LIKE ' . $dbo->q($coupon['code'] . ';;%'))
1868 ->andWhere(array(
1869 $dbo->qn('o.createdby') . ' = ' . $user->id,
1870 $dbo->qn('u.jid') . ' = ' . $user->id,
1871 ), 'OR');
1872
1873 $dbo->setQuery($q);
1874
1875 // compare the number of usages against the maximum limit
1876 if ((int) $dbo->loadResult() >= $coupon['maxperuser'])
1877 {
1878 // the user already redeemed the coupon all the allowed times
1879 return false;
1880 }
1881 }
1882
1883 // validate publishing dates using specified mode
1884 if (!VAPDateHelper::isNull($coupon['dstart']) || !VAPDateHelper::isNull($coupon['dend']))
1885 {
1886 // compare current day with starting date (if specified)
1887 if (!VAPDateHelper::isNull($coupon['dstart']) && $coupon['dstart'] > $now)
1888 {
1889 // the coupon is not yet valid
1890 return false;
1891 }
1892
1893 // compare current day with ending date (if specified)
1894 if (!VAPDateHelper::isNull($coupon['dend']) && $coupon['dend'] < $now)
1895 {
1896 // the coupon is expired
1897 return false;
1898 }
1899 }
1900
1901 if ($cart)
1902 {
1903 // get selected subscription
1904 $subscr = $cart->getSubscription();
1905
1906 // make sure the base cost of the subscription is equals or higher than
1907 // the coupon minimum threshold
1908 if ($subscr['price'] < $coupon['mincost'])
1909 {
1910 // total cost is too low
1911 return false;
1912 }
1913 }
1914
1915 /**
1916 * This event can be used to apply additional conditions to the coupon validation.
1917 * When this event is triggered, the system already validated the standard conditions
1918 * and the coupon has been approved for the usage.
1919 *
1920 * @param string $scope For which entity we are redeeming the coupon.
1921 * @param array $coupon The coupon code to check.
1922 * @param mixed $cart The cart instance.
1923 *
1924 * @return boolean Return false to deny the coupon activation.
1925 *
1926 * @since 1.7
1927 */
1928 if (VAPFactory::getEventDispatcher()->false('onBeforeActivateCoupon', array('subscription', $coupon, $cart)))
1929 {
1930 // a plugin decided to deny the coupon activation
1931 return false;
1932 }
1933
1934 return true;
1935 }
1936
1937 /**
1938 * Returns all the services assigned to the specified coupon.
1939 *
1940 * @param integer $id_coupon The coupon ID.
1941 *
1942 * @return array A list containing the ID of the assigned services.
1943 */
1944 public static function getAllCouponServices($id_coupon)
1945 {
1946 $services = array();
1947
1948 $dbo = JFactory::getDbo();
1949
1950 $q = $dbo->getQuery(true)
1951 ->select($dbo->qn('id_service'))
1952 ->from($dbo->qn('#__vikappointments_coupon_service_assoc'))
1953 ->where($dbo->qn('id_coupon') . ' = ' . (int) $id_coupon);
1954
1955 $dbo->setQuery($q);
1956 return $dbo->loadColumn();
1957 }
1958
1959 /**
1960 * Returns all the employees assigned to the specified coupon.
1961 *
1962 * @param integer $id_coupon The coupon ID.
1963 *
1964 * @return array A list containing the ID of the assigned employees.
1965 */
1966 public static function getAllCouponEmployees($id_coupon)
1967 {
1968 $employees = array();
1969
1970 $dbo = JFactory::getDbo();
1971
1972 $q = $dbo->getQuery(true)
1973 ->select($dbo->qn('id_employee'))
1974 ->from($dbo->qn('#__vikappointments_coupon_employee_assoc'))
1975 ->where($dbo->qn('id_coupon') . ' = ' . (int) $id_coupon);
1976
1977 $dbo->setQuery($q);
1978 return $dbo->loadColumn();
1979 }
1980
1981 /**
1982 * Marks the specified coupon as used.
1983 * In addition, removes the coupon if it should be deleted once
1984 * the maximum number of usages is reached.
1985 *
1986 * @param array $coupon The coupon details.
1987 *
1988 * @return boolean True on success, false otherwise.
1989 *
1990 * @deprecated 1.8 Use VikAppointmentsModelCoupon::redeem() instead.
1991 */
1992 public static function couponUsed($coupon, $dbo = null)
1993 {
1994 return JModelVAP::getInstance('coupon')->redeem($coupon);
1995 }
1996
1997 /**
1998 * Validates the specified recurring data.
1999 *
2000 * @param integer $repeat The repeat by identifier.
2001 * @param integer $amount The selected amount.
2002 * @param integer $for The repeat for identifier.
2003 *
2004 * @return boolean True if valid, false otherwise.
2005 */
2006 public static function validateRecurringData($repeat, $amount, $for)
2007 {
2008 if (!VAPFactory::getConfig()->getBool('enablerecur'))
2009 {
2010 return false;
2011 }
2012
2013 $params = self::getRecurrenceParams();
2014
2015 if (($repeat - 1) < 0 || ($repeat - 1) >= count($params['repeat']) || $params['repeat'][$repeat - 1] == 0)
2016 {
2017 return false;
2018 }
2019
2020 if (($for - 1) < 0 || ($for - 1) >= count($params['for']) || $params['for'][$for - 1] == 0)
2021 {
2022 return false;
2023 }
2024
2025 if ($amount < $params['min'] || $params['max'] < $amount)
2026 {
2027 return false;
2028 }
2029
2030 return true;
2031 }
2032
2033 /**
2034 * Calculates the discounted total cost considering the coupon code and
2035 * the user credit (if specified).
2036 *
2037 * @param float $total_cost The base total cost.
2038 * @param array $coupon The coupon code.
2039 * @param mixed &$credit The current user credit. Provide true
2040 * to retrieve the user credit from the database.
2041 * @param float &$creditUsed The credit amount that has been used.
2042 *
2043 * @return float The final discounted total cost.
2044 */
2045 public static function getDiscountTotalCost($total_cost, $coupon, &$credit = false, &$creditUsed = 0)
2046 {
2047 if (!empty($coupon))
2048 {
2049 if ($coupon['percentot'] == 1)
2050 {
2051 // percent
2052 $total_cost -= $total_cost * $coupon['value'] / 100.0;
2053 }
2054 else
2055 {
2056 // total
2057 $total_cost -= $coupon['value'];
2058 }
2059 }
2060
2061 /**
2062 * If the credit is specified, use it.
2063 *
2064 * @since 1.6
2065 */
2066 if ($credit === true)
2067 {
2068 $user = JFactory::getUser();
2069 $credit = 0.0;
2070
2071 if (!$user->guest)
2072 {
2073 $dbo = JFactory::getDbo();
2074
2075 $q = $dbo->getQuery(true)
2076 ->select($dbo->qn('credit'))
2077 ->from($dbo->qn('#__vikappointments_users'))
2078 ->where($dbo->qn('jid') . ' = ' . $user->id)
2079 ->orWhere(array(
2080 $dbo->qn('jid') . ' <= 0',
2081 $dbo->qn('billing_mail') . ' = ' . $dbo->q($user->email),
2082 ), 'AND');
2083
2084 $dbo->setQuery($q, 0, 1);
2085 $credit = (float) $dbo->loadResult();
2086 }
2087 }
2088
2089 if ($credit && $total_cost > 0)
2090 {
2091 if ($credit > $total_cost)
2092 {
2093 $creditUsed = $total_cost;
2094 }
2095 else
2096 {
2097 $creditUsed = $credit;
2098 }
2099
2100 $total_cost -= $credit;
2101 }
2102
2103 return max(array($total_cost, 0));
2104 }
2105
2106 /**
2107 * Calculates the total amount to pay for the specified appointment.
2108 *
2109 * @param mixed $order The appointment details.
2110 *
2111 * @return float The resulting total.
2112 *
2113 * @since 1.7.8
2114 */
2115 public static function getTotalBeforePayment($order)
2116 {
2117 // subtract amount already paid
2118 $total = $order->totals->gross - $order->totals->paid;
2119
2120 if ($order->statusRole == 'PENDING')
2121 {
2122 // calculate the deposit to leave according to the preferred configuration (if any)
2123 $deposit = static::getDepositAmountToLeave($total, $order->skip_deposit);
2124
2125 if ($deposit !== false)
2126 {
2127 // use the specified deposit
2128 $total = $deposit;
2129 }
2130
2131 if ($order->deposit > 0 && !$order->skip_deposit)
2132 {
2133 // the customer needs to pay a custom deposit
2134 $total = min($order->deposit, $order->totals->gross - $order->totals->paid);
2135 }
2136 }
2137
2138 return max(0, (float) $total);
2139 }
2140
2141 /**
2142 * Returns the deposit amount that should be left.
2143 *
2144 * @param float $total_cost The total cost of the order.
2145 * @param boolean $ignore True to skip the deposit calculation.
2146 * It should be verified when the customer decides
2147 * to pay the full amount (only for OPTIONAL mode).
2148 *
2149 * @return mixed The new amount if the deposit should be left, otherwise false.
2150 */
2151 public static function getDepositAmountToLeave($total_cost, $ignore = false)
2152 {
2153 $config = VAPFactory::getConfig();
2154
2155 $use = $config->getUint('usedeposit');
2156
2157 if (!$use)
2158 {
2159 // [NO] do not use deposit
2160 return false;
2161 }
2162
2163 if ($use == 1 && $ignore)
2164 {
2165 // [OPTIONAL] the customer decided to pay the full amount
2166 return false;
2167 }
2168
2169 $deposit_after = $config->getFloat('depositafter', 0);
2170 $deposit_value = $config->getFloat('depositvalue', 0);
2171 $deposit_type = $config->getUint('deposittype', 1);
2172
2173 // make sure the condition is verified
2174 if ($total_cost > $deposit_after)
2175 {
2176 if ($deposit_type == 1)
2177 {
2178 // percent
2179 return round($total_cost * $deposit_value / 100.0, 2);
2180 }
2181 else
2182 {
2183 // total
2184 return $deposit_value;
2185 }
2186 }
2187
2188 // the total cost is still lower than the minimum required
2189 return false;
2190 }
2191
2192 /**
2193 * Returns all the ZIP codes of the given employee.
2194 *
2195 * @param integer $id_employee The employee ID.
2196 *
2197 * @return mixed The ZIP codes array if any, false otherwise.
2198 */
2199 public static function getEmployeeZipCodes($id_employee)
2200 {
2201 $dbo = JFactory::getDbo();
2202
2203 $q = $dbo->getQuery(true)
2204 ->select($dbo->qn('zipcodes'))
2205 ->from($dbo->qn('#__vikappointments_employee_settings'))
2206 ->where($dbo->qn('id_employee') . ' = ' . (int) $id_employee);
2207
2208 $dbo->setQuery($q, 0, 1);
2209 $zips = $dbo->loadResult();
2210
2211 return $zips ? json_decode($zips, true) : false;
2212 }
2213
2214 /**
2215 * Returns the ID of the custom field that will be used to validate the ZIP code.
2216 *
2217 * Tthis method will check also whether the selected services require a ZIP validation.
2218 * This way, we can prevent the blocking issue that occurred when the ZIP restriction
2219 * was enabled and the configuration didn't specify a field to validate the entered
2220 * ZIP Code (@since 1.7).
2221 *
2222 * @param integer $id_employee The employee ID to search for in case ths
2223 * global field is not set.
2224 * @param mixed $services Either an array or a service ID. When specified
2225 * the system will make sure that the selected
2226 * services requires the ZIP restriction (@since 1.7).
2227 *
2228 * @return mixed The field ID if specified, false otherwise.
2229 */
2230 public static function getZipCodeValidationFieldId($id_employee = null, $services = array())
2231 {
2232 // get global setting
2233 $id_field = VAPFactory::getConfig()->getInt('zipcfid');
2234
2235 if ($id_field <= 0 && $id_employee > 0)
2236 {
2237 $dbo = JFactory::getDbo();
2238
2239 $q = $dbo->getQuery(true)
2240 ->select($dbo->qn('zip_field_id'))
2241 ->from($dbo->qn('#__vikappointments_employee_settings'))
2242 ->where($dbo->qn('id_employee') . ' = ' . (int) $id_employee);
2243
2244 $dbo->setQuery($q, 0, 1);
2245 $id_field = $dbo->loadResult();
2246 }
2247
2248 if ($id_field <= 0)
2249 {
2250 // no field found
2251 return false;
2252 }
2253
2254 // when specified, validate the services
2255 if (!$services)
2256 {
2257 // nothing else to validate
2258 return $id_field;
2259 }
2260
2261 if (!is_array($services))
2262 {
2263 $services = (array) $services;
2264 }
2265
2266 // get service model
2267 $model = JModelVAP::getInstance('service');
2268
2269 foreach ($services as $id_service)
2270 {
2271 // check whether the service requires a ZIP validation
2272 if ($model->hasZipRestriction($id_service))
2273 {
2274 // yes, we can return the field found
2275 return $id_field;
2276 }
2277 }
2278
2279 // the booked services do not require the ZIP validation
2280 return false;
2281 }
2282
2283 /**
2284 * Helper method used to validate the specified ZIP code.
2285 *
2286 * @param string $zip_code The specified ZIP code.
2287 * @param mixed $employees Either an array or an employee ID.
2288 * @param mixed $services Either an array or a service ID. When specified
2289 * the system will make sure that the selected
2290 * services requires the ZIP restriction (@since 1.7).
2291 *
2292 * @return boolean True if valid, false otherwise.
2293 *
2294 * @uses getZipCodeValidationFieldId()
2295 */
2296 public static function validateZipCode($zip_code, $employees, $services = array())
2297 {
2298 if (!$employees)
2299 {
2300 $employees = array(0);
2301 }
2302 else if (!is_array($employees))
2303 {
2304 $employees = (array) $employees;
2305 }
2306
2307 // check whether the ZIP validation is required for the selected employee and service
2308 $id_field = self::getZipCodeValidationFieldId($employees[0], $services);
2309
2310 if (!$id_field)
2311 {
2312 // ZIP code validation not needed
2313 return true;
2314 }
2315
2316 if (empty($zip_code))
2317 {
2318 /**
2319 * Try to recover the ZIP code from the request by using the
2320 * name of the custom field.
2321 *
2322 * @since 1.7
2323 */
2324 $zip_code = JFactory::getApplication()->input->getString('vapcf' . $id_field);
2325 }
2326
2327 if (empty($zip_code))
2328 {
2329 // empty ZIP code, nothing to validate
2330 return false;
2331 }
2332
2333 // make ZIP code uppercase for a better validation
2334 $zip_code = strtoupper($zip_code);
2335 // accept only letters and digits
2336 $zip_code = preg_replace('/[^A-Z0-9]/i', '', $zip_code);
2337
2338 $global_zips = VAPFactory::getConfig()->getArray('zipcodes', array());
2339
2340 $dispatcher = VAPFactory::getEventDispatcher();
2341
2342 foreach ($employees as $id_emp)
2343 {
2344 $args = false;
2345
2346 if ($id_emp > 0)
2347 {
2348 // get ZIP codes specified by the employee
2349 $args = self::getEmployeeZipCodes($id_emp);
2350 }
2351
2352 if (!$args)
2353 {
2354 // use global ZIP codes
2355 $args = $global_zips;
2356 }
2357
2358 $valid = false;
2359
2360 /**
2361 * It is possible to use this hook to enhance or change the default algorithm
2362 * while checking whether a specific ZIP code is allowed or not.
2363 *
2364 * @param string $zip The ZIP code to validate.
2365 * @param array $accepted An array of accepted ZIP codes.
2366 * @param integer $id_emp The employee ID (0 or -1 mean global).
2367 * @param array $services An array of booked services.
2368 *
2369 * @return boolean Return true to accept the ZIP code. Return false to deny the
2370 * ZIP Code. Return null to rely on the default algorithm.
2371 *
2372 * @since 1.7
2373 */
2374 $result = $dispatcher->falseOrTrue('onValidateZipCode', array($zip_code, $args, $id_emp, $services));
2375
2376 if (!is_null($result))
2377 {
2378 // a plugin validated the ZIP code, use its decision
2379 return $result;
2380 }
2381
2382 // go ahead with the defaul algorithm
2383 for ($i = 0; $i < count($args) && !$valid; $i++)
2384 {
2385 if ($args[$i]['from'] <= $zip_code && $zip_code <= $args[$i]['to'])
2386 {
2387 $valid = true;
2388 }
2389 }
2390
2391 if (!$valid)
2392 {
2393 // ZIP code not accepted by this employee
2394 return false;
2395 }
2396 }
2397
2398 // the selected ZIP code is accepted by all the selected employees
2399 return true;
2400 }
2401
2402 /**
2403 * Helper method used to format a UNIX timestamp to the closest unit.
2404 * In case there is not a close unit, the specified date format will be used.
2405 *
2406 * @param string $dt_f The date format.
2407 * @param integer $ts The timestamp to format.
2408 *
2409 * @return string The formatted date.
2410 */
2411 public static function formatTimestamp($dt_f, $ts)
2412 {
2413 $diff = time() - $ts;
2414
2415 if (abs($diff) < 60)
2416 {
2417 return JText::translate('VAPDFNOW');
2418 }
2419
2420 $minutes = abs($diff) / 60;
2421
2422 if ($minutes < 60)
2423 {
2424 return JText::sprintf('VAPDFMINS' . ($diff > 0 ? 'AGO' : 'AFT'), floor($minutes));
2425 }
2426
2427 $hours = $minutes / 60;
2428
2429 if ($hours < 24)
2430 {
2431 $hours = floor($hours);
2432
2433 if ($hours == 1)
2434 {
2435 return JText::translate('VAPDFHOUR' . ($diff > 0 ? 'AGO' : 'AFT'));
2436 }
2437
2438 return JText::sprintf('VAPDFHOURS' . ($diff > 0 ? 'AGO' : 'AFT'), $hours);
2439 }
2440
2441 $days = $hours / 24;
2442
2443 if ($days < 7)
2444 {
2445 $days = floor($days);
2446
2447 if ($days == 1)
2448 {
2449 return JText::translate('VAPDFDAY' . ($diff > 0 ? 'AGO' : 'AFT'));
2450 }
2451
2452 return JText::sprintf('VAPDFDAYS' . ($diff > 0 ? 'AGO' : 'AFT'), $days);
2453 }
2454
2455 $weeks = $days / 7;
2456
2457 if ($weeks < 3)
2458 {
2459 $weeks = floor($weeks);
2460
2461 if ($weeks == 1)
2462 {
2463 return JText::translate('VAPDFWEEK' . ($diff > 0 ? 'AGO' : 'AFT'));
2464 }
2465
2466 return JText::sprintf('VAPDFWEEKS'.($diff > 0 ? 'AGO' : 'AFT'), $weeks);
2467 }
2468
2469 return date($dt_f, $ts);
2470 }
2471
2472 /**
2473 * Helper method to format the specified minutes to the closest unit.
2474 * For example, 150 minutes will be formatted as "1 hour & 30 min.".
2475 *
2476 * @param string $minutes The minutes amount.
2477 * @param boolean $apply True to format, false to return it plain.
2478 *
2479 * @return string The formatted string.
2480 */
2481 public static function formatMinutesToTime($minutes, $apply = null)
2482 {
2483 $min_str = array(
2484 JText::translate('VAPSHORTCUTMINUTE'), // singular
2485 '', // plural
2486 );
2487
2488 /**
2489 * If not specified, rely on global setting.
2490 *
2491 * @since 1.7
2492 */
2493 if (is_null($apply))
2494 {
2495 $apply = VAPFactory::getConfig()->getBool('formatduration');
2496 }
2497
2498 if (!$apply)
2499 {
2500 return $minutes . ' ' . $min_str[0];
2501 }
2502
2503 $hours_str = array(
2504 JText::translate('VAPFORMATHOUR'), // singular
2505 JText::translate('VAPFORMATHOURS'), // plural
2506 );
2507
2508 $days_str = array(
2509 JText::translate('VAPFORMATDAY'), // singular
2510 JText::translate('VAPFORMATDAYS'), // plural
2511 );
2512
2513 $weeks_str = array(
2514 JText::translate('VAPFORMATWEEK'), // singular
2515 JText::translate('VAPFORMATWEEKS'), // plural
2516 );
2517
2518 $comma_char = JText::translate('VAPFORMATCOMMASEP');
2519 $and_char = JText::translate('VAPFORMATANDSEP');
2520
2521 $is_negative = $minutes < 0 ? 1 : 0;
2522 $minutes = abs($minutes);
2523
2524 $format = "";
2525
2526 while ($minutes >= 60)
2527 {
2528 $app_str = "";
2529
2530 if ($minutes >= 10080)
2531 {
2532 // weeks
2533 $val = floor($minutes / 10080);
2534
2535 $app_str = $val . ' ' . $weeks_str[(int) ($val > 1)]; // if greater than 1 then plural, otherwise singular
2536 $minutes = $minutes % 10080;
2537 }
2538 else if ($minutes >= 1440)
2539 {
2540 // days
2541 $val = floor($minutes / 1440);
2542
2543 $app_str = $val . ' ' . $days_str[(int) ($val > 1)]; // if greater than 1 then plural, otherwise singular
2544 $minutes = $minutes % 1440;
2545 }
2546 else
2547 {
2548 // hours
2549 $val = floor($minutes / 60);
2550
2551 $app_str = $val . ' ' . $hours_str[(int) ($val > 1)]; // if greater than 1 then plural, otherwise singular
2552 $minutes = $minutes % 60;
2553 }
2554
2555 $sep = '';
2556
2557 if ($minutes > 0)
2558 {
2559 $sep = $comma_char;
2560 }
2561 else if ($minutes == 0)
2562 {
2563 $sep = " $and_char";
2564 }
2565
2566 $format .= (!empty($format) ? $sep . ' ' : '') . $app_str;
2567 }
2568
2569 if ($minutes > 0)
2570 {
2571 $format .= (!empty($format) ? " $and_char " : '') . $minutes . ' ' . $min_str[0];
2572 }
2573
2574 if ($is_negative)
2575 {
2576 $format = '-' . $format;
2577 }
2578
2579 return $format;
2580 }
2581
2582 /**
2583 * Helper method used to format the checkin timestamp.
2584 * It may return all the following values:
2585 * - today In case the checkin is for the current day (e.g. today in 2 hours).
2586 * - tomorrow In case the checkin is for the next day (e.g. tomorrow @ 10:00).
2587 * - datetime A formatted datetime (e.g. 2018-07-28 @ 10:00).
2588 *
2589 * @param string $dt_f The default date format.
2590 * @param string $t_f The default time format.
2591 * @param integer $ts The checkin timestamp.
2592 *
2593 * @return string The formatted checkin.
2594 *
2595 * @uses formatMinutesToTime()
2596 */
2597 public static function formatCheckinTimestamp($dt_f, $t_f, $ts)
2598 {
2599 $today = getdate();
2600 $date = getdate($ts);
2601 $diff = $date[0] - $today[0];
2602
2603 $today_no_time = strtotime('00:00:00');
2604 $date_no_time = strtotime('00:00:00', $ts);
2605 $diff_no_time = $date_no_time - $today_no_time;
2606
2607 if ($diff > 0 && $diff_no_time >= -3600 && $diff_no_time <= 3600)
2608 {
2609 return JText::sprintf('VAPTODAYIN', self::formatMinutesToTime(ceil($diff / 60)));
2610 }
2611 else if ($diff_no_time >= 82800 && $diff_no_time <= 90000)
2612 {
2613 return JText::sprintf('VAPTOMORROWAT', date($t_f, $ts));
2614 }
2615
2616 return date($dt_f, $ts);
2617 }
2618
2619 /**
2620 * Helper method used to render the contents of HTML descriptions.
2621 *
2622 * @param string $description The description to render.
2623 * @param string $task The view/task that invoked this method.
2624 * @param array $params An array of options.
2625 *
2626 * @return string The rendered description.
2627 */
2628 public static function renderHtmlDescription($description, $task, $params = array())
2629 {
2630 $dispatcher = VAPFactory::getEventDispatcher();
2631 $dispatcher->import('content');
2632
2633 $content = JTable::getInstance('content');
2634 $content->text = $description;
2635
2636 $lookup = array(
2637 'employeeslist' => 0, // short
2638 'employeesearch' => 1, // full
2639 'serviceslist' => 0, // short
2640 'servicesearch' => 1, // full
2641 'microdata' => 0, // short
2642 'paymentconfirm' => 0, // short
2643 'paymentorder' => 1, // full
2644 );
2645
2646 // checks if the task should use the short or full description
2647 $full = !empty($lookup[$task]);
2648
2649 /**
2650 * In case of e-mail custom text we should route
2651 * any URLs for being used externally by prepending
2652 * the base domain.
2653 *
2654 * @since 1.6.5
2655 */
2656 if ($task == 'custmail')
2657 {
2658 // look for any src/href attributes
2659 $content->text = preg_replace_callback("/\s*(src|href)=([\"'])(.*?)[\"']/i", function($match)
2660 {
2661 // check if the URL starts with the base domain
2662 if (stripos($match[3], JUri::root()) !== 0 && !preg_match("/^(https?:\/\/|www\.)/i", $match[3]))
2663 {
2664 // prepend base domain to URL
2665 $match[0] = ' ' . $match[1] . '=' . $match[2] . JUri::root() . $match[3] . $match[2];
2666 }
2667
2668 return $match[0];
2669 }, $content->text);
2670 }
2671
2672 /**
2673 * Lets the platform handler prepares the content.
2674 *
2675 * @since 1.6.3
2676 */
2677 VAPApplication::getInstance()->onContentPrepare($content, $full);
2678
2679 return $content->text;
2680 }
2681
2682 /**
2683 * Extracts and renders the short description from the specified argument.
2684 * In case the HTML do not use a READ MORE separator, the short description
2685 * will be created at runtime by taking a substring of the whole content.
2686 *
2687 * @param string $description The description to render.
2688 * @param integer $maxlen The maximum length of characters. If not
2689 * specified, up to 256 chars will be taken.
2690 *
2691 * @return string The resulting description.
2692 *
2693 * @since 1.7
2694 */
2695 public static function renderShortHtmlDescription($description, $maxlen = null)
2696 {
2697 // render HTML description
2698 VAPApplication::getInstance()->onContentPrepare($description);
2699
2700 // in case we have a short description, use it without taking a substrinh
2701 if (!$description->introtext)
2702 {
2703 // check whether the plain text exceeds the maximum number of characters
2704 $plain = strip_tags($description->text);
2705
2706 if (mb_strlen($plain, 'UTF-8') > $maxlen)
2707 {
2708 // The length of the description exceeded the maximum amount.
2709 // We need to display a substring of the description by stripping
2710 // all the HTML tags to avoid breaking the whole code.
2711 $description->introtext = mb_substr($plain, 0, $maxlen, 'UTF-8');
2712 // trim any ending space and dots to properly concat the ellipsis
2713 $description->introtext = rtrim($description->introtext, '. ') . '...';
2714 }
2715 else
2716 {
2717 // not exceeding length, use it in full
2718 $description->introtext = $description->text;
2719 }
2720 }
2721
2722 return $description->introtext;
2723 }
2724
2725 /**
2726 * Loads the main assets (CSS and JS) of the component.
2727 *
2728 * @return void
2729 */
2730 public static function load_css_js()
2731 {
2732 $vik = VAPApplication::getInstance();
2733
2734 $options = array(
2735 'version' => VIKAPPOINTMENTS_SOFTWARE_VERSION,
2736 );
2737
2738 // since jQuery is a required dependency, the framework should be
2739 // invoked even if jQuery is disabled
2740 $vik->loadFramework('jquery.framework');
2741
2742 $vik->addScript(VAPASSETS_URI . 'js/jquery-ui.min.js');
2743 $vik->addScript(VAPASSETS_URI . 'js/vikappointments.js', $options);
2744
2745 /**
2746 * Load the CSS file containing the environment variables.
2747 *
2748 * @since 1.7.2
2749 */
2750 JHtml::fetch('vaphtml.assets.environment');
2751
2752 $vik->addStyleSheet(VAPASSETS_URI . 'css/jquery-ui.min.css');
2753 $vik->addStyleSheet(VAPASSETS_URI . 'css/vikappointments.css', $options);
2754 $vik->addStyleSheet(VAPASSETS_URI . 'css/vikappointments-mobile.css', $options);
2755 $vik->addStyleSheet(VAPASSETS_URI . 'css/input-select.css', $options);
2756
2757 /**
2758 * Include adapter to adjust some layouts according to the current platform version.
2759 *
2760 * @since 1.7
2761 */
2762 if (VersionListener::isJoomla3x())
2763 {
2764 $vik->addStyleSheet(VAPASSETS_URI . 'css/adapter/J30.css');
2765 }
2766 else if (VersionListener::isJoomla4x())
2767 {
2768 $vik->addStyleSheet(VAPASSETS_URI . 'css/adapter/J40.css');
2769 }
2770
2771 /**
2772 * Adjust component layout to fit the specified theme.
2773 *
2774 * @since 1.6
2775 */
2776 $theme = VAPFactory::getConfig()->get('sitetheme');
2777
2778 if ($theme)
2779 {
2780 $vik->addStyleSheet(VAPASSETS_URI . 'css/themes/' . $theme . '.css', $options);
2781 }
2782
2783 /**
2784 * Loads the custom CSS file.
2785 *
2786 * @since 1.7.2 Moved in a specified helper function.
2787 */
2788 JHtml::fetch('vaphtml.assets.customcss');
2789
2790 /**
2791 * Loads utils.
2792 *
2793 * @since 1.7
2794 */
2795 JHtml::fetch('vaphtml.assets.utils');
2796
2797 /**
2798 * Always instantiate the currency object.
2799 *
2800 * @since 1.7
2801 */
2802 JHtml::fetch('vaphtml.assets.currency');
2803
2804 /**
2805 * Auto set CSRF token to ajaxSetup so all jQuery ajax call will contain CSRF token.
2806 *
2807 * @since 1.7
2808 */
2809 JHtml::fetch('vaphtml.sitescripts.ajaxcsrf');
2810 }
2811
2812 /**
2813 * Loads the scripts needed to use Select2 jQuery plugin.
2814 *
2815 * @return void
2816 *
2817 * @deprecated 1.8 Use VAPHtmlAssets::select2() instead.
2818 */
2819 public static function load_complex_select()
2820 {
2821 JHtml::fetch('vaphtml.assets.select2');
2822 }
2823
2824 /**
2825 * Loads the stylesheets needed to use Font Awesome.
2826 *
2827 * @return void
2828 *
2829 * @deprecated 1.8 Use VAPHtmlAssets::fontawesome() instead.
2830 */
2831 public static function load_font_awesome()
2832 {
2833 JHtml::fetch('vaphtml.assets.fontawesome');
2834 }
2835
2836 /**
2837 * Loads the scripts needed to use Chart JS jQuery plugin.
2838 *
2839 * @return void
2840 *
2841 * @deprecated 1.8 Use VAPHtmlAssets::chartjs() instead.
2842 */
2843 public static function load_charts()
2844 {
2845 JHtml::fetch('vaphtml.assets.chartjs');
2846 }
2847
2848 /**
2849 * Loads the scripts needed to use Fancybox jQuery plugin.
2850 *
2851 * @return void
2852 *
2853 * @deprecated 1.8 Use VAPHtmlAssets::fancybox() instead.
2854 */
2855 public static function load_fancybox()
2856 {
2857 JHtml::fetch('vaphtml.assets.fancybox');
2858 }
2859
2860 /**
2861 * Loads the scripts needed to use Google Maps javascript framework.
2862 * Requires a valid Google API Key.
2863 *
2864 * @return void
2865 *
2866 * @deprecated 1.8 Use VAPHtmlAssets::googlemaps() instead.
2867 */
2868 public static function load_googlemaps()
2869 {
2870 JHtml::fetch('vaphtml.assets.googlemaps');
2871 }
2872
2873 /**
2874 * Loads the scripts needed to use Colorpicker jQuery plugin.
2875 *
2876 * @return void
2877 *
2878 * @since 1.6
2879 * @deprecated 1.8 Use VAPHtmlAssets::colorpicker() instead.
2880 */
2881 public static function load_colorpicker()
2882 {
2883 JHtml::fetch('vaphtml.assets.colorpicker');
2884 }
2885
2886 /**
2887 * Loads the javascript utils.
2888 *
2889 * @param array $options A list of options for the scripts to load.
2890 *
2891 * @return void
2892 *
2893 * @since 1.6
2894 * @deprecated 1.8 Use VAPHtmlAssets::utils() instead.
2895 */
2896 public static function load_utils(array $options = array())
2897 {
2898 JHtml::fetch('vaphtml.assets.utils');
2899 }
2900
2901 /**
2902 * Loads the javascript utils and configure the Currency JS object.
2903 *
2904 * @return void
2905 *
2906 * @uses load_utils()
2907 *
2908 * @since 1.6
2909 * @deprecated 1.8 Use VAPHtmlAssets::currency() instead.
2910 */
2911 public static function load_currency_js()
2912 {
2913 JHtml::fetch('vaphtml.assets.currency');
2914 }
2915
2916 /**
2917 * Prepares the datepicker regional object.
2918 *
2919 * @return void
2920 *
2921 * @since 1.6
2922 */
2923 public static function load_datepicker_regional()
2924 {
2925 // Labels
2926 $done = JText::translate('VAPJQCALDONE');
2927 $prev = JText::translate('VAPJQCALPREV');
2928 $next = JText::translate('VAPJQCALNEXT');
2929 $today = JText::translate('VAPJQCALTODAY');
2930 $wk = JText::translate('VAPJQCALWKHEADER');
2931
2932 // Months
2933 $months = array(
2934 JText::translate('JANUARY'),
2935 JText::translate('FEBRUARY'),
2936 JText::translate('MARCH'),
2937 JText::translate('APRIL'),
2938 JText::translate('MAY'),
2939 JText::translate('JUNE'),
2940 JText::translate('JULY'),
2941 JText::translate('AUGUST'),
2942 JText::translate('SEPTEMBER'),
2943 JText::translate('OCTOBER'),
2944 JText::translate('NOVEMBER'),
2945 JText::translate('DECEMBER'),
2946 );
2947
2948 $months_short = array(
2949 JText::translate('JANUARY_SHORT'),
2950 JText::translate('FEBRUARY_SHORT'),
2951 JText::translate('MARCH_SHORT'),
2952 JText::translate('APRIL_SHORT'),
2953 JText::translate('MAY_SHORT'),
2954 JText::translate('JUNE_SHORT'),
2955 JText::translate('JULY_SHORT'),
2956 JText::translate('AUGUST_SHORT'),
2957 JText::translate('SEPTEMBER_SHORT'),
2958 JText::translate('OCTOBER_SHORT'),
2959 JText::translate('NOVEMBER_SHORT'),
2960 JText::translate('DECEMBER_SHORT'),
2961 );
2962
2963 $months = json_encode($months);
2964 $months_short = json_encode($months_short);
2965
2966 // Days
2967 $days = array(
2968 JText::translate('SUNDAY'),
2969 JText::translate('MONDAY'),
2970 JText::translate('TUESDAY'),
2971 JText::translate('WEDNESDAY'),
2972 JText::translate('THURSDAY'),
2973 JText::translate('FRIDAY'),
2974 JText::translate('SATURDAY'),
2975 );
2976
2977 $days_short_3 = array(
2978 JText::translate('SUN'),
2979 JText::translate('MON'),
2980 JText::translate('TUE'),
2981 JText::translate('WED'),
2982 JText::translate('THU'),
2983 JText::translate('FRI'),
2984 JText::translate('SAT'),
2985 );
2986
2987 $days_short_2 = array();
2988 foreach ($days_short_3 as $d)
2989 {
2990 $days_short_2[] = mb_substr($d, 0, 2, 'UTF-8');
2991 }
2992
2993 // snippet used to make sure the substring of
2994 // the week days doesn't return the same value (see Hebrew)
2995 // for all the elements
2996 $days_short_2 = array_unique($days_short_2);
2997
2998 if (count($days_short_2) != count($days_short_3))
2999 {
3000 // the count doesn't match, use the 3 chars days
3001 $days_short_2 = $days_short_3;
3002 }
3003
3004 $days = json_encode($days);
3005 $days_short_3 = json_encode($days_short_3);
3006 $days_short_2 = json_encode($days_short_2);
3007
3008 $lang = JFactory::getLanguage();
3009
3010 // should return a value between 0-6 (1: Monday, 0: Sunday)
3011 $start_of_week = $lang->getFirstDay();
3012 $is_rtl = $lang->isRtl() ? 'true' : 'false';
3013
3014 JFactory::getDocument()->addScriptDeclaration(
3015 <<<JS
3016 jQuery(function($){
3017 $.datepicker.regional["vikappointments"] = {
3018 closeText: "$done",
3019 prevText: "$prev",
3020 nextText: "$next",
3021 currentText: "$today",
3022 monthNames: $months,
3023 monthNamesShort: $months_short,
3024 dayNames: $days,
3025 dayNamesShort: $days_short_3,
3026 dayNamesMin: $days_short_2,
3027 weekHeader: "$wk",
3028 firstDay: $start_of_week,
3029 isRTL: $is_rtl,
3030 showMonthAfterYear: false,
3031 yearSuffix: ""
3032 };
3033
3034 $.datepicker.setDefaults($.datepicker.regional["vikappointments"]);
3035 });
3036 JS
3037 );
3038 }
3039
3040 /**
3041 * Creates a UNIX timestamp starting from a string date.
3042 *
3043 * @param string $date The date to parse.
3044 * @param integer $hour The hours to use.
3045 * @param integer $min The minutes to use.
3046 *
3047 * @return integer The resulting UNIX timestamp.
3048 */
3049 public static function createTimestamp($date, $hour = 0, $min = 0)
3050 {
3051 if ($hour == 23 && $min == 59)
3052 {
3053 $sec = 59;
3054 }
3055 else
3056 {
3057 $sec = 0;
3058 }
3059
3060 return VAPDateHelper::getTimestamp($date, $hour, $min, $sec);
3061 }
3062
3063 /**
3064 * Returns the current time adjusted to the global timezone.
3065 * Proxy for timestamp() method without passing any arguments.
3066 *
3067 * @param mixed $offset The timezone to use.
3068 *
3069 * @return integer The current time.
3070 *
3071 * @since 1.7
3072 */
3073 public static function now($offset = null)
3074 {
3075 return self::timestamp(null, $offset);
3076 }
3077
3078 /**
3079 * Adjusts the given timestamp to the global timezone.
3080 *
3081 * @param integer $ts The timestamp to adjust.
3082 * @param mixed $offset The timezone to use.
3083 *
3084 * @return integer A timestamp adjusted to the given timezone.
3085 *
3086 * @since 1.7
3087 */
3088 public static function timestamp($ts = null, $offset = null)
3089 {
3090 if (!$offset)
3091 {
3092 // use global timezone
3093 $offset = JFactory::getConfig()->get('offset', 'UTC');
3094 }
3095
3096 // create timezone instance
3097 $timezone = new DateTimeZone($offset);
3098
3099 if (is_null($ts))
3100 {
3101 // get current time based on server configuration
3102 $date = JFactory::getDate();
3103 }
3104 else
3105 {
3106 // instantiate date object using the given timestamp
3107 $date = JFactory::getDate(date('Y-m-d H:i:s', $ts));
3108 }
3109
3110 // adjust to global timezone
3111 $date->setTimezone($timezone);
3112
3113 // convert adjusted datetime to timestamp (based on server timezone)
3114 return strtotime($date->format('Y-m-d H:i:s', true));
3115 }
3116
3117 /**
3118 * Returns the check-out date time.
3119 *
3120 * @param mixed $checkin Either a date string to a timestamp.
3121 * @param integer $duration The duration of the appointment (in minutes).
3122 *
3123 * @return string The resulting date string.
3124 *
3125 * @since 1.6
3126 */
3127 public static function getCheckout($checkin, $duration)
3128 {
3129 if (is_numeric($checkin))
3130 {
3131 $checkin = date('Y-m-d H:i:s', $checkin);
3132 }
3133
3134 $date = JFactory::getDate($checkin);
3135 $date->modify('+' . (int) $duration . ' minutes');
3136
3137 return $date->format('Y-m-d H:i:s');
3138 }
3139
3140 /**
3141 * Checks if the given minute is a correct/supported interval.
3142 *
3143 * @param integer $minute The minute value to check.
3144 *
3145 * @return boolean True if correct, false otherwise.
3146 */
3147 public static function isMinuteAnInterval($minute)
3148 {
3149 $min = VAPFactory::getConfig()->getUint('minuteintervals');
3150
3151 for ($i = 0; $i < 60; $i += $min)
3152 {
3153 if ($i == $minute)
3154 {
3155 return true;
3156 }
3157 }
3158
3159 return false;
3160 }
3161
3162 /**
3163 * Helper method used to calculate the right day with the given shift.
3164 * It is used to display the correct position of the days depending on the
3165 * first day of the week.
3166 *
3167 * @param integer $day_index The index of the day.
3168 * @param integer $shift The index of the first day.
3169 *
3170 * @return integer The resulting position
3171 */
3172 public static function getShiftedDay($day_index, $shift)
3173 {
3174 if ($day_index + $shift < 7)
3175 {
3176 return $day_index + $shift;
3177 }
3178
3179 return $day_index + $shift - 7;
3180 }
3181
3182 /**
3183 * Helper method used to generate a serial code.
3184 * In a remote case, this method may generate 2 identical codes.
3185 * The probability to have 2 identical strings is:
3186 * 1 / count($map)^$len
3187 *
3188 * @param integer $length The length of the serial code.
3189 * @param string $scope The purpose of the serial code.
3190 * @param array $map A map containing all the allowed tokens.
3191 *
3192 * @return string The resulting serial code.
3193 */
3194 public static function generateSerialCode($length = 12, $scope = null, $map = null)
3195 {
3196 $code = '';
3197
3198 /**
3199 * This event can be used to change the way the system generates
3200 * a serial code. It is possible to edit the code or simply to
3201 * alter the map of allowed tokens. In case the serial code
3202 * didn't reach the specified length, the remaining characters
3203 * will be generated according to the default algorithm.
3204 *
3205 * @param string $code The serial code.
3206 * @param array|null &$map A map of allowed tokens.
3207 * @param integer $length The length of the serial code.
3208 * @param string|null $scope The purpose of the code.
3209 *
3210 * @return void
3211 *
3212 * @since 1.7
3213 */
3214 VAPFactory::getEventDispatcher()->trigger('onGenerateSerialCode', array(&$code, &$map, $length, $scope));
3215
3216 if (!is_scalar($code))
3217 {
3218 // reset code in case of invalid string
3219 $code = '';
3220 }
3221
3222 // check if we already have a complete serial code
3223 if (strlen($code) >= $length)
3224 {
3225 // just return the specified number of characters
3226 return substr($code, 0, $length);
3227 }
3228
3229 if (!$map)
3230 {
3231 // use default tokens if not specified/modified
3232 $map = array(
3233 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
3234 '0123456789'
3235 );
3236 }
3237 else
3238 {
3239 // always treat as array
3240 $map = (array) $map;
3241 }
3242
3243 // iterate until the specified length is reached
3244 for ($i = strlen($code); $i < $length; $i++)
3245 {
3246 // toss tokens block
3247 $_row = rand(0, count($map) - 1);
3248 // toss block character
3249 $_col = random_int(0, strlen($map[$_row]) - 1);
3250
3251 // append character to serial code
3252 $code .= (string) $map[$_row][$_col];
3253 }
3254
3255 return $code;
3256 }
3257
3258 /**
3259 * Checks if the given service owns a private calendar
3260 * that cannot be shared with other services.
3261 *
3262 * @param integer $id_ser The service ID.
3263 *
3264 * @return boolean True if own calendar, false otherwise.
3265 *
3266 * @deprecated 1.8 Use VikAppointmentsModelService::hasOwnCalendar() instead.
3267 */
3268 public static function hasServiceOwnCalendar($id_ser)
3269 {
3270 return JModelVAP::getInstance('service')->hasOwnCalendar($id_ser);
3271 }
3272
3273 /**
3274 * Helper method used to get all the reservations (with extended details) that belong
3275 * to the specified employee and service.
3276 *
3277 * @param integer $id_emp The employee ID.
3278 * @param integer $id_ser The service ID.
3279 * @param integer $start_ts The start of the time range.
3280 * @param integer $end_ts The end of the time range.
3281 * @param mixed $dbo The database object.
3282 *
3283 * @return array The list containing all the reservations found.
3284 *
3285 * @deprecated 1.8 Without replacement.
3286 */
3287 public static function getAllEmployeeExtendedReservations($id_emp, $id_ser, $start_ts, $end_ts, $dbo = null)
3288 {
3289 if (!$dbo)
3290 {
3291 $dbo = JFactory::getDbo();
3292 }
3293
3294 // if id_ser NOT -1 and service has own calendar, don't consider the
3295 // reservations of the other services (of the same employee)
3296 if (!self::hasServiceOwnCalendar($id_ser))
3297 {
3298 // don't apply own search (unset service)
3299 $id_ser = -1;
3300 }
3301
3302 /*$q = "SELECT `r`.`id` AS `rid`, `r`.`checkin_ts` AS `checkin`, `r`.`people`, `r`.`duration` AS `rduration`, `r`.`total_cost` AS `total_cost`, `r`.`status` AS `status`, `r`.`sid` AS `rsid`, `r`.`purchaser_mail` AS `rmail`,
3303 `r`.`sleep` AS `rsleep`, `r`.`paid` AS `paid`, `r`.`tot_paid`, `r`.`id_payment`, `r`.`purchaser_nominative`, `e`.`id` AS `id_employee`, `e`.`nickname` AS `ename`, `s`.`name` AS `sname`
3304 FROM `#__vikappointments_reservation` AS `r`
3305 LEFT JOIN `#__vikappointments_employee` AS `e` ON `r`.`id_employee`=`e`.`id`
3306 LEFT JOIN `#__vikappointments_service` AS `s` ON `r`.`id_service`=`s`.`id`
3307 WHERE `r`.`status`<>'REMOVED' AND `r`.`status`<>'CANCELED' AND `e`.`id`=$id_emp AND
3308 ((`s`.`has_own_cal`=0 AND $id_ser=-1) OR (`s`.`has_own_cal`=1 AND `r`.`id_service`=$id_ser)) AND
3309 $start_ts <= `r`.`checkin_ts` AND `r`.`checkin_ts` <= $end_ts
3310 ORDER BY `r`.`checkin_ts`;";*/
3311
3312 $excluded_status = array('REMOVED', 'CANCELED');
3313
3314 $q = $dbo->getQuery(true)
3315 ->select(array(
3316 $dbo->qn('r.id', 'rid'),
3317 $dbo->qn('r.sid', 'rsid'),
3318 $dbo->qn('r.id_employee'),
3319 $dbo->qn('r.id_service'),
3320 $dbo->qn('r.checkin_ts', 'checkin'),
3321 $dbo->qn('r.people'),
3322 $dbo->qn('r.duration', 'rduration'),
3323 $dbo->qn('r.sleep', 'rsleep'),
3324 $dbo->qn('r.total_cost'),
3325 $dbo->qn('r.tot_paid'),
3326 $dbo->qn('r.paid'),
3327 $dbo->qn('r.id_payment'),
3328 $dbo->qn('r.status'),
3329 $dbo->qn('r.purchaser_nominative'),
3330 $dbo->qn('r.purchaser_mail', 'rmail'),
3331 $dbo->qn('r.closure'),
3332 ))
3333 ->select($dbo->qn('e.nickname', 'ename'))
3334 ->select($dbo->qn('s.name', 'sname'));
3335
3336 $q->from($dbo->qn('#__vikappointments_reservation', 'r'));
3337 $q->leftjoin($dbo->qn('#__vikappointments_employee', 'e') . ' ON ' . $dbo->qn('r.id_employee') . ' = ' . $dbo->qn('e.id'));
3338 $q->leftjoin($dbo->qn('#__vikappointments_service', 's') . ' ON ' . $dbo->qn('r.id_service') . ' = ' . $dbo->qn('s.id'));
3339
3340 $q->where(array(
3341 $dbo->qn('r.status') . ' NOT IN (' . implode(', ', array_map(array($dbo, 'q'), $excluded_status)) . ')',
3342 $dbo->qn('e.id') . ' = ' . (int) $id_emp,
3343 $dbo->qn('r.checkin_ts') . ' BETWEEN ' . (int) $start_ts . ' AND ' . (int) $end_ts,
3344 ));
3345
3346 /**
3347 * Do not display closure records within the front-end.
3348 *
3349 * @since 1.6
3350 */
3351 if (JFactory::getApplication()->isClient('site'))
3352 {
3353 $q->where($dbo->qn('r.closure') . ' = 0');
3354 }
3355
3356 /**
3357 * (
3358 * (`s`.`has_own_cal` = 0 AND $id_ser = -1 ) OR
3359 * (`s`.`has_own_cal` = 1 AND `r`.`id_service` = $id_ser)
3360 * )";
3361 */
3362 $q->andWhere(array(
3363 '(' . $dbo->qn('s.has_own_cal') . ' = 0 AND ' . (int) $id_ser . ' = -1)',
3364 '(' . $dbo->qn('s.has_own_cal') . ' = 1 AND ' . (int) $id_ser . ' = ' . $dbo->qn('r.id_service') . ')',
3365 ), 'OR');
3366
3367 $q->order($dbo->qn('r.checkin_ts') . ' ASC');
3368
3369 $dbo->setQuery($q);
3370 $rows = $dbo->loadAssocList();
3371
3372 if (!$rows)
3373 {
3374 return array();
3375 }
3376
3377 for ($i = 0; $i < count($rows); $i++)
3378 {
3379 $rows[$i]['pname'] = '';
3380 if ($rows[$i]['id_payment'] != -1)
3381 {
3382 $p = self::getPayment($rows[$i]['id_payment'], false);
3383 if (count($p) > 0)
3384 {
3385 $rows[$i]['pname'] = $p['name'];
3386 }
3387 }
3388 }
3389
3390 return $rows;
3391 }
3392
3393 /**
3394 * Returns the list of the employee reservations for the given day.
3395 * In order to support midnight reservations (that starts on a day and ends on the next one),
3396 * it is needed to return also the reservations for the previous day and for the next day.
3397 *
3398 * @param integer $id_emp The employee ID.
3399 * @param integer $id_ser The service ID.
3400 * @param integer $start_ts The starting delimiter (UNIX timestamp).
3401 * @param integer $end_ts The ending delimiter (UNIX timestamp)
3402 * @param mixed $dbo The database object.
3403 *
3404 * @return array A list of matching reservations.
3405 *
3406 * @deprecated 1.8 Without Replacement.
3407 */
3408 public static function getAllEmployeeReservations($id_emp, $id_ser, $start_ts, $end_ts, $dbo = null)
3409 {
3410 if (!$dbo)
3411 {
3412 $dbo = JFactory::getDbo();
3413 }
3414
3415 // if id_ser NOT -1 and service has own calendar -> don't consider the reservations of the other services (of the same employee)
3416 if (!self::hasServiceOwnCalendar($id_ser))
3417 {
3418 // don't apply own search
3419 $id_ser = -1; // unset service
3420 }
3421
3422 $bounds = array($start_ts, $end_ts);
3423
3424 if ($start_ts + 86399 == $end_ts)
3425 {
3426 /**
3427 * We are looking for the reservations for the current day.
3428 * Extend this bounds in order to support midnight reservations.
3429 *
3430 * Instead having:
3431 * 2018-07-09 @ 00:00:00 - 2018-07-09 23:59:59,
3432 * we need to have :
3433 * 2018-07-08 @ 00:00:00 - 2018-07-10 23:59:59
3434 *
3435 * @since 1.6
3436 */
3437 $start_ts = strtotime('-1 day 00:00:00', $bounds[0]);
3438 $end_ts = strtotime('+1 day 23:59:59', $bounds[0]);
3439 }
3440
3441 $q = "SELECT `r`.`checkin_ts`, `r`.`duration`, `r`.`sleep`, `r`.`people`, SUM(`r`.`people`) AS `people_count`, `r`.`id`, `r`.`id_service`, `r`.`closure`, `r`.`id_employee`
3442 FROM `#__vikappointments_reservation` AS `r`
3443 LEFT JOIN `#__vikappointments_service` AS `s` ON `r`.`id_service`=`s`.`id`
3444 WHERE `r`.`status`<>'REMOVED' AND `r`.`status`<>'CANCELED' AND `r`.`id_employee`=$id_emp AND
3445 ((`s`.`has_own_cal`=0 AND $id_ser=-1) OR (`s`.`has_own_cal`=1 AND `r`.`id_service`=$id_ser)) AND
3446 $start_ts <= `r`.`checkin_ts` AND `r`.`checkin_ts` <= $end_ts GROUP BY `r`.`checkin_ts` ORDER BY `r`.`checkin_ts`;";
3447
3448 $dbo->setQuery($q);
3449 $list = $dbo->loadAssocList();
3450
3451 foreach ($list as $i => $b)
3452 {
3453 if ($list[$i]['checkin_ts'] < $bounds[0])
3454 {
3455 $day = -1;
3456 }
3457 else if ($list[$i]['checkin_ts'] > $bounds[1])
3458 {
3459 $day = 1;
3460 }
3461 else
3462 {
3463 $day = 0;
3464 }
3465
3466 $list[$i]['@day'] = $day;
3467 }
3468
3469 return $list;
3470 }
3471
3472 /**
3473 * Returns the list of the employee reservations for the given day excluding the specified ID.
3474 * In order to support midnight reservations (that starts on a day and ends on the next one),
3475 * it is needed to return also the reservations for the previous day and for the next day.
3476 *
3477 * @param integer $id_emp The employee ID.
3478 * @param integer $id_ser The service ID.
3479 * @param integer $no_id The reservation ID to exclude.
3480 * @param integer $start_ts The starting delimiter (UNIX timestamp).
3481 * @param integer $end_ts The ending delimiter (UNIX timestamp)
3482 * @param mixed $dbo The database object.
3483 *
3484 * @return array A list of matching reservations.
3485 *
3486 * @uses getAllEmployeeReservations()
3487 *
3488 * @deprecated 1.8 Without Replacement.
3489 */
3490 public static function getAllEmployeeReservationsExcludingResId($id_emp, $id_ser, $no_id, $start_ts, $end_ts, $dbo = null)
3491 {
3492 $bookings = self::getAllEmployeeReservations($id_emp, $id_ser, $start_ts, $end_ts, $dbo);
3493
3494 if ($no_id > 0 && $bookings)
3495 {
3496 $i = 0;
3497 while ($i < count($bookings) && $bookings[$i]['id'] != $no_id)
3498 {
3499 // iterate while the current booking is not the one to exclude
3500 $i++;
3501 }
3502
3503 if ($i < count($bookings))
3504 {
3505 // booking found, splice the array
3506 array_splice($bookings, $i, 1);
3507 }
3508 }
3509
3510 return $bookings;
3511 }
3512
3513 /**
3514 * Returns the list of the service reservations for the given day.
3515 *
3516 * @param integer $id_ser The service ID.
3517 * @param integer $start_ts The starting delimiter (UNIX timestamp).
3518 * @param integer $end_ts The ending delimiter (UNIX timestamp)
3519 * @param mixed $dbo The database object.
3520 *
3521 * @return array A list of matching reservations.
3522 *
3523 * @deprecated 1.8 Use VAPAvailabilitySearch::getReservations() instead.
3524 */
3525 public static function getAllServiceReservations($id_ser, $start_ts, $end_ts, $dbo = null)
3526 {
3527 VAPLoader::import('libraries.availability.manager');
3528 $search = VAPAvailabilityManager::getInstance($id_ser);
3529
3530 $start = date('Y-m-d', $start_ts);
3531 $end = date('Y-m-d', $end_ts);
3532
3533 return $search->getReservations($start, $end);
3534 }
3535
3536 /**
3537 * Evaluates all the bookings that intersect the specified day.
3538 *
3539 * @param array $bookings The bookings list.
3540 * @param integer $curr_index The current index of the list, in order to ignore
3541 * all the records lower than this value.
3542 * @param integer $start The UNIX timestamp of the day.
3543 *
3544 * @return array An array containing the following properties:
3545 * 0: integer num of bookings evaluated;
3546 * 1: array daily hour reservations found.
3547 *
3548 * @deprecated 1.8 Without replacement.
3549 */
3550 public static function evaluateBookingArray($bookings, $curr_index, $start)
3551 {
3552 if (!count($bookings))
3553 {
3554 // no bookings
3555 return array(0, array());
3556 }
3557
3558 $end = strtotime('23:59:59', $start);
3559
3560 $skip = $curr_index;
3561 while ($skip < count($bookings) && $bookings[$skip]['checkin_ts'] < $start)
3562 {
3563 $skip++;
3564 }
3565
3566 if ($skip)
3567 {
3568 /**
3569 * Consider also the previous booking as it may be straddling 2 different days (midnight appointments).
3570 *
3571 * @since 1.6
3572 */
3573 $skip--;
3574 }
3575
3576 $same_day = true;
3577 $rows = array();
3578
3579 for ($i = $skip, $n = count($bookings); $i < $n && $bookings[$i]['checkin_ts'] <= $end; $i++)
3580 {
3581 $same_day = self::isBetween($bookings[$i]['checkin_ts'], $start, $end);
3582
3583 if (!$same_day)
3584 {
3585 /**
3586 * Fallback to check if the checkout intersects the delimiters.
3587 *
3588 * @since 1.6
3589 */
3590 $checkout = self::getCheckout($bookings[$i]['checkin_ts'], $bookings[$i]['duration']);
3591 $same_day = self::isBetween($checkout, $start + 1, $end); // +1 is used to make sure the checkout is not equals to the start delimiter
3592 }
3593
3594 if ($same_day)
3595 {
3596 // $rows[$i - $skip] = $bookings[$i];
3597 $rows[] = $bookings[$i];
3598 }
3599 }
3600
3601 return array($skip + count($rows), $rows);
3602 }
3603
3604 /**
3605 * Checks if the given value is between the 2 delimiters.
3606 *
3607 * @param integer $val The value to check.
3608 * @param integer $start The starting delimiter.
3609 * @param integer $end The ending delimiter.
3610 *
3611 * @return boolean True if the value is between the delimiters, false otherwise.
3612 */
3613 public static function isBetween($val, $start, $end)
3614 {
3615 return $start <= $val && $val <= $end;
3616 }
3617
3618 /**
3619 * Checks if the specified employee is not fully occupied for the given day.
3620 *
3621 * @param integer $id_emp The employee ID.
3622 * @param integer $id_ser The service ID.
3623 * @param array $arr_res An array containing all the daily reservations.
3624 * @param integer $day_ts The day UNIX timestamp.
3625 * @param integer $max_people The maximum number of people.
3626 * @param mixed $dbo The database object.
3627 * @param array $locations The locations array.
3628 *
3629 * @return boolean False if fully occupied, otherwise true.
3630 *
3631 * @deprecated 1.8 Use VAPAvailabilitySearch::isDayAvailable() instead.
3632 */
3633 public static function isFreeIntervalOnDay($id_emp, $id_ser, $arr_res, $day_ts, $max_people, $dbo = null, $locations = array())
3634 {
3635 if (is_numeric($day_ts))
3636 {
3637 // convert to date string
3638 $day_ts = date('Y-m-d', $day_ts);
3639 }
3640
3641 VAPLoader::import('libraries.availability.manager');
3642 $search = VAPAvailabilityManager::getInstance($id_ser, $id_emp, array('locations' => $locations));
3643
3644 return $search->isDayAvailable($day_ts);
3645 }
3646
3647 /**
3648 * Checks if the specified service owns at least an employee that
3649 * is not fully occupied for the given day.
3650 *
3651 * @param array $employees The employees IDs.
3652 * @param integer $id_ser The service ID.
3653 * @param array $arr_res An array containing all the daily reservations.
3654 * @param integer $day_ts The day UNIX timestamp.
3655 * @param mixed $dbo The database object.
3656 * @param array $locations The locations array.
3657 *
3658 * @return boolean False if fully occupied, otherwise true.
3659 *
3660 * @deprecated 1.8 Use VAPAvailabilitySearch::isDayAvailable() instead.
3661 */
3662 public static function isFreeIntervalOnDayService($employees, $id_ser, $arr_res, $day_ts, $dbo = null, $locations = array())
3663 {
3664 if (is_numeric($day_ts))
3665 {
3666 // convert to date string
3667 $day_ts = date('Y-m-d', $day_ts);
3668 }
3669
3670 VAPLoader::import('libraries.availability.manager');
3671 $search = VAPAvailabilityManager::getInstance($id_ser, null, array('locations' => $locations));
3672
3673 return $search->isDayAvailable($day_ts);
3674 }
3675
3676 /**
3677 * Checks if the specified service owns at least an employee that
3678 * is not fully occupied for the given day. This method supports
3679 * services with maximum capacity higher than 1.
3680 *
3681 * @param array $employees The employees IDs.
3682 * @param integer $id_ser The service ID.
3683 * @param array $arr_res An array containing all the daily reservations.
3684 * @param integer $day_ts The day UNIX timestamp.
3685 * @param integer $max_capacity The maximum number of people.
3686 * @param mixed $dbo The database object.
3687 * @param array $locations The locations array.
3688 *
3689 * @return boolean False if fully occupied, otherwise true.
3690 *
3691 * @since 1.2
3692 * @deprecated 1.8 Use VAPAvailabilitySearch::isDayAvailable() instead.
3693 */
3694 public static function isFreeIntervalOnDayGroupService($employees, $id_ser, $arr_res, $day_ts, $max_capacity, $dbo = null, $locations = array())
3695 {
3696 if (is_numeric($day_ts))
3697 {
3698 // convert to date string
3699 $day_ts = date('Y-m-d', $day_ts);
3700 }
3701
3702 VAPLoader::import('libraries.availability.manager');
3703 $search = VAPAvailabilityManager::getInstance($id_ser, null, array('locations' => $locations));
3704
3705 return $search->isDayAvailable($day_ts);
3706 }
3707
3708 /**
3709 * Method used to obtain a list of reservations at the given date and time.
3710 *
3711 * @param integer $id_emp The employee ID.
3712 * @param integer $checkin The reservations checkin.
3713 * @param mixed $dbo The database object.
3714 * @param mixed $q A query builder used to overwrite SELECT and FROM statements.
3715 *
3716 * @return array The list of matching reservations.
3717 *
3718 * @deprecated 1.8 Without replacement.
3719 */
3720 public static function getEmployeeAppointmentAt($id_emp, $checkin, $dbo = null, $q = null)
3721 {
3722 if (!$dbo)
3723 {
3724 $dbo = JFactory::getDbo();
3725 }
3726
3727 if (!$q)
3728 {
3729 $q = $dbo->getQuery(true);
3730
3731 $q->select($dbo->qn('r.id', 'rid'))->from($dbo->qn('#__vikappointments_reservation', 'r'));
3732 }
3733
3734 if ($id_emp)
3735 {
3736 $q->where($dbo->qn('r.id_employee') . ' = ' . (int) $id_emp);
3737 }
3738
3739 $q->where(array(
3740 $dbo->qn('r.status') . ' IN (\'CONFIRMED\', \'PENDING\')',
3741 $dbo->qn('r.checkin_ts') . ' <= ' . (int) $checkin,
3742 (int) $checkin . ' < (' . $dbo->qn('r.checkin_ts') . ' + ' . $dbo->qn('r.duration') . ' * 60 + ' . $dbo->qn('r.sleep') . ')',
3743 ));
3744
3745 $dbo->setQuery($q);
3746 return $dbo->loadAssocList();
3747 }
3748
3749 /**
3750 * Elaborates the timeline by intersecting the worktimes and the bookings found.
3751 *
3752 * @param array $worktime The list of the available working times.
3753 * @param array $bookings The reservations to intersect.
3754 * @param mixed $service The service details.
3755 *
3756 * @return array A list of associative arrays containing the elaborated timeline.
3757 * The keys contain the time (hour * 60 + min) and the values
3758 * contain the status (0: blocked, 1: available, 2: not enough space).
3759 *
3760 * @deprecated 1.8 VAPAvailabilityTimelineEmployee::getTimeline() instead.
3761 */
3762 public static function elaborateTimeLine($worktime, $bookings, $service)
3763 {
3764 /**
3765 * Load service details in case the service ID was passed.
3766 *
3767 * @since 1.6.5
3768 */
3769 if (is_scalar($service))
3770 {
3771 $dbo = JFactory::getDbo();
3772
3773 $q = $dbo->getQuery(true)
3774 ->select($dbo->qn(array('id', 'interval', 'duration', 'sleep')))
3775 ->from($dbo->qn('#__vikappointments_service'))
3776 ->where($dbo->qn('id') . ' = ' . (int) $service);
3777
3778 $dbo->setQuery($q, 0, 1);
3779 $service = $dbo->loadAssoc();
3780
3781 if (!$service)
3782 {
3783 throw new Exception(sprintf('Service [%d] not found', $service), 404);
3784 }
3785 }
3786 else
3787 {
3788 $service = (array) $service;
3789 }
3790
3791 $min_int = VAPFactory::getConfig()->getUint('minuteintervals');
3792
3793 if ($service['interval'] == 1)
3794 {
3795 $min_int = 5;
3796 }
3797
3798 $arr = array();
3799
3800 for ($i = 0; $i < count($worktime); $i++)
3801 {
3802 //for( $j = $worktime[$i]['fromts'], $len = 0; $j < $worktime[$i]['endts']; $j+=$min_int) {
3803 for ($j = $worktime[$i]['fromts'], $len = 0; ($j + $min_int) <= $worktime[$i]['endts']; $j += $min_int)
3804 {
3805 $arr[$i][$j] = 1;
3806 }
3807 }
3808
3809 foreach ($bookings as $b)
3810 {
3811 $date = getdate($b['checkin_ts']);
3812 $start = ($date['hours'] * 60) + $date['minutes'];
3813
3814 if (isset($b['@day']))
3815 {
3816 /**
3817 * Check the day factor of a reservation to check if it is referring
3818 * to the current day or if it close to the bounds of this working time.
3819 * Used to support midnight reservations.
3820 *
3821 * @since 1.6
3822 */
3823
3824 if ($b['@day'] == 1)
3825 {
3826 // we are evaluating a reservation for the next day, so we need to increase the
3827 // initial time by 1440 minutes (24 hours * 60).
3828 $start += 1440;
3829 }
3830 else if ($b['@day'] == -1)
3831 {
3832 // we are evaluating a reservation for the previous day, so we need to decrease the
3833 // initial time by 1440 minutes (24 hours * 60).
3834 $start -= 1440;
3835 }
3836 }
3837
3838 for ($i = $start; $i < $start + $b['duration'] + $b['sleep']; $i += $min_int)
3839 {
3840 $found = false;
3841 for ($j = 0; $j < count($arr) && !$found; $j++)
3842 {
3843 if (!empty($arr[$j][$i]))
3844 {
3845 $found = true;
3846 $arr[$j][$i] = 0;
3847 }
3848 }
3849 }
3850 }
3851
3852 if ($service['interval'] != 1)
3853 {
3854 $n_step = $service['duration'] + $service['sleep'];
3855
3856 for ($i = 0; $i < count($arr); $i++)
3857 {
3858 $step = 0;
3859 //for( $j = $worktime[$i]['fromts'], $len = 0; $j < $worktime[$i]['endts']; $j+=$min_int) {
3860 for ($j = $worktime[$i]['fromts'], $len = 0; ($j + $min_int) <= $worktime[$i]['endts']; $j += $min_int)
3861 {
3862 if ($arr[$i][$j] == 1)
3863 {
3864 $step += $min_int;
3865 if ($step >= $n_step)
3866 {
3867 $step-=$min_int;
3868 }
3869 }
3870 else
3871 {
3872 if ($step != 0 && $step < $n_step)
3873 {
3874 for ($back = $j - $min_int; $back >= $j - $step; $back -= $min_int)
3875 {
3876 $arr[$i][$back] = 2;
3877 }
3878 }
3879
3880 $step = 0;
3881 }
3882 }
3883
3884 if ($step != 0 && $step < $n_step)
3885 {
3886 for ($back = $j - $min_int; $back >= $j - $step; $back -= $min_int)
3887 {
3888 $arr[$i][$back] = 2;
3889 }
3890 }
3891 }
3892 }
3893
3894 $mod = round(($service['duration'] + $service['sleep']) / $min_int);
3895
3896 if ($service['interval'] == 1 && $mod != 1)
3897 {
3898 $new_arr = array();
3899
3900 for ($i = 0; $i < count($arr); $i++)
3901 {
3902 $new_arr[$i] = array();
3903 $value = 1;
3904 $start = 0;
3905 $all_free = true;
3906
3907 $count = 0;
3908
3909 for ($j = $worktime[$i]['fromts']; $j < $worktime[$i]['endts']; $j += $min_int, $count++)
3910 {
3911 if ($count % $mod == 0)
3912 {
3913 $start = $j;
3914 $value = 1;
3915 $all_free = true;
3916 }
3917
3918 $hourmin = intval($j / 60) . ' : ' . ($j % 60);
3919 if ($arr[$i][$j] == 0)
3920 {
3921 $all_free = false;
3922 }
3923
3924 $value &= ($arr[$i][$j] == 2 ? 0 : $arr[$i][$j]);
3925
3926 if ((($count + 1) % $mod == 0 || $j + $min_int == $worktime[$i]['endts']))
3927 {
3928 // LAST TIME SLOTS is not enough length
3929 if (($count+1) % $mod != 0)
3930 {
3931 $value = 0;
3932 }
3933
3934 if ($value == 0 && $all_free)
3935 {
3936 $value = 2;
3937 }
3938
3939 $new_arr[$i][$start] = $value;
3940 }
3941 }
3942 }
3943
3944 $arr = $new_arr;
3945 }
3946
3947 return $arr;
3948 }
3949
3950 /**
3951 * Elaborates the timeline by intersecting the worktimes and the bookings found.
3952 * Filters the arrays of timelines to support a single associative array.
3953 *
3954 * @param array $worktime The list of the available working times.
3955 * @param array $bookings The reservations to intersect.
3956 * @param array $service The service details.
3957 *
3958 * @return array An associative array containing the elaborated timeline.
3959 * The keys contain the time (hour * 60 + min) and the values
3960 * contain the status (0: blocked, 1: available, 2: not enough space).
3961 *
3962 * @deprecated 1.8 VAPAvailabilityTimelineService::getTimeline() instead.
3963 */
3964 public static function elaborateTimeLineService($worktime, $bookings, $service)
3965 {
3966 $arr = self::elaborateTimeLine($worktime, $bookings, $service);
3967
3968 $timeline = array();
3969 foreach ($arr as $a)
3970 {
3971 foreach ($a as $hour => $val)
3972 {
3973 $timeline[$hour] = $val;
3974 }
3975 }
3976
3977 return $timeline;
3978 }
3979
3980 /**
3981 * Elaborates the timeline by intersecting the worktimes and the bookings found.
3982 * This method accepts multiple bookings at the same date and time depending on
3983 * the maximum capacity defined by the given service.
3984 *
3985 * @param array $worktime The list of the available working times.
3986 * @param array $bookings The reservations to intersect.
3987 * @param array $service The service details.
3988 * @param integer $people The number of specified people.
3989 * @param mixed &$seats An array containing the remaining seats for each time.
3990 *
3991 * @return array An associative array containing the elaborated timeline.
3992 * The keys contain the time (hour * 60 + min) and the values
3993 * contain the status (0: blocked, 1: available, 2: not enough space).
3994 *
3995 * @deprecated 1.8 VAPAvailabilityTimelineGroup::getTimeline() instead.
3996 */
3997 public static function elaborateTimeLineGroupService($worktime, $bookings, $service, $people = 1, &$seats = null)
3998 {
3999 $min_int = 0;
4000
4001 if ($service['interval'] == 1)
4002 {
4003 $min_int = $service['duration'] + $service['sleep'];
4004 }
4005 else
4006 {
4007 $min_int = VAPFactory::getConfig()->getUint('minuteintervals');
4008 }
4009
4010 $arr = array();
4011
4012 for ($i = 0; $i < count($worktime); $i++)
4013 {
4014 //for( $j = $worktime[$i]['fromts'], $len = 0; $j < $worktime[$i]['endts']; $j+=$min_int) {
4015 for ($j = $worktime[$i]['fromts'], $len = 0; ($j + $min_int) <= $worktime[$i]['endts']; $j += $min_int)
4016 {
4017 $arr[$i][$j] = 1;
4018 }
4019 }
4020
4021 $cont_people = 0;
4022 for ($k = 0; $k < count($bookings); $k++)
4023 {
4024 $b = $bookings[$k];
4025
4026 $cont_people += $b['people_count'];
4027 if ($k == count($bookings) - 1 || $bookings[$k + 1]['checkin_ts'] != $b['checkin_ts'])
4028 {
4029 $date = getdate($b['checkin_ts']);
4030 $start = ($date['hours'] * 60) + $date['minutes'];
4031
4032 if (isset($b['@day']))
4033 {
4034 /**
4035 * Check the day factor of a reservation to check if it is referring
4036 * to the current day or if it close to the bounds of this working time.
4037 * Used to support midnight reservations.
4038 *
4039 * @since 1.6
4040 */
4041
4042 if ($b['@day'] == 1)
4043 {
4044 // we are evaluating a reservation for the next day, so we need to increase the
4045 // initial time by 1440 minutes (24 hours * 60).
4046 $start += 1440;
4047 }
4048 else if ($b['@day'] == -1)
4049 {
4050 // we are evaluating a reservation for the previous day, so we need to decrease the
4051 // initial time by 1440 minutes (24 hours * 60).
4052 $start -= 1440;
4053 }
4054 }
4055
4056 for ($i = $start; $i < $start + $b['duration'] + $b['sleep']; $i += $min_int)
4057 {
4058 $found = false;
4059 for ($j = 0; $j < count($arr) && !$found; $j++)
4060 {
4061 /**
4062 * Try to block appointments that come from a different service or
4063 * if the number of people exceeds the total capacity.
4064 *
4065 * @since 1.6 check if the services are different only if $arr[$j][$i] is set
4066 */
4067 if (!empty($arr[$j][$i]) && ($cont_people + $people > $service['max_capacity'] || $b['id_service'] != $service['id'] || $b['closure']))
4068 {
4069 // if $b['id_service'] doesn't exist, take a look at the VikAppointments::getAllEmployeeReservations() function
4070 // if $service['id'] doesn't exist, take a look at the VikAppointmentsController::get_day_time_line() and VikAppointmentsController::get_day_time_line_service() functions
4071 $found = true;
4072 $arr[$j][$i] = 0;
4073 }
4074
4075 /**
4076 * If $seats argument is an array, push the remaining seats.
4077 *
4078 * @since 1.6
4079 */
4080 if (is_array($seats))
4081 {
4082 if ($b['id_service'] == $service['id'] && !$b['closure'])
4083 {
4084 // same service, we can display the remaining seats
4085 $seats[$i] = $service['max_capacity'] - $cont_people;
4086 }
4087 else
4088 {
4089 // booked for a different service, unset the remaining seats
4090 $seats[$i] = 0;
4091 }
4092 }
4093 }
4094
4095 /**
4096 * We may have different services that display shifted
4097 * timelines. This would cause an issue as previous check
4098 * ignores the times that don't match the evaluated slots.
4099 *
4100 * We need to unset here all the times that intersect with
4101 * an existing reservation, which might have been created for
4102 * a different service.
4103 *
4104 * @since 1.6.2
4105 */
4106 if (!$found)
4107 {
4108 // find all slots that intersect this one
4109 for ($j = 0; $j < count($arr); $j++)
4110 {
4111 foreach ($arr[$j] as $arr_hm => &$v)
4112 {
4113 if (($start < $arr_hm && $arr_hm < $start + $b['duration'] + $b['sleep'])
4114 || ($arr_hm < $start && $start < $arr_hm + $service['duration'] + $service['sleep']))
4115 {
4116 $v = 0;
4117 }
4118 }
4119 }
4120 }
4121 }
4122
4123 $cont_people = 0;
4124 }
4125 }
4126
4127 $n_step = $service['duration'] + $service['sleep'];
4128
4129 /*
4130
4131 for( $i = 0; $i < count($arr); $i++ ) {
4132 $step = 0;
4133 //for( $j = $worktime[$i]['fromts'], $len = 0; $j < $worktime[$i]['endts']; $j+=$min_int ) {
4134 for( $j = $worktime[$i]['fromts']; ($j+$min_int) <= $worktime[$i]['endts']; $j+=$min_int ) {
4135 if( $arr[$i][$j] == 1 ) {
4136 $step+=$min_int;
4137 if( $step == $n_step ) {
4138 $step-=$min_int;
4139 }
4140 } else {
4141 if( $step != 0 && $step < $n_step ) {
4142 for( $back = $j-$min_int; $back >= $j-$step; $back-=$min_int ) {
4143 $arr[$i][$back] = 2;
4144 }
4145 }
4146
4147 $step = 0;
4148 }
4149 }
4150
4151 if( $step != 0 && $step < $n_step ) {
4152 for( $back = $j-$min_int; $back >= $j-$step; $back-=$min_int ) {
4153 $arr[$i][$back] = 2;
4154 }
4155 }
4156 }
4157
4158 */
4159
4160 // array deep : elaborate each timeline
4161 for ($level = 0; $level < count($arr); $level++)
4162 {
4163 // get all the times in the current timeline
4164 $keys = array_keys($arr[$level]);
4165 // insert the end working time to evaluate properly the last available time
4166 $keys[] = $worktime[$level]['endts'];
4167
4168 for ($i = 0; $i < count($keys)-1; $i++)
4169 {
4170 $last_index = -1;
4171
4172 for ($j = $i + 1; $j < count($keys) && $last_index == -1; $j++)
4173 {
4174 /**
4175 * If index is last or if current time is not available.
4176 *
4177 * @since 1.6 Use empty($arr[$level][$keys[$j]]) to avoid "Undefined Index" notices.
4178 * These notices may be raised when the reservations were stored for certain
4179 * times that don't exist anymore.
4180 */
4181 // if ($keys[$j] == count($keys) -1 || $arr[$level][$keys[$j]] == 0)
4182 if ($keys[$j] == count($keys) -1 || empty($arr[$level][$keys[$j]]))
4183 {
4184 // store last index found and stop for statement
4185 $last_index = $j;
4186 }
4187 }
4188
4189 // if subtraction of last index found with current index is not enough
4190 if ($keys[$last_index] - $keys[$i] < $n_step)
4191 {
4192 // if current time is still available
4193 if ($arr[$level][$keys[$i]] == 1)
4194 {
4195 // mark current time as no more available
4196 $arr[$level][$keys[$i]] = 2;
4197 }
4198 }
4199 }
4200 }
4201
4202 $timeline = array();
4203 foreach ($arr as $a)
4204 {
4205 foreach ($a as $hour => $val)
4206 {
4207 $timeline[$hour] = $val;
4208 }
4209 }
4210
4211 return $timeline;
4212 }
4213
4214 // TIMEZONE
4215
4216 /**
4217 * Elaborates the timeline by intersecting the worktimes and the bookings found.
4218 * This method should be used in case the times need to be adjusted to the
4219 * employee timezone.
4220 *
4221 * @param array $worktime The list of the available working times.
4222 * @param array $bookings The reservations to intersect.
4223 * @param array $service The service details.
4224 * @param string $timezone The timezone string.
4225 *
4226 * @return array A list of associative arrays containing the elaborated timeline.
4227 * The keys contain the time (hour * 60 + min) and the values
4228 * contain the status (0: blocked, 1: available, 2: not enough space).
4229 *
4230 * @since 1.4
4231 *
4232 * @deprecated 1.8 Without replacement.
4233 */
4234 public static function elaborateTimeLineTimezone($worktime, $bookings, $service, $timezone)
4235 {
4236 $min_int = VAPFactory::getConfig()->getUint('minuteintervals');
4237 if ($service['interval'] == 1)
4238 {
4239 $min_int = 5;
4240 }
4241
4242 $arr = array();
4243
4244 for ($i = 0; $i < count($worktime); $i++)
4245 {
4246 for ($j = $worktime[$i]['fromts'], $len = 0; ($j + $min_int) <= $worktime[$i]['endts']; $j += $min_int)
4247 {
4248 $arr[$i][$j] = 1;
4249 }
4250 }
4251
4252 self::setCurrentTimezone($timezone);
4253
4254 foreach ($bookings as $b)
4255 {
4256 $date = getdate($b['checkin_ts']);
4257 $start = $date['hours'] * 60 + $date['minutes'];
4258
4259 if (isset($b['@day']))
4260 {
4261 /**
4262 * Check the day factor of a reservation to check if it is referring
4263 * to the current day or if it close to the bounds of this working time.
4264 * Used to support midnight reservations.
4265 *
4266 * @since 1.6
4267 */
4268
4269 if ($b['@day'] == 1)
4270 {
4271 // we are evaluating a reservation for the next day, so we need to increase the
4272 // initial time by 1440 minutes (24 hours * 60).
4273 $start += 1440;
4274 }
4275 else if ($b['@day'] == -1)
4276 {
4277 // we are evaluating a reservation for the previous day, so we need to decrease the
4278 // initial time by 1440 minutes (24 hours * 60).
4279 $start -= 1440;
4280 }
4281 }
4282
4283 for ($i = $start; $i < $start + $b['duration'] + $b['sleep']; $i += $min_int)
4284 {
4285 $found = false;
4286 for ($j = 0; $j < count($arr) && !$found; $j++)
4287 {
4288 if (!empty($arr[$j][$i]))
4289 {
4290 $found = true;
4291 $arr[$j][$i] = 0;
4292 }
4293 }
4294
4295 /**
4296 * We may have different service that display shifted
4297 * timelines. This would cause an issue as previous check
4298 * ignores the times that don't matches the evaluated slots.
4299 *
4300 * We need to unset here all the times that intersect with
4301 * an existing reservation, which might have been created for
4302 * a different service.
4303 *
4304 * @since 1.6.2
4305 */
4306 if (!$found)
4307 {
4308 // find all slots that intersect this one
4309 for ($j = 0; $j < count($arr); $j++)
4310 {
4311 foreach ($arr[$j] as $arr_hm => &$v)
4312 {
4313 if (($start < $arr_hm && $arr_hm < $start + $b['duration'] + $b['sleep'])
4314 || ($arr_hm < $start && $start < $arr_hm + $service['duration'] + $service['sleep']))
4315 {
4316 $v = 0;
4317 }
4318 }
4319 }
4320 }
4321 }
4322 }
4323
4324 if ($service['interval'] != 1)
4325 {
4326 $n_step = $service['duration'] + $service['sleep'];
4327
4328 for ($i = 0; $i < count($arr); $i++)
4329 {
4330 $step = 0;
4331 //for( $j = $worktime[$i]['fromts'], $len = 0; $j < $worktime[$i]['endts']; $j+=$min_int) {
4332 for ($j = $worktime[$i]['fromts'], $len = 0; ($j + $min_int) <= $worktime[$i]['endts']; $j += $min_int)
4333 {
4334 if ($arr[$i][$j] == 1)
4335 {
4336 $step += $min_int;
4337 if ($step >= $n_step)
4338 {
4339 $step-=$min_int;
4340 }
4341 }
4342 else
4343 {
4344 if ($step != 0 && $step < $n_step)
4345 {
4346 for ($back = $j - $min_int; $back >= $j - $step; $back -= $min_int)
4347 {
4348 $arr[$i][$back] = 2;
4349 }
4350 }
4351
4352 $step = 0;
4353 }
4354 }
4355
4356 if ($step != 0 && $step < $n_step)
4357 {
4358 for ($back = $j - $min_int; $back >= $j - $step; $back -= $min_int)
4359 {
4360 $arr[$i][$back] = 2;
4361 }
4362 }
4363 }
4364
4365 }
4366
4367 $mod = round(($service['duration'] + $service['sleep']) / $min_int);
4368
4369 if ($service['interval'] == 1 && $mod != 1)
4370 {
4371 $new_arr = array();
4372
4373 for ($i = 0; $i < count($arr); $i++)
4374 {
4375 $new_arr[$i] = array();
4376 $value = 1;
4377 $start = 0;
4378 $all_free = true;
4379
4380 $count = 0;
4381 for ($j = $worktime[$i]['fromts']; $j < $worktime[$i]['endts']; $j += $min_int, $count++)
4382 {
4383 if ($count % $mod == 0)
4384 {
4385 $start = $j;
4386 $value = 1;
4387 $all_free = true;
4388 }
4389
4390 $hourmin = intval($j / 60) . ' : ' . ($j % 60);
4391
4392 if ($arr[$i][$j] == 0)
4393 {
4394 $all_free = false;
4395 }
4396
4397 $value &= ($arr[$i][$j] == 2 ? 0 : $arr[$i][$j]);
4398
4399 if ((($count + 1) % $mod == 0 || $j + $min_int == $worktime[$i]['endts']))
4400 {
4401 // LAST TIME SLOTS is not enough length
4402 if (($count + 1) % $mod != 0)
4403 {
4404 $value = 0;
4405 }
4406
4407 if ($value == 0 && $all_free)
4408 {
4409 $value = 2;
4410 }
4411
4412 $new_arr[$i][$start] = $value;
4413 }
4414 }
4415 }
4416
4417 $arr = $new_arr;
4418 }
4419
4420 return $arr;
4421
4422 }
4423
4424 /**
4425 * Elaborates the timeline by intersecting the worktimes and the bookings found.
4426 * Filters the arrays of timelines to support a single associative array.
4427 * This method should be used in case the times need to be adjusted to the
4428 * employee timezone.
4429 *
4430 * @param array $worktime The list of the available working times.
4431 * @param array $bookings The reservations to intersect.
4432 * @param array $service The service details.
4433 * @param string $timezone The timezone string.
4434 *
4435 * @return array An associative array containing the elaborated timeline.
4436 * The keys contain the time (hour * 60 + min) and the values
4437 * contain the status (0: blocked, 1: available, 2: not enough space).
4438 *
4439 * @since 1.4
4440 *
4441 * @deprecated 1.8 Without replacement.
4442 */
4443 public static function elaborateTimeLineServiceTimezone($worktime, $bookings, $service, $timezone)
4444 {
4445 $arr = self::elaborateTimeLineTimezone($worktime, $bookings, $service, $timezone);
4446
4447 $timeline = array();
4448 foreach ($arr as $a)
4449 {
4450 foreach ($a as $hour => $val)
4451 {
4452 $timeline[$hour] = $val;
4453 }
4454 }
4455
4456 return $timeline;
4457 }
4458
4459 /**
4460 * Elaborates the timeline by intersecting the worktimes and the bookings found.
4461 * This method accepts multiple bookings at the same date and time depending on
4462 * the maximum capacity defined by the given service.
4463 *
4464 * This method always converts the timezone according to the configuration of
4465 * the employee, only in case the multi-timezone setting is enabled.
4466 *
4467 * @param array $worktime The list of the available working times.
4468 * @param array $bookings The reservations to intersect.
4469 * @param array $service The service details.
4470 * @param integer $people The number of specified people.
4471 * @param string $timezone The employee timezone (if set).
4472 * @param mixed &$seats An array containing the remaining seats for each time.
4473 *
4474 * @return array An associative array containing the elaborated timeline.
4475 * The keys contain the time (hour * 60 + min) and the values
4476 * contain the status (0: blocked, 1: available, 2: not enough space).
4477 *
4478 * @since 1.4
4479 *
4480 * @deprecated 1.8 Without replacement.
4481 */
4482 public static function elaborateTimeLineGroupServiceTimezone($worktime, $bookings, $service, $people, $timezone, &$seats = null)
4483 {
4484 $min_int = 0;
4485
4486 if ($service['interval'] == 1)
4487 {
4488 $min_int = $service['duration'] + $service['sleep'];
4489 }
4490 else
4491 {
4492 $min_int = VAPFactory::getConfig()->getUint('minuteintervals');
4493 }
4494
4495 $arr = array();
4496
4497 for ($i = 0; $i < count($worktime); $i++)
4498 {
4499 //for( $j = $worktime[$i]['fromts'], $len = 0; $j < $worktime[$i]['endts']; $j+=$min_int) {
4500 for ($j = $worktime[$i]['fromts'], $len = 0; ($j+$min_int) <= $worktime[$i]['endts']; $j += $min_int)
4501 {
4502 $arr[$i][$j] = 1;
4503 }
4504 }
4505
4506 self::setCurrentTimezone($timezone);
4507
4508 $cont_people = 0;
4509 for ($k = 0; $k < count($bookings); $k++)
4510 {
4511 $b = $bookings[$k];
4512
4513 $cont_people += $b['people_count'];
4514 if ($k == count($bookings) - 1 || $bookings[$k + 1]['checkin_ts'] != $b['checkin_ts'])
4515 {
4516 $date = getdate($b['checkin_ts']);
4517 $start = ($date['hours'] * 60) + $date['minutes'];
4518
4519 if (isset($b['@day']))
4520 {
4521 /**
4522 * Check the day factor of a reservation to check if it is referring
4523 * to the current day or if it close to the bounds of this working time.
4524 * Used to support midnight reservations.
4525 *
4526 * @since 1.6
4527 */
4528
4529 if ($b['@day'] == 1)
4530 {
4531 // we are evaluating a reservation for the next day, so we need to increase the
4532 // initial time by 1440 minutes (24 hours * 60).
4533 $start += 1440;
4534 }
4535 else if ($b['@day'] == -1)
4536 {
4537 // we are evaluating a reservation for the previous day, so we need to decrease the
4538 // initial time by 1440 minutes (24 hours * 60).
4539 $start -= 1440;
4540 }
4541 }
4542
4543 for ($i = $start; $i < $start + $b['duration'] + $b['sleep']; $i += $min_int)
4544 {
4545 $found = false;
4546 for ($j = 0; $j < count($arr) && !$found; $j++)
4547 {
4548 /**
4549 * Try to block appointments that come from a different service or
4550 * if the number of people exceeds the total capacity.
4551 *
4552 * @since 1.6 check if the services are different only if $arr[$j][$i] is set
4553 */
4554 if (!empty($arr[$j][$i]) && ($cont_people + $people > $service['max_capacity'] || $b['id_service'] != $service['id'] || $b['closure']))
4555 {
4556 // if $b['id_service'] doesn't exist, take a look at the VikAppointments::getAllEmployeeReservationsExcludingResId() function
4557 // if $service['id'] doesn't exist, take a look at the VikAppointmentsController::get_day_time_line() and VikAppointmentsController::get_day_time_line_service() functions
4558 $found = true;
4559 $arr[$j][$i] = 0;
4560 }
4561
4562 /**
4563 * If $seats argument is an array, push the remaining seats.
4564 *
4565 * @since 1.6
4566 */
4567 if (is_array($seats))
4568 {
4569 if ($b['id_service'] == $service['id'] && !$b['closure'])
4570 {
4571 // same service, we can display the remaining seats
4572 $seats[$i] = $service['max_capacity'] - $cont_people;
4573 }
4574 else
4575 {
4576 // booked for a different service, unset the remaining seats
4577 $seats[$i] = 0;
4578 }
4579 }
4580 }
4581 }
4582
4583 $cont_people = 0;
4584 }
4585 }
4586
4587 $n_step = $service['duration'] + $service['sleep'];
4588
4589 for ($i = 0; $i < count($arr); $i++)
4590 {
4591 $step = 0;
4592 //for( $j = $worktime[$i]['fromts'], $len = 0; $j < $worktime[$i]['endts']; $j+=$min_int) {
4593 for ($j = $worktime[$i]['fromts'], $len = 0; ($j + $min_int) <= $worktime[$i]['endts']; $j += $min_int)
4594 {
4595 if ($arr[$i][$j] == 1)
4596 {
4597 $step += $min_int;
4598 if ($step == $n_step)
4599 {
4600 $step -= $min_int;
4601 }
4602 }
4603 else
4604 {
4605 if ($step != 0 && $step < $n_step)
4606 {
4607 for ($back = $j - $min_int; $back >= $j - $step; $back -= $min_int)
4608 {
4609 $arr[$i][$back] = 2;
4610 }
4611 }
4612
4613 $step = 0;
4614 }
4615 }
4616
4617 if ($step != 0 && $step < $n_step)
4618 {
4619 for ($back = $j - $min_int; $back >= $j - $step; $back -= $min_int)
4620 {
4621 $arr[$i][$back] = 2;
4622 }
4623 }
4624 }
4625
4626 $timeline = array();
4627 foreach ($arr as $a)
4628 {
4629 foreach ($a as $hour => $val)
4630 {
4631 $timeline[$hour] = $val;
4632 }
4633 }
4634
4635 return $timeline;
4636 }
4637
4638 ///////////
4639
4640 /**
4641 * Parses the timelines in the array to fetch a single timeline.
4642 *
4643 * @param array $timelines The fetched timelines.
4644 *
4645 * @return array The resulting timeline.
4646 *
4647 * @deprecated 1.8 Without replacement.
4648 */
4649 public static function parseServiceTimeline($timelines)
4650 {
4651 $arr = array();
4652
4653 foreach ($timelines as $tl)
4654 {
4655 foreach ($tl as $hour => $val)
4656 {
4657 $res = $val;
4658 $is_pending = false;
4659
4660 for ($i = 0; $i < count($timelines) && $res != 1; $i++)
4661 {
4662 $res = (!empty($timelines[$i][$hour])) ? $timelines[$i][$hour] : 0;
4663 if ($res == 2)
4664 {
4665 $is_pending = true;
4666 }
4667 }
4668
4669 if ($res == 0 && $is_pending)
4670 {
4671 $res = 2;
4672 }
4673
4674 $arr[$hour] = $res;
4675 }
4676 }
4677
4678 return $arr;
4679 }
4680
4681 /**
4682 * Checks if the employees works on the specified day for the given service.
4683 *
4684 * @param integer $id_emp The employee ID.
4685 * @param integer $id_ser The service ID.
4686 * @param integer $ts The day UNIX timestamp.
4687 * @param mixed $dbo The database object.
4688 * @param array $locations The locations array.
4689 *
4690 * @return boolean True if it works, otherwise false.
4691 *
4692 * @deprecated 1.8 Use VAPAvailabilitySearch::hasWorkingDay() instead.
4693 */
4694 public static function hasEmployeeWorkingTimeOn($id_emp, $id_ser, $ts, $dbo = '', $locations = array())
4695 {
4696 if (is_numeric($ts))
4697 {
4698 $ts = date('Y-m-d', $ts);
4699 }
4700
4701 VAPLoader::import('libraries.availability.manager');
4702 $search = VAPAvailabilityManager::getInstance($id_service, $id_emp, array('locations' => $locations));
4703
4704 return $search->hasWorkingDay($ts);
4705 }
4706
4707 /**
4708 * Checks if the specified timestamp is in the past or
4709 * doesn't follow the booking minutes restriction.
4710 *
4711 * @param integer $timestamp The UNIX timestamp to check.
4712 * @param mixed $service Either the service details or the ID.
4713 *
4714 * @return boolean True if not allowed, false otherwise.
4715 *
4716 * @deprecated 1.8 Use VAPAvailabilitySearch::isPastTime() instead.
4717 */
4718 public static function isTimeInThePast($timestamp, $service = null)
4719 {
4720 if (is_numeric($timestamp))
4721 {
4722 // convert check-in from timestamp to date string
4723 $timestamp = date('Y-m-d H:i:s', $timestamp);
4724 }
4725
4726 if (!$service)
4727 {
4728 $input = JFactory::getApplication()->input;
4729
4730 // get service ID from request
4731 $service = $input->getUint('id_service');
4732
4733 if (!$service)
4734 {
4735 // service not found, try a different name
4736 $service = $input->getUint('id_ser');
4737 }
4738 }
4739
4740 if ($service && !is_numeric($service))
4741 {
4742 // we had a service array, take only the ID
4743 $service = (array) $service;
4744 $service = $service['id'];
4745 }
4746
4747 VAPLoader::import('libraries.availability.manager');
4748 $search = VAPAvailabilityManager::getInstance($service);
4749
4750 return $search->isPastTime($timestamp);
4751 }
4752
4753 /**
4754 * Checks if the specified timestamp belong to a closing day/period.
4755 *
4756 * @param integer $ts The timestamp to check.
4757 * @param integer $id_ser The service ID to restrict the closing days.
4758 *
4759 * @return boolean True if closing day, false otherwise.
4760 *
4761 * @deprecated 1.8 Use VAPAvailabilitySearch::isClosingDay() instead.
4762 */
4763 public static function isClosingDay($ts, $id_ser = null)
4764 {
4765 if (is_numeric($ts))
4766 {
4767 // convert to date string
4768 $ts = date('Y-m-d', $ts);
4769 }
4770
4771 VAPLoader::import('libraries.availability.manager');
4772 $search = VAPAvailabilityManager::getInstance($id_ser);
4773
4774 return $search->isClosingDay($ts);
4775 }
4776
4777 /**
4778 * Checks if the employees works on the specified day by checking the closing days too.
4779 *
4780 * @param integer $id_emp The employee ID.
4781 * @param integer $id_ser The service ID.
4782 * @param integer $ts The day UNIX timestamp.
4783 * @param array $closing_days The closing days list.
4784 * @param array $closing_perios The closing periods list.
4785 * @param mixed $dbo The database object.
4786 * @param array $locations The locations array.
4787 *
4788 * @return boolean True if it works, otherwise false.
4789 *
4790 * @deprecated 1.8 Use VAPAvailabilitySearch::isDayOpen() instead.
4791 */
4792 public static function isTableDayAvailable($id_emp, $id_ser, $ts, $closing_days = null, $closing_periods = null, $dbo = null, $locations = array())
4793 {
4794 if (is_numeric($ts))
4795 {
4796 // convert to date string
4797 $ts = date('Y-m-d', $ts);
4798 }
4799
4800 VAPLoader::import('libraries.availability.manager');
4801 $search = VAPAvailabilityManager::getInstance($id_ser, $id_emp, array('locations' => $locations));
4802
4803 return $search->isDayOpen($ts);
4804 }
4805
4806 /**
4807 * Checks if there is at least an employee that works on the
4808 * specified day by checking the closing days too.
4809 *
4810 * @param array $employees The employee IDs.
4811 * @param integer $id_ser The service ID.
4812 * @param integer $ts The day UNIX timestamp.
4813 * @param array $closing_days The closing days list.
4814 * @param array $closing_perios The closing periods list.
4815 * @param mixed $dbo The database object.
4816 * @param array $locations The locations array.
4817 *
4818 * @return boolean True if it works, otherwise false.
4819 *
4820 * @deprecated 1.8 Use VAPAvailabilitySearch::isDayOpen() instead.
4821 */
4822 public static function isGenericTableDayAvailable($employees, $id_ser, $ts, $closing_days = null, $closing_periods = null, $dbo = null, $locations = array())
4823 {
4824 if (is_numeric($ts))
4825 {
4826 // convert to date string
4827 $ts = date('Y-m-d', $ts);
4828 }
4829
4830 VAPLoader::import('libraries.availability.manager');
4831 $search = VAPAvailabilityManager::getInstance($id_ser, null, array('locations' => $locations));
4832
4833 return $search->isDayOpen($ts);
4834 }
4835
4836 /**
4837 * Returns the list of employees that offer the specified service.
4838 *
4839 * @param integer $id_ser The service ID.
4840 * @param boolean $ordering True to sort the employees.
4841 * @param boolean $listable True to take only listable employees.
4842 *
4843 * @return array The employees list.
4844 */
4845 public static function getEmployeesRelativeToService($id_ser, $ordering = false, $listable = false)
4846 {
4847 $dbo = JFactory::getDbo();
4848
4849 $q = $dbo->getQuery(true)
4850 ->select($dbo->qn(array(
4851 'e.id',
4852 'e.nickname',
4853 'e.timezone',
4854 )))
4855 ->from($dbo->qn('#__vikappointments_employee', 'e'))
4856 ->leftjoin($dbo->qn('#__vikappointments_ser_emp_assoc', 'a') . ' ON ' . $dbo->qn('e.id') . ' = ' . $dbo->qn('a.id_employee'))
4857 ->where($dbo->qn('a.id_service') . ' = ' . (int) $id_ser);
4858
4859 /**
4860 * Take only employees that should be listed.
4861 *
4862 * @since 1.6.5
4863 */
4864 if ($listable === true)
4865 {
4866 $q->where($dbo->qn('e.listable') . ' = 1');
4867 $q->andWhere(array(
4868 $dbo->qn('active_to') . ' = -1',
4869 $dbo->qn('active_to') . ' > ' . time(),
4870 ), 'OR');
4871 }
4872
4873 if ($ordering)
4874 {
4875 /**
4876 * Use custom ordering.
4877 *
4878 * @since 1.6.4
4879 */
4880 $q->order($dbo->qn('a.ordering') . ' ASC');
4881 }
4882
4883 $dbo->setQuery($q);
4884 return $dbo->loadAssocList();
4885 }
4886
4887 /**
4888 * Checks if the specified employee is available for the specified checkin.
4889 *
4890 * @param integer $id_emp The employee ID.
4891 * @param integer $id_ser The service ID.
4892 * @param integer $res_id The reservation to exlude, if any.
4893 * @param integer $checkin The checkin timestamp to check.
4894 * @param integer $duration The duration (in min.) to calculate the checkout.
4895 * @param integer $people The number of people.
4896 * @param integer $max_capacity The maximum number of allowed people.
4897 * @param mixed $dbo The database object.
4898 *
4899 * @return boolean True if available, otherwise false.
4900 */
4901 public static function isEmployeeAvailableFor($id_emp, $id_ser, $res_id, $checkin, $duration, $people, $max_capacity, $dbo = null)
4902 {
4903 if (!$dbo)
4904 {
4905 $dbo = JFactory::getDbo();
4906 }
4907
4908 $checkout = $duration * 60;
4909
4910 $start_res = date('H:i', $checkin);
4911 $exp = explode(':', $start_res);
4912 $start_res = $exp[0] * 60 + $exp[1];
4913 $end_res = $start_res + $duration;
4914
4915 $wt = self::getEmployeeWorkingTimes($id_emp, $id_ser, $checkin);
4916
4917 /**
4918 * Subtract 1 second from the closing time to make sure that
4919 * the check-in doesn't start when the working shift ends.
4920 *
4921 * @since 1.6.5
4922 */
4923 for ($i = 0; $i < count($wt) && !self::isBetween($start_res, $wt[$i]['fromts'], $wt[$i]['endts'] - 1); $i++);
4924
4925 if ($i >= count($wt))
4926 {
4927 // closing time
4928 return -1;
4929 }
4930
4931 // if id_ser NOT -1 and service has own calendar, don't consider
4932 // the reservations of the other services (of the same employee)
4933 $id_ser_app = $id_ser;
4934
4935 if (!self::hasServiceOwnCalendar($id_ser))
4936 {
4937 // don't apply own search
4938 $id_ser_app = -1; // unset service
4939 }
4940
4941 /**
4942 * Use timestamp for checkout.
4943 *
4944 * @since 1.6.2
4945 */
4946 $checkout += $checkin;
4947
4948 $q = "SELECT `r`.`id`, `r`.`people`
4949 FROM `#__vikappointments_reservation` AS `r`
4950 LEFT JOIN `#__vikappointments_service` AS `s` ON `s`.`id`=`r`.`id_service`
4951 WHERE `r`.`id` <> $res_id AND `r`.`id_employee` = $id_emp AND
4952 (
4953 (`s`.`has_own_cal` = 0 AND $id_ser_app = -1)
4954 OR (`s`.`has_own_cal` = 1 AND `r`.`id_service` = $id_ser_app)
4955 )
4956 AND `r`.`status` <> 'REMOVED' AND `r`.`status` <> 'CANCELED' AND
4957 (
4958 (
4959 `r`.`checkin_ts` <= $checkin AND $checkin < (`r`.`checkin_ts` + `r`.`duration` * 60 + `r`.`sleep` * 60)
4960 )
4961 OR
4962 (
4963 `r`.`checkin_ts` < $checkout AND $checkout <= (`r`.`checkin_ts` + `r`.`duration` * 60 + `r`.`sleep` * 60)
4964 )
4965 OR
4966 (
4967 `r`.`checkin_ts` <= $checkin AND $checkout <= (`r`.`checkin_ts` + `r`.`duration` * 60 + `r`.`sleep` * 60)
4968 )
4969 OR
4970 (
4971 `r`.`checkin_ts` >= $checkin AND $checkout >= (`r`.`checkin_ts` + `r`.`duration` * 60 + `r`.`sleep` * 60)
4972 )
4973 OR
4974 (
4975 `r`.`checkin_ts` = $checkin AND $checkout = (`r`.`checkin_ts` + `r`.`duration` * 60 + `r`.`sleep` * 60)
4976 )
4977 )";
4978
4979 $dbo->setQuery($q, 0, $max_capacity);
4980 $rows = $dbo->loadAssocList();
4981
4982 if ($rows)
4983 {
4984 $cont_people = 0;
4985
4986 foreach ($rows as $r)
4987 {
4988 $cont_people += $r['people'];
4989 }
4990
4991 if ($cont_people + $people > $max_capacity)
4992 {
4993 // not available
4994 return 0;
4995 }
4996 }
4997
4998 /**
4999 * Sum 1 second to the opening time to make sure that
5000 * the check-out doesn't end when the working shift starts.
5001 *
5002 * @since 1.6.5
5003 */
5004 for ($i = 0; $i < count($wt) && !self::isBetween($end_res, $wt[$i]['fromts'] + 1, $wt[$i]['endts']); $i++);
5005
5006 if ($i >= count($wt))
5007 {
5008 // closing time
5009 return -1;
5010 }
5011
5012 /**
5013 * Get employee timeline in order to make sure that
5014 * the selected time slot is accepted by the employee.
5015 *
5016 * This is needed in order to assign the reservation to an
5017 * employee that work on the specified check-in but that
5018 * shouldn't accept the requested time slot.
5019 *
5020 * @since 1.6.5
5021 */
5022 $timeline = VikAppointments::elaborateTimeLine($wt, array(), $id_ser);
5023
5024 foreach ($timeline as $shift)
5025 {
5026 if (isset($shift[$start_res]))
5027 {
5028 // time slot found
5029 return 1;
5030 }
5031 }
5032
5033 // not available
5034 return 0;
5035 }
5036
5037 /**
5038 * Checks if there is at least an employee available for the specified checkin.
5039 *
5040 * @param integer $id_ser The service ID.
5041 * @param integer $checkin The checkin timestamp to check.
5042 * @param integer $duration The duration (in min.) to calculate the checkout.
5043 * @param integer $people The number of people.
5044 * @param integer $max_capacity The maximum number of allowed people.
5045 * @param mixed $dbo The database object.
5046 *
5047 * @return boolean True if available, otherwise false.
5048 *
5049 * @uses isEmployeeAvailableFor()
5050 */
5051 public static function getAvailableEmployeeOnService($id_ser, $checkin, $duration, $people, $max_capacity, $dbo = null)
5052 {
5053 if (!$dbo)
5054 {
5055 $dbo = JFactory::getDbo();
5056 }
5057
5058 $id_ser = (int) $id_ser;
5059
5060 $q = "SELECT `e`.`id`, COUNT(`r`.`id`) AS `count`
5061 FROM `#__vikappointments_employee` AS `e`
5062 LEFT JOIN `#__vikappointments_ser_emp_assoc` AS `a` ON `e`.`id` = `a`.`id_employee`
5063 LEFT JOIN `#__vikappointments_reservation` AS `r` ON `e`.`id` = `r`.`id_employee`
5064 WHERE `a`.`id_service` = $id_ser
5065 GROUP BY `e`.`id`
5066 ORDER BY `count` ASC;";
5067
5068 $dbo->setQuery($q);
5069 $employees = $dbo->loadAssocList();
5070
5071 if (!$employees)
5072 {
5073 return -1;
5074 }
5075
5076 foreach ($employees as $e)
5077 {
5078 if (self::isEmployeeAvailableFor($e['id'], $id_ser, -1, $checkin, $duration, $people, $max_capacity, $dbo) == 1)
5079 {
5080 return $e['id'];
5081 }
5082 }
5083
5084 return 0;
5085 }
5086
5087 /**
5088 * Returns the employee working times for the given day.
5089 * In case of 24h working days, the system will extend the ending
5090 * time of the last working day in order to support midnight appointments.
5091 *
5092 * @param integer $id_emp The employee ID.
5093 * @param integer $id_ser The service ID.
5094 * @param integer $day The date timestamp.
5095 * @param array $locations The supported locations.
5096 *
5097 * @return array A list containing the matching working days.
5098 *
5099 * @uses _getEmployeeWorkingTimes()
5100 */
5101 public static function getEmployeeWorkingTimes($id_emp, $id_ser, $day, array $locations = array())
5102 {
5103 // get working times for the given day
5104 $worktimes = self::_getEmployeeWorkingTimes($id_emp, $id_ser, $day, $locations);
5105
5106 // update current timestamp by one day
5107 $day = strtotime('+1 day 00:00:00', $day);
5108
5109 // fallback to obtain the working times for the next day
5110 $next = self::_getEmployeeWorkingTimes($id_emp, $id_ser, $day, $locations);
5111
5112 if ($worktimes && $next && $next[0]['fromts'] == 0)
5113 {
5114 // We have probably a 24H working time.
5115 // Extend the last working time with the first
5116 // one of the next day
5117 $last = &$worktimes[count($worktimes) - 1];
5118
5119 $last['endts'] += $next[0]['endts'];
5120 }
5121
5122 return $worktimes;
5123 }
5124
5125 /**
5126 * Returns the employee working times for the given day.
5127 *
5128 * @param integer $id_emp The employee ID.
5129 * @param integer $id_ser The service ID.
5130 * @param integer $day The date timestamp.
5131 * @param array $locations The supported locations.
5132 *
5133 * @return array A list containing the matching working days.
5134 *
5135 * @since 1.6
5136 */
5137 protected static function _getEmployeeWorkingTimes($id_emp, $id_ser, $day, array $locations = array())
5138 {
5139 $dbo = JFactory::getDbo();
5140
5141 $date = getdate($day);
5142 //$timestamp = mktime( 0, 0, 0, $date['mon'], $date['mday'], $date['year'] );
5143 $timestamp = date('Ymd', $date[0]);
5144
5145 // obtain the working days for the given custom day
5146
5147 /**
5148 * Convert the timestamp in the database to UTC format,
5149 * so that an offset lower than 0 won't shift anymore the
5150 * dates to the previous ones.
5151 *
5152 * @see CONVERT_TZ()
5153 *
5154 * @since 1.6.2
5155 */
5156
5157 $q = $dbo->getQuery(true)
5158 ->select('*')
5159 ->from($dbo->qn('#__vikappointments_emp_worktime'))
5160 ->where(array(
5161 $dbo->qn('id_employee') . ' = ' . (int) $id_emp,
5162 $dbo->qn('id_service') . ' = ' . (int) $id_ser,
5163 'DATE_FORMAT(
5164 CONVERT_TZ(FROM_UNIXTIME(' . $dbo->qn('ts') . '), @@session.time_zone, \'+00:00\'),
5165 \'%Y%m%d\'
5166 ) = ' . $timestamp,
5167 ))
5168 ->order(array(
5169 $dbo->qn('closed') . ' DESC',
5170 $dbo->qn('fromts') . ' ASC',
5171 ));
5172
5173 if (count($locations))
5174 {
5175 $q->andWhere(array(
5176 $dbo->qn('id_location') . ' = -1',
5177 $dbo->qn('id_location') . ' IN (' . implode(',', array_map('intval', $locations)) . ')',
5178 ), 'OR');
5179 }
5180
5181 $dbo->setQuery($q);
5182 $rows = $dbo->loadAssocList();
5183
5184 if ($rows)
5185 {
5186 if ($rows[0]['closed'])
5187 {
5188 // this custom day is closed
5189 return [];
5190 }
5191
5192 return $rows;
5193 }
5194
5195 // obtain the working days for the given week day
5196
5197 $q->clear('where');
5198
5199 $q->where(array(
5200 $dbo->qn('id_employee') . ' = ' . (int) $id_emp,
5201 $dbo->qn('id_service') . ' = ' . (int) $id_ser,
5202 $dbo->qn('day') . ' = ' . $date['wday'],
5203 ));
5204
5205 /**
5206 * Fixed query to ignore weekday when the timestamp is specified.
5207 *
5208 * @since 1.6.1
5209 */
5210 $q->where($dbo->qn('ts') . ' <= 0');
5211
5212 if (count($locations))
5213 {
5214 $q->andWhere(array(
5215 $dbo->qn('id_location') . ' = -1',
5216 $dbo->qn('id_location') . ' IN (' . implode(',', array_map('intval', $locations)) . ')',
5217 ), 'OR');
5218 }
5219
5220 $dbo->setQuery($q);
5221 $rows = $dbo->loadAssocList();
5222
5223 if ($rows)
5224 {
5225 if ($rows[0]['closed'])
5226 {
5227 // this day of the week is closed
5228 return [];
5229 }
5230
5231 return $rows;
5232 }
5233
5234 return [];
5235 }
5236
5237 /**
5238 * Attaches the working days of the employee to the specified service.
5239 *
5240 * This method have been moved here from the `empeditservice` controller
5241 * as it needed to be used in other sections of the program.
5242 *
5243 * @param integer $id_service The service ID.
5244 * @param integer $id_employee The employee ID.
5245 *
5246 * @return boolean True if attached, otherwise false.
5247 *
5248 * @since 1.7
5249 */
5250 public static function attachWorkingDays($id_service, $id_employee)
5251 {
5252 $dbo = JFactory::getDbo();
5253
5254 $attached = false;
5255
5256 $q = $dbo->getQuery(true)
5257 ->select('*')
5258 ->from($dbo->qn('#__vikappointments_emp_worktime'))
5259 ->where(array(
5260 $dbo->qn('id_employee') . ' = ' . (int) $id_employee,
5261 $dbo->qn('id_service') . ' = -1',
5262 ));
5263
5264 $dbo->setQuery($q);
5265
5266 foreach ($dbo->loadObjectList() as $w)
5267 {
5268 /**
5269 * Keep a relation with the parent working day
5270 * while assigning a new employee to this service.
5271 *
5272 * @since 1.6.2
5273 */
5274 $w->parent = $w->id;
5275
5276 // inject service ID
5277 $w->id_service = $id_service;
5278 // unset ID for insert
5279 unset($w->id);
5280
5281 $dbo->insertObject('#__vikappointments_emp_worktime', $w, 'id');
5282
5283 $attached = $attached || $w->id;
5284 }
5285
5286 return $attached;
5287 }
5288
5289 /**
5290 * Updates the status of all the orders out of time to REMOVED.
5291 * This method is used to free the slots occupied by pending orders
5292 * that haven't been confirmed within the specified range of time.
5293 *
5294 * Affects only the reservations that match the specified employee ID.
5295 *
5296 * @param integer $id_emp The employee ID.
5297 * @param mixed $dbo The database object.
5298 *
5299 * @return void
5300 *
5301 * @deprecated 1.8 Use VikAppointmentsModelReservation::checkExpired() instead.
5302 */
5303 public static function removeAllReservationsOutOfTime($id_emp, $dbo = null)
5304 {
5305 JModelVAP::getInstance('reservation')->checkExpired(array('id_employee' => $id_emp));
5306 }
5307
5308 /**
5309 * Updates the status of all the orders out of time to REMOVED.
5310 * This method is used to free the slots occupied by pending orders
5311 * that haven't been confirmed within the specified range of time.
5312 *
5313 * Affects only the reservations that match the specified service ID.
5314 *
5315 * @param integer $id_ser The service ID.
5316 * @param mixed $dbo The database object.
5317 *
5318 * @return void
5319 *
5320 * @deprecated 1.8 Use VikAppointmentsModelReservation::checkExpired() instead.
5321 */
5322 public static function removeAllServicesReservationsOutOfTime($id_ser, $dbo = null)
5323 {
5324 JModelVAP::getInstance('reservation')->checkExpired(array('id_service' => $id_ser));
5325 }
5326
5327 /**
5328 * Returns the list of the payments created by the specified employee.
5329 * If the employee doesn't own any custom payment, the global ones
5330 * will be returned.
5331 *
5332 * @param integer $id_emp The employee ID.
5333 *
5334 * @return array The payments list.
5335 *
5336 * @deprecated 1.8 Use getPayments() instead.
5337 */
5338 public static function getAllEmployeePayments($id_emp = 0)
5339 {
5340 return static::getPayments('appointments', array('id_employee' => $id_emp));
5341 }
5342
5343 /**
5344 * Returns a list of available payments.
5345 *
5346 * @param string $group The group to which the payments belong (appointments, packages, subscriptions or empsubscriptions).
5347 * @param array $options An array of options to filter the payments, such as "id_employee" to take only the payments assigned
5348 * to the specified employee (only for appointments) and "strict" to validate the publishing options of
5349 * the payments (by default it relies on the client).
5350 *
5351 * @return array The payments list.
5352 *
5353 * @since 1.7
5354 */
5355 public static function getPayments($group = null, array $options = array())
5356 {
5357 $dispatcher = VAPFactory::getEventDispatcher();
5358
5359 $dbo = JFactory::getDbo();
5360 $user = JFactory::getUser();
5361
5362 $q = $dbo->getQuery(true);
5363
5364 $q->select('p.*');
5365 $q->from($dbo->qn('#__vikappointments_gpayments', 'p'));
5366
5367 if ($group == 'appointments')
5368 {
5369 // allowed for appointments
5370 $q->where($dbo->qn('p.appointments') . ' = 1');
5371
5372 // if employee is set, get global and custom payments
5373 if (!empty($options['id_employee']))
5374 {
5375 $q->andWhere(array(
5376 $dbo->qn('p.id_employee') . ' <= 0',
5377 $dbo->qn('p.id_employee') . ' = ' . (int) $options['id_employee'],
5378 ), 'OR');
5379
5380 // custom payments come first
5381 $q->order($dbo->qn('p.id_employee') . ' DESC');
5382 }
5383 // otherwise get only global payments
5384 else
5385 {
5386 $q->where($dbo->qn('p.id_employee') . ' <= 0');
5387 }
5388 }
5389 else
5390 {
5391 // allowed for packages and subscriptions
5392 $q->where($dbo->qn('p.subscr') . ' = 1');
5393 }
5394
5395 if (!isset($options['strict']))
5396 {
5397 // strict mode undefined, lean on the current application client
5398 $options['strict'] = JFactory::getApplication()->isClient('site');
5399 }
5400
5401 // check whether we should validate the publishing options of the payments
5402 if ($options['strict'])
5403 {
5404 $q->where($dbo->qn('p.published') . ' = 1');
5405
5406 /**
5407 * Retrieve only the payments that belong to the view
5408 * access level of the current user.
5409 *
5410 * @since 1.6.2
5411 */
5412 $levels = $user->getAuthorisedViewLevels();
5413
5414 if ($levels)
5415 {
5416 $q->where($dbo->qn('p.level') . ' IN (' . implode(', ', $levels) . ')');
5417 }
5418 }
5419
5420 $q->order(array(
5421 // published payments before unpublished ones
5422 $dbo->qn('p.published') . ' DESC',
5423 // finally sort by ordering column
5424 $dbo->qn('p.ordering') . ' ASC',
5425 ));
5426
5427 /**
5428 * Trigger event to allow the plugins to manipulate the query used to retrieve
5429 * the available payment gateways.
5430 *
5431 * @param mixed &$query The query string or a query builder object.
5432 * @param string $group The group to which the payments belong.
5433 * @param array $options An array of options to filter the payments.
5434 *
5435 * @return void
5436 *
5437 * @since 1.7
5438 */
5439 $dispatcher->trigger('onFetchAvailablePaymentMethods', array(&$q, $group, $options));
5440
5441 $dbo->setQuery($q);
5442 $payments = $dbo->loadAssocList();
5443
5444 // check if there is at least a payment
5445 if (!$payments)
5446 {
5447 // no available payments
5448 return [];
5449 }
5450
5451 $count = 0;
5452
5453 /**
5454 * The payment can be available only for trusted customer.
5455 * In this case, we have to count the total number of orders
5456 * made by the specified user, which must be equals or greater
5457 * than the "trust" factor of the payment.
5458 *
5459 * @since 1.7.1
5460 */
5461 if (!$user->guest)
5462 {
5463 $q = $dbo->getQuery(true);
5464 $q->select('COUNT(1)');
5465 $q->where(1);
5466
5467 if ($group == 'appointments')
5468 {
5469 // count number of approved appointments
5470 $approved = JHtml::fetch('vaphtml.status.find', 'code', array('appointments' => 1, 'approved' => 1));
5471
5472 $q->from($dbo->qn('#__vikappointments_reservation', 'o'));
5473 $q->leftjoin($dbo->qn('#__vikappointments_users', 'c') . ' ON ' . $dbo->qn('c.id') . ' = ' . $dbo->qn('o.id_user'));
5474
5475 $q->andWhere(array(
5476 $dbo->qn('o.createdby') . ' = ' . $user->id,
5477 $dbo->qn('c.jid') . ' = ' . $user->id,
5478 ), 'OR');
5479 }
5480 else if ($group == 'packages')
5481 {
5482 // count number of approved packages
5483 $approved = JHtml::fetch('vaphtml.status.find', 'code', array('packages' => 1, 'approved' => 1));
5484
5485 $q->from($dbo->qn('#__vikappointments_package_order', 'o'));
5486 $q->leftjoin($dbo->qn('#__vikappointments_users', 'c') . ' ON ' . $dbo->qn('c.id') . ' = ' . $dbo->qn('o.id_user'));
5487
5488 $q->andWhere(array(
5489 $dbo->qn('o.createdby') . ' = ' . $user->id,
5490 $dbo->qn('c.jid') . ' = ' . $user->id,
5491 ), 'OR');
5492 }
5493 else if ($group == 'subscriptions')
5494 {
5495 // count number of approved subscriptions (customers)
5496 $approved = JHtml::fetch('vaphtml.status.find', 'code', array('subscriptions' => 1, 'approved' => 1));
5497
5498 $q->from($dbo->qn('#__vikappointments_subscr_order', 'o'));
5499 $q->leftjoin($dbo->qn('#__vikappointments_users', 'c') . ' ON ' . $dbo->qn('c.id') . ' = ' . $dbo->qn('o.id_user'));
5500
5501 $q->where($dbo->qn('c.jid') . ' = ' . $user->id);
5502 }
5503 else if ($group == 'empsubscriptions')
5504 {
5505 // count number of approved subscriptions (employees)
5506 $approved = JHtml::fetch('vaphtml.status.find', 'code', array('subscriptions' => 1, 'approved' => 1));
5507
5508 $q->from($dbo->qn('#__vikappointments_subscr_order', 'o'));
5509 $q->leftjoin($dbo->qn('#__vikappointments_employee', 'e') . ' ON ' . $dbo->qn('e.id') . ' = ' . $dbo->qn('o.id_employee'));
5510
5511 $q->where($dbo->qn('e.jid') . ' = ' . $user->id);
5512 }
5513
5514 if ($approved)
5515 {
5516 // filter by approved status
5517 $q->where($dbo->qn('o.status') . ' IN (' . implode(',', array_map(array($dbo, 'q'), $approved)) . ')');
5518 }
5519
5520 $dbo->setQuery($q);
5521 $count = (int) $dbo->loadResult();
5522 }
5523
5524 // remove all the payments that do not match with the minimum required count
5525 // of appointments/orders confirmed by the customer
5526 $payments = array_values(array_filter($payments, function($p) use ($count)
5527 {
5528 return $p['trust'] <= $count;
5529 }));
5530
5531 if (!$payments)
5532 {
5533 // no available payments
5534 return [];
5535 }
5536
5537 // check whether the first available payment is published and belongs
5538 // to the specified employee
5539 if ($payments[0]['id_employee'] > 0 && $payments[0]['published'])
5540 {
5541 // filter array to remove the global payments
5542 $payments = array_values(array_filter($payments, function($item)
5543 {
5544 return $item['id_employee'] > 0;
5545 }));
5546 }
5547
5548 /**
5549 * Trigger event to allow the plugins to manipulate the list containing
5550 * all the payment methods that are going to be displayed.
5551 *
5552 * @param array &$payments An array of available payments.
5553 * @param string $group The group to which the payments belong.
5554 * @param array $options An array of options to filter the payments.
5555 *
5556 * @return void
5557 *
5558 * @since 1.7
5559 */
5560 $dispatcher->trigger('onManipulateAvailablePaymentMethods', array(&$payments, $group, $options));
5561
5562 return array_values($payments);
5563 }
5564
5565 /**
5566 * Returns the payment record that matches the given ID.
5567 * The payment can be global or owned by a specific employee.
5568 *
5569 * @param integer $id_pay The payment ID.
5570 * @param boolean $strict True to get the payment only if it is published.
5571 *
5572 * @return mixed An associative array on success, otherwise null.
5573 */
5574 public static function getPayment($id_pay, $strict = true)
5575 {
5576 $dbo = JFactory::getDbo();
5577
5578 $q = $dbo->getQuery(true);
5579
5580 $q->select('*')
5581 ->from($dbo->qn('#__vikappointments_gpayments'))
5582 ->where($dbo->qn('id') . ' = ' . $id_pay);
5583
5584 if ($strict)
5585 {
5586 $q->where($dbo->qn('published') . ' = 1');
5587
5588 /**
5589 * Retrieve only the payments that belong to the view
5590 * access level of the current user.
5591 *
5592 * @since 1.6.2
5593 */
5594 $levels = JFactory::getUser()->getAuthorisedViewLevels();
5595
5596 if ($levels)
5597 {
5598 $q->where($dbo->qn('level') . ' IN (' . implode(', ', $levels) . ')');
5599 }
5600 }
5601
5602 $dbo->setQuery($q, 0, 1);
5603 return $dbo->loadAssoc();
5604 }
5605
5606 /////////////////////////////////////////////
5607 ///////////////// LOCATIONS /////////////////
5608 /////////////////////////////////////////////
5609
5610 /**
5611 * Returns the location related to the specified employee, service and checkin.
5612 *
5613 * @param integer $id_emp The employee ID. If not provided, it won't be used.
5614 * @param integer $id_ser The service ID.
5615 * @param integer $ts The checkin UNIX timestamp.
5616 * @param mixed $dbo The database object.
5617 *
5618 * @return mixed The location ID on success, otherwise false.
5619 *
5620 * @deprecated 1.8 Use VikAppointmentsModelWorktime::getLocation() instead.
5621 */
5622 public static function getEmployeeLocationFromTime($id_emp, $id_ser, $ts, $dbo = null)
5623 {
5624 if (is_numeric($ts))
5625 {
5626 $ts = date('Y-m-d H:i:s', $ts);
5627 }
5628
5629 return JModelVAP::getInstance('worktime')->getLocation($ts, $id_ser, $id_emp);
5630 }
5631
5632 /**
5633 * Method used to return the details of the given location.
5634 *
5635 * @param integer $id_location The location ID.
5636 * @param mixed $dbo The database object.
5637 *
5638 * @return mixed The location details on success, false otherwise.
5639 *
5640 * @deprecated 1.8 Use VikAppointmentsModelLocation::getInfo() instead.
5641 */
5642 public static function fillEmployeeLocation($id_location, $dbo = null)
5643 {
5644 return (array) JModelVAP::getInstance('location')->getInfo($id_location);
5645 }
5646
5647 /**
5648 * Parses the locations details to create a human-readable string.
5649 *
5650 * @param mixed $location The location details.
5651 *
5652 * @return string The location information as string.
5653 *
5654 * @deprecated 1.8 Without replacement.
5655 */
5656 public static function locationToString($location)
5657 {
5658 if (!$location)
5659 {
5660 return '';
5661 }
5662
5663 $location = (array) $location;
5664
5665 return isset($location['text']) ? $location['text'] : '';
5666 }
5667
5668 /**
5669 * Calculates the distance between 2 coordinates.
5670 *
5671 * @param float $lat_1 The latitude of the first point.
5672 * @param float $lng_1 The longitude of the first point.
5673 * @param float $lat_2 The latitude of the first point.
5674 * @param float $lng_2 The longitude of the second point.
5675 *
5676 * @return float The distance between the 2 points (in km).
5677 *
5678 * @since 1.5
5679 */
5680 public static function getGeodeticaDistance($lat_1, $lng_1, $lat_2, $lng_2)
5681 {
5682 $lat_1 = $lat_1 * pi() / 180.0;
5683 $lng_1 = $lng_1 * pi() / 180.0;
5684
5685 $lat_2 = $lat_2 * pi() / 180.0;
5686 $lng_2 = $lng_2 * pi() / 180.0;
5687
5688 /** distance between 2 coordinates
5689 * R = 6371 (Eart radius ~6371 km)
5690 *
5691 * coordinates in radians
5692 * lat1, lng1, lat2, lng2
5693 *
5694 * Calculate the included angle fi
5695 * fi = abs( lng1 - lng2 );
5696 *
5697 * Calculate the third side of the spherical triangle
5698 * p = acos(
5699 * sin(lat2) * sin(lat1) +
5700 * cos(lat2) * cos(lat1) *
5701 * cos( fi )
5702 * )
5703 *
5704 * Multiply the third side per the Earth radius (distance in km)
5705 * D = p * R;
5706 *
5707 * MINIFIED EXPRESSION
5708 *
5709 * acos(
5710 * sin(lat2) * sin(lat1) +
5711 * cos(lat2) * cos(lat1) *
5712 * cos( abs(lng1-lng2) )
5713 * ) * R
5714 *
5715 */
5716
5717 return acos(
5718 sin($lat_2) * sin($lat_1) +
5719 cos($lat_2) * cos($lat_1) *
5720 cos(abs($lng_1 - $lng_2))
5721 ) * 6371;
5722 }
5723
5724 /**
5725 * Helper method used to format the distance
5726 * in meters and kilometers.
5727 *
5728 * @param float $distance The distance to format (in km).
5729 * @param mixed $unit The unit to use or the filters array
5730 * containing the unit parameter.
5731 *
5732 * @return string The formatted distance.
5733 *
5734 * @since 1.5
5735 */
5736 public static function formatDistance($distance, $unit = null)
5737 {
5738 VAPLoader::import('libraries.helpers.distance');
5739
5740 if (!$unit)
5741 {
5742 $input = JFactory::getApplication()->input;
5743 $unit = $input->get('filters', array(), 'array');
5744 }
5745
5746 if (is_array($unit))
5747 {
5748 $unit = isset($unit['distunit']) ? $unit['distunit'] : VAPDistanceHelper::METER;
5749 }
5750
5751 // distance is always passed in meters and needs to be converted
5752 // to the specified unit
5753 return VAPDistanceHelper::format($distance * 1000, $unit, VAPDistanceHelper::METER);
5754 }
5755
5756 /**
5757 * Helper method used to convert the distance in kilometers.
5758 *
5759 * @param float $distance The distance to convert.
5760 * @param mixed $unit The unit to use or the filters array
5761 * containing the unit parameter.
5762 *
5763 * @return string The converted distance.
5764 *
5765 * @since 1.6
5766 */
5767 public static function convertDistanceToKilometers($distance, $unit = null)
5768 {
5769 VAPLoader::import('libraries.helpers.distance');
5770
5771 if (!$unit)
5772 {
5773 $input = JFactory::getApplication()->input;
5774 $unit = $input->get('filters', array(), 'array');
5775 }
5776
5777 if (is_array($unit))
5778 {
5779 $unit = isset($unit['distunit']) ? $unit['distunit'] : VAPDistanceHelper::KILOMETER;
5780 }
5781
5782 return VAPDistanceHelper::convert($distance, VAPDistanceHelper::KILOMETER, $unit);
5783 }
5784
5785 /**
5786 * Returns the timezone of the given employee.
5787 *
5788 * @param integer $id_emp The employee ID.
5789 *
5790 * @return string The employee timezone.
5791 *
5792 * @deprecated 1.8 Use VikAppointmentsModelEmployee::getTimezone() instead.
5793 */
5794 public static function getEmployeeTimezone($id_emp)
5795 {
5796 return JModelVAP::getInstance('employee')->getTimezone($id_emp);
5797 }
5798
5799 /**
5800 * Alters the server timezone with the given one.
5801 *
5802 * @param mixed $tz The new timezone to set.
5803 *
5804 * @return mixed The changing result.
5805 *
5806 * @deprecated 1.8 Without replacement.
5807 */
5808 public static function setCurrentTimezone($tz)
5809 {
5810 if (!$tz || !VAPFactory::getConfig()->getBool('multitimezone'))
5811 {
5812 return false;
5813 }
5814
5815 return date_default_timezone_set($tz);
5816 }
5817
5818 /////////////////////////////////////////////
5819 ////////////////// REVIEWS //////////////////
5820 /////////////////////////////////////////////
5821
5822 /**
5823 * Helper method used to round a float value to the closest half.
5824 *
5825 * @param float $d The amount to round.
5826 *
5827 * @return float The rounded amount.
5828 */
5829 public static function roundHalfClosest($d)
5830 {
5831 $floor = floor($d * 2) / 2;
5832 $ceil = ceil($d * 2) / 2;
5833
5834 if (abs($d - $floor) < abs($d - $ceil))
5835 {
5836 return $floor;
5837 }
5838
5839 return $ceil;
5840 }
5841
5842 /**
5843 * Loads the reviews for the given entity.
5844 *
5845 * @todo Need a method refactoring.
5846 *
5847 * @param string $figure The entity to get (employee or service).
5848 * @param integer $id The entity ID.
5849 * @param integer $start The limit start.
5850 *
5851 * @return array The reviews list.
5852 *
5853 * @since 1.4
5854 */
5855 public static function loadReviews($figure, $id, $start = 0)
5856 {
5857 $result = new stdClass;
5858 $result->size = 0;
5859 $result->votes = 0;
5860
5861 $dbo = JFactory::getDbo();
5862
5863 $config = VAPFactory::getConfig();
5864
5865 $lim = $config->getUint('revlimlist');
5866 $lim0 = $start;
5867
5868 $session = JFactory::getSession();
5869
5870 $app = \JFactory::getApplication();
5871
5872 $ordering = [
5873 'by' => $app->getUserStateFromRequest('vikappointments.reviews.order.column', 'revordby', '', 'string') ?: 'timestamp',
5874 'mode' => $app->getUserStateFromRequest('vikappointments.reviews.order.direction', 'revordmode', '', 'string') ?: 'desc',
5875 ];
5876
5877 $q = $dbo->getQuery(true);
5878
5879 $q->select('SQL_CALC_FOUND_ROWS r.*');
5880 $q->select($dbo->qn('u.image'));
5881
5882 $q->from($dbo->qn('#__vikappointments_reviews', 'r'));
5883
5884 /**
5885 * Fixed LEFT JOIN which was loading all the customers that was not assigned to a
5886 * specific Joomla/WordPress user ID. The JOIN must exclude all the records that
5887 * owns a `jid` equals or lower than 0.
5888 *
5889 * @since 1.6.3
5890 */
5891 $q->leftjoin($dbo->qn('#__vikappointments_users', 'u')
5892 . ' ON ' . $dbo->qn('r.jid') . ' = ' . $dbo->qn('u.jid') . ' AND ' . $dbo->qn('r.jid') . ' > 0');
5893
5894 $q->where(array(
5895 $dbo->qn('r.id_' . $figure) . ' = ' . (int) $id,
5896 $dbo->qn('r.published') . ' = 1',
5897 $dbo->qn('r.comment') . ' <> \'\'',
5898 ));
5899
5900 if ($config->getBool('revlangfilter'))
5901 {
5902 $q->where($dbo->qn('r.langtag') . ' = ' . $dbo->q(JFactory::getLanguage()->getTag()));
5903 }
5904
5905 /**
5906 * Sanitize ordering column.
5907 *
5908 * @since 1.7.10
5909 */
5910 $ordcol = in_array($ordering['by'], ['timestamp', 'rating']) ? $ordering['by'] : 'timestamp';
5911
5912 /**
5913 * Sanitize ordering mode.
5914 *
5915 * @since 1.7.9
5916 */
5917 $direction = strcasecmp((string) $ordering['mode'], 'asc') ? 'DESC' : 'ASC';
5918
5919 $q->order($dbo->qn('r.' . $ordcol) . ' ' . $direction);
5920
5921 $dbo->setQuery($q, $lim0, $lim);
5922 $result->rows = $dbo->loadObjectList();
5923
5924 if ($result->rows)
5925 {
5926 $dbo->setQuery('SELECT FOUND_ROWS();');
5927 $result->size = (int) $dbo->loadResult();
5928 }
5929
5930 /**
5931 * Always look for any reviews without comment.
5932 * Before 1.7 version, the votes were calculated
5933 * only in case the entity owned at least a review.
5934 *
5935 * @since 1.7
5936 */
5937 $q = $dbo->getQuery(true)
5938 ->select('COUNT(1)')
5939 ->from($dbo->qn('#__vikappointments_reviews'))
5940 ->where(array(
5941 $dbo->qn('id_' . $figure) . ' = ' . (int) $id,
5942 $dbo->qn('published') . ' = 1',
5943 ));
5944
5945 $dbo->setQuery($q);
5946 $result->votes = (int) $dbo->loadResult();
5947
5948 return $result;
5949 }
5950
5951 /**
5952 * Returns the links used to switch ordering.
5953 *
5954 * @param string $base The base URI.
5955 * @param string $by The current ordering column.
5956 * @param string $mode The current ordering direction.
5957 *
5958 * @return array An array containing the link details.
5959 *
5960 * @since 1.4
5961 */
5962 public static function getReviewsOrderingLinks($base, $by, $mode)
5963 {
5964 $columns = array(
5965 'timestamp' => 'DESC',
5966 'rating' => 'DESC',
5967 );
5968
5969 $app = \JFactory::getApplication();
5970
5971 $ordering = [
5972 'by' => $app->getUserState('vikappointments.reviews.order.column', '') ?: 'timestamp',
5973 'mode' => $app->getUserState('vikappointments.reviews.order.direction', '') ?: 'DESC',
5974 ];
5975
5976 if (empty($by))
5977 {
5978 $by = $ordering['by'];
5979 $mode = $ordering['mode'];
5980 }
5981
5982 /**
5983 * Sanitize ordering mode.
5984 *
5985 * @since 1.7.9
5986 */
5987 $mode = strcasecmp((string) $mode, 'asc') ? 'DESC' : 'ASC';
5988
5989 if (!array_key_exists($by, $columns))
5990 {
5991 $by = $ordering['by'];
5992 }
5993
5994 $links = array();
5995
5996 foreach ($columns as $col => $m)
5997 {
5998 $arr = array(
5999 'uri' => '',
6000 'active' => false,
6001 'mode' => '',
6002 'name' => JText::translate('VAPREVIEWORDERING' . strtoupper($col)),
6003 );
6004
6005 $l = "{$base}&revordby={$col}&revordmode=";
6006
6007 if ($by == $col)
6008 {
6009 $l .= $mode == 'ASC' ? 'DESC' : 'ASC';
6010 $arr['active'] = true;
6011 $arr['mode'] = $mode;
6012 }
6013 else
6014 {
6015 $l .= $m;
6016 }
6017
6018 $arr['uri'] = $l;
6019
6020 $links[] = $arr;
6021 }
6022
6023 $ordering['by'] = $by;
6024 $ordering['mode'] = $mode;
6025
6026 $app->getUserState('vikappointments.reviews.order.column', $ordering['by']);
6027 $app->getUserState('vikappointments.reviews.order.direction', $ordering['mode']);
6028
6029 return $links;
6030 }
6031
6032 /**
6033 * Helper method used to check if the current customer
6034 * is allowed to leave a review for the specified employee.
6035 *
6036 * @param integer $id_emp The ID of the employee.
6037 *
6038 * @return boolean True if the review can be left, otherwise false.
6039 *
6040 * @uses userCanLeaveReview()
6041 *
6042 * @since 1.4
6043 */
6044 public static function userCanLeaveEmployeeReview($id_emp)
6045 {
6046 if (!self::isEmployeesReviewsEnabled())
6047 {
6048 // reviews for employees are not enabled
6049 return false;
6050 }
6051
6052 // evaluate system criteria
6053 return self::userCanLeaveReview($id_emp, 'employee');
6054 }
6055
6056 /**
6057 * Helper method used to check if the current customer
6058 * is allowed to leave a review for the specified service.
6059 *
6060 * @param integer $id_ser The ID of the service.
6061 *
6062 * @return boolean True if the review can be left, otherwise false.
6063 *
6064 * @uses userCanLeaveReview()
6065 *
6066 * @since 1.4
6067 */
6068 public static function userCanLeaveServiceReview($id_ser)
6069 {
6070 if (!self::isServicesReviewsEnabled())
6071 {
6072 // reviews for services are not enabled
6073 return false;
6074 }
6075
6076 // evaluate system criteria
6077 return self::userCanLeaveReview($id_ser, 'service');
6078 }
6079
6080 /**
6081 * Helper method used to check if the current customer
6082 * is allowed to leave a review.
6083 *
6084 * @param integer $id The ID of the entity.
6085 * @param string $type The entity type (employee or service).
6086 *
6087 * @return boolean True if the review can be left, otherwise false.
6088 *
6089 * @since 1.6
6090 */
6091 protected static function userCanLeaveReview($id, $type)
6092 {
6093 $user = JFactory::getUser();
6094 $dispatcher = VAPFactory::getEventDispatcher();
6095
6096 /**
6097 * Trigger event to override the default system criteria used to
6098 * validate whether a review should be left or not.
6099 *
6100 * @param string $type The entity type (service or employee).
6101 * @param integer $id The entity ID for which the review should be left.
6102 * @param JUser $user The current user object.
6103 *
6104 * @return boolean True if the review should be left.
6105 *
6106 * @since 1.6
6107 */
6108 if ($dispatcher->is('onValidateLeaveReview', array($type, $id, $user)))
6109 {
6110 // ignore the default system criteria as the plugin overrided it
6111 // to allow the user to leave the review
6112 return true;
6113 }
6114
6115 if ($user->guest)
6116 {
6117 // user not logged in
6118 return false;
6119 }
6120
6121 $dbo = JFactory::getDbo();
6122
6123 $q = $dbo->getQuery(true)
6124 ->select(1)
6125 ->from($dbo->qn('#__vikappointments_reviews'))
6126 ->where(array(
6127 $dbo->qn('id_' . $type) . ' = ' . (int) $id,
6128 $dbo->qn('jid') . ' = ' . $user->id,
6129 ));
6130
6131 $dbo->setQuery($q, 0, 1);
6132 $dbo->execute();
6133
6134 if ($dbo->getNumRows())
6135 {
6136 // user already wrote a review for this entity
6137 return false;
6138 }
6139
6140 $q = $dbo->getQuery(true)
6141 ->select(1)
6142 ->from($dbo->qn('#__vikappointments_reservation', 'r'))
6143 ->leftjoin($dbo->qn('#__vikappointments_users', 'u') . ' ON ' . $dbo->qn('r.id_user') . ' = ' . $dbo->qn('u.id'))
6144 ->where(array(
6145 $dbo->qn('r.id_' . $type) . ' = ' . (int) $id,
6146 $dbo->qn('u.jid') . ' = ' . $user->id,
6147 $dbo->qn('r.checkin_ts') . ' < ' . $dbo->q(JFactory::getDate()->toSql()),
6148 ));
6149
6150 // get approved statuses
6151 $approved = JHtml::fetch('vaphtml.status.find', 'code', array('appointments' => 1, 'approved' => 1));
6152
6153 if ($approved)
6154 {
6155 // filter by approved status
6156 $q->where($dbo->qn('r.status') . ' IN (' . implode(',', array_map(array($dbo, 'q'), $approved)) . ')');
6157 }
6158
6159 $dbo->setQuery($q, 0, 1);
6160 $dbo->execute();
6161
6162 if ($dbo->getNumRows() == 0)
6163 {
6164 // user never placed an order for this entity
6165 return false;
6166 }
6167
6168 // the user can leave the review
6169 return true;
6170 }
6171
6172 /////////////////////////////////////////////
6173 /////////////// SUBSCRIPTIONS ///////////////
6174 /////////////////////////////////////////////
6175
6176 /**
6177 * Checks if there is at least a published subscription.
6178 *
6179 * @return boolean True if any, false otherwise.
6180 *
6181 * @since 1.5
6182 * @deprecated 1.8 Use VAPSubscriptions::has() instead.
6183 */
6184 public static function isSubscriptions()
6185 {
6186 // Always search for employees subscriptions because when this method was
6187 // written the subscriptions for the customers were not exist.
6188 VAPLoader::import('libraries.models.subscriptions');
6189 return VAPSubscriptions::has($group = 1);
6190 }
6191
6192 /**
6193 * Returns the trial subscription (if any).
6194 *
6195 * @param boolean $translate True to translate the subscriptions, false otherwise.
6196 *
6197 * @return array The trial subscription. False if it doesn't exist.
6198 *
6199 * @since 1.5
6200 * @deprecated 1.8 Use VAPSubscriptions::getTrial() instead.
6201 */
6202 public static function getTrialSubscription($translate = true)
6203 {
6204 // Always search for employees subscriptions because when this method was
6205 // written the subscriptions for the customers were not exist.
6206 VAPLoader::import('libraries.models.subscriptions');
6207 return VAPSubscriptions::getTrial($group = 1, $translate);
6208 }
6209
6210 /**
6211 * Returns a list of active subscriptions.
6212 *
6213 * @param boolean $translate True to translate the subscriptions, false otherwise.
6214 *
6215 * @return array The subscriptions list. False if the list is empty.
6216 *
6217 * @since 1.5
6218 * @deprecated 1.8 Use VAPSubscriptions::getList() instead.
6219 */
6220 public static function getSubscriptions($trial = false, $translate = true)
6221 {
6222 // Always search for employees subscriptions because when this method was
6223 // written the subscriptions for the customers were not exist.
6224 VAPLoader::import('libraries.models.subscriptions');
6225 return VAPSubscriptions::getList($group = 1, $trial, $translate);
6226 }
6227
6228 /**
6229 * Returns the details of the given subscription.
6230 *
6231 * @param integer $id The subscription ID.
6232 * @param boolean $strict True to get the subscription only if it is published.
6233 * @param boolean $translate True to translate the subscriptions, false otherwise.
6234 *
6235 * @return array The trial subscription. False if it doesn't exist.
6236 *
6237 * @since 1.6
6238 * @deprecated 1.8 Use VAPSubscriptions::get() instead.
6239 */
6240 public static function getSubscription($id, $strict = true, $translate = true)
6241 {
6242 // Always search for employees subscriptions because when this method was
6243 // written the subscriptions for the customers were not exist.
6244 VAPLoader::import('libraries.models.subscriptions');
6245 return VAPSubscriptions::get($id, $group = 1, $strict, $translate);
6246 }
6247
6248 /**
6249 * Returns a list of subscriptions matching the given query.
6250 *
6251 * @param array $where An associative array containing the query terms.
6252 * @param integer $lim The number of records to retrieve. Null to ignore this value.
6253 * @param boolean $translate True to translate the subscriptions, false otherwise.
6254 *
6255 * @return array The subscriptions list. False if the list is empty. The associative array
6256 * of the subscription in case $lim is equals to 1.
6257 *
6258 * @since 1.6
6259 * @deprecated 1.8 Use VAPSubscriptions::search() instead.
6260 */
6261 public static function _getSubscriptions(array $where = array(), $lim = null, $translate = false)
6262 {
6263 // Always search for employees subscriptions because when this method was
6264 // written the subscriptions for the customers were not exist.
6265 $where['group'] = 1;
6266
6267 VAPLoader::import('libraries.models.subscriptions');
6268 return VAPSubscriptions::search($where, $lim, $translate);
6269 }
6270
6271 /**
6272 * Method used to extend the subscription lifetime of the given employee.
6273 *
6274 * @param array $subscr The subscription purchased.
6275 * @param array $employee The employee details.
6276 *
6277 * @return void
6278 *
6279 * @since 1.5
6280 * @deprecated 1.8 Use JModelSubscrorder::extendEmployee() instead.
6281 */
6282 public static function applyAdditionalSubscription($subscr, $employee)
6283 {
6284 JModelVAP::getInstance('subscrorder')->extendEmployee($employee, $subscr);
6285 }
6286
6287 /////////////////////////////////////////////
6288 ///////////////// CUSTOMERS /////////////////
6289 /////////////////////////////////////////////
6290
6291 /**
6292 * Returns the details of the given customer.
6293 *
6294 * @param mixed $id The customer ID. If not specified,
6295 * the customer assigned to the current
6296 * user will be retrieved, if any.
6297 *
6298 * @return mixed The customer object if exists, NULL otherwise
6299 *
6300 * @since 1.7
6301 */
6302 public static function getCustomer($id = null)
6303 {
6304 // import customer object handler
6305 VAPLoader::import('libraries.models.customer');
6306
6307 try
6308 {
6309 // return customer details
6310 return VAPCustomer::getInstance($id);
6311 }
6312 catch (Exception $e)
6313 {
6314 // catch any errors (probably user not found)
6315 }
6316
6317 // unable to fetch customer data, return null
6318 return null;
6319 }
6320
6321 /**
6322 * Returns the timezone of the currently logged-in user.
6323 *
6324 * @return DateTimeZone
6325 *
6326 * @since 1.7
6327 */
6328 public static function getUserTimezone()
6329 {
6330 static $tz = null;
6331
6332 // fetch timezone only once
6333 if (!$tz)
6334 {
6335 $app = JFactory::getApplication();
6336
6337 /**
6338 * Do not load the timezone cached in the cookies in case
6339 * the multi-timezone is disabled.
6340 *
6341 * @since 1.7.5
6342 */
6343 if (VAPFactory::getConfig()->getBool('multitimezone'))
6344 {
6345 // first of all, extract timezone from user cookie
6346 $tz = $app->input->cookie->getString('vikappointments_user_timezone', null);
6347 }
6348
6349 // ignore cookie in case we are accessing from the back-end
6350 if (!$tz || $app->isClient('administrator'))
6351 {
6352 $user = JFactory::getUser();
6353
6354 if (!$user->guest)
6355 {
6356 // use current user timezone
6357 $tz = $user->getTimezone();
6358 }
6359 else
6360 {
6361 // use the default system timezone
6362 $tz = $app->get('offset', 'UTC');
6363 }
6364 }
6365
6366 if (!$tz instanceof DateTimeZone)
6367 {
6368 $tz = new DateTimeZone($tz);
6369 }
6370 }
6371
6372 return $tz;
6373 }
6374
6375 /////////////////////////////////////////////
6376 ////////////////// ORDERS ///////////////////
6377 /////////////////////////////////////////////
6378
6379 /**
6380 * Returns the order details of the given ID.
6381 *
6382 * @param integer $order_id The order number (ID).
6383 * @param string $order_key The order key (sid). If not provided,
6384 * this field won't used while fetching the records.
6385 * @param string $langtag The translation language. If not provided
6386 * it will be used the default one.
6387 *
6388 * @return mixed A list of orders on success, otherwise false.
6389 *
6390 * @deprecated 1.8 Use VAPOrderFactory::getAppointments() instead.
6391 */
6392 public static function fetchOrderDetails($order_id, $order_key = null, $langtag = null)
6393 {
6394 try
6395 {
6396 VAPLoader::import('libraries.order.factory');
6397 return VAPOrderFactory::getAppointments($order_id, $langtag, array('sid' => $order_key));
6398 }
6399 catch (Exception $e)
6400 {
6401
6402 }
6403
6404 return false;
6405 }
6406
6407 /**
6408 * Countes the number of services within the purchased packages that can be still used.
6409 *
6410 * @param integer $id_ser The service ID.
6411 * @param integer $id_user The user ID. If not provided,
6412 * the current user will be retrieved.
6413 *
6414 * @return integer The remaining number of services.
6415 *
6416 * @deprecated 1.8 Use VikAppointmentsModelPackorder::countRemaining() instead.
6417 */
6418 public static function countRemainingServicePackages($id_ser, $id_user = null)
6419 {
6420 return JModelVAP::getInstance('packorder')->countRemaining($id_ser, $id_user);
6421 }
6422
6423 /**
6424 * Redeems the remaining packages for the services contained within the cart.
6425 *
6426 * @param mixed $cart The cart instance.
6427 *
6428 * @return boolean True in case at least a package was redeemed, false otherwise.
6429 */
6430 public static function usePackagesForServicesInCart($cart)
6431 {
6432 // get customer details of the logged-in user
6433 $customer = static::getCustomer();
6434
6435 if (!$customer)
6436 {
6437 // guest user, nothing to redeem
6438 return false;
6439 }
6440
6441 $redeemed = false;
6442
6443 $lookup = array();
6444
6445 // iterate items in cart
6446 foreach ($cart->getItemsList() as $item)
6447 {
6448 // reset discounted flag
6449 $item->setDiscounted(0);
6450
6451 if (!array_key_exists($item->getID(), $lookup))
6452 {
6453 // load remaining services to redeem
6454 $lookup[$item->getID()] = self::countRemainingServicePackages($item->getServiceID());
6455 }
6456
6457 // try to redeem a package for each participant
6458 if ($lookup[$item->getID()] - $item->getPeople() >= 0)
6459 {
6460 // decrease lookup
6461 $lookup[$item->getID()] -= $item->getPeople();
6462 // mark product as discounted
6463 $item->setDiscounted(1);
6464
6465 $redeemed = true;
6466 }
6467 else
6468 {
6469 // unset price in case of a valid subscription for the booked service
6470 if ($customer->isSubscribed($item->getServiceID(), $item->getCheckinDate()))
6471 {
6472 $item->setPrice(0);
6473 }
6474 }
6475 }
6476
6477 if ($redeemed)
6478 {
6479 // update cart on success
6480 $cart->store();
6481 }
6482
6483 return $redeemed;
6484 }
6485
6486 /**
6487 * Checks whether the customer is compliant with the mandatory purchase setting,
6488 * by ensuring that the total number of appointments in cart is equals or lower
6489 * than the total number of packages that can be redeemed.
6490 *
6491 * In case the mentioned setting is disabled, this method will always return true.
6492 *
6493 * @param mixed $cart The cart instance.
6494 *
6495 * @return boolean True if compliant, false otherwise.
6496 *
6497 * @since 1.7
6498 */
6499 public static function isCompliantWithMandatoryPackage($cart)
6500 {
6501 if (VAPFactory::getConfig()->getBool('packsmandatory') == false)
6502 {
6503 // feature disabled, doesn't need to go ahead
6504 return true;
6505 }
6506
6507 if (JFactory::getUser()->guest)
6508 {
6509 // guest user, cannot be compliant
6510 return false;
6511 }
6512
6513 $dispatcher = VAPFactory::getEventDispatcher();
6514
6515 $lookup = array();
6516
6517 // iterate items in cart
6518 foreach ($cart->getItemsList() as $item)
6519 {
6520 /**
6521 * Checks whether a cart item (service) is compliant with the mandatory
6522 * package setting. Triggers only when globally enabled.
6523 *
6524 * @param VAPCartItem $item The cart item to validate.
6525 * @param VAPCart $cart The cart instance.
6526 *
6527 * @return boolean True to ignore the validation, false in case
6528 * the item is not compliant, null to fallback
6529 * to the default validation.
6530 *
6531 * @since 1.7.4
6532 */
6533 $valid = $dispatcher->trigger('onValidateMandatoryPackageCompliance', array($item, $cart));
6534
6535 if (in_array(true, $valid, true))
6536 {
6537 // ignore item validation
6538 continue;
6539 }
6540 else if (in_array(false, $valid, true))
6541 {
6542 // item not compliant
6543 return false;
6544 }
6545
6546 if (!array_key_exists($item->getID(), $lookup))
6547 {
6548 // load remaining services to redeem
6549 $lookup[$item->getID()] = self::countRemainingServicePackages($item->getServiceID());
6550 }
6551
6552 // try to redeem a package for each participant
6553 if ($lookup[$item->getID()] - $item->getPeople() < 0)
6554 {
6555 // not enough packages to redeem, cannot book the appointment
6556 return false;
6557 }
6558
6559 // decrease lookup
6560 $lookup[$item->getID()] -= $item->getPeople();
6561 }
6562
6563 // enough packages to redeem, can book the appointment
6564 return true;
6565 }
6566
6567 /**
6568 * Registers all the packages that have been used to purchase a service.
6569 *
6570 * @param array $order_details The order details list.
6571 * @param boolean $increase True to increase the number of used packages,
6572 * false to free them.
6573 *
6574 * @return integer The number of packages used.
6575 *
6576 * @deprecated 1.8 Use VikAppointmentsModelPackorder::usePackages() instead.
6577 */
6578 public static function registerPackagesUsed($order_details, $increase = true)
6579 {
6580 if (is_array($order_details))
6581 {
6582 // order details retrieved with fetchOrderDetails
6583 $order_details = $order_details[0]['id'];
6584 }
6585
6586 return JModelVAP::getInstance('packorder')->usePackages($order_details, $increase);
6587 }
6588
6589 /////////////////////////////////////////////
6590 /////////////// ADMIN E-MAIL ////////////////
6591 /////////////////////////////////////////////
6592
6593 /**
6594 * Loads the admin e-mail template that should be parsed.
6595 *
6596 * @param array $orders The orders list.
6597 *
6598 * @return string The e-mail template.
6599 *
6600 * @deprecated 1.8 Without replacement.
6601 */
6602 public static function loadAdminEmailTemplate(array $orders)
6603 {
6604 ob_start();
6605 include VAPHELPERS . DIRECTORY_SEPARATOR . 'mail_tmpls' . DIRECTORY_SEPARATOR . VAPFactory::getConfig()->get('adminmailtmpl');
6606 $content = ob_get_contents();
6607 ob_end_clean();
6608
6609 return $content;
6610 }
6611
6612 /**
6613 * Sends the notification e-mail to the administrator(s)
6614 * and related employee(s).
6615 *
6616 * @param array $order_details The orders that should be notified.
6617 *
6618 * @return void
6619 */
6620 public static function sendAdminEmail($order_details)
6621 {
6622 if (!$order_details)
6623 {
6624 return;
6625 }
6626
6627 self::loadLanguage(self::getDefaultLanguage('site'));
6628
6629 $send_when = self::getSendMailWhen();
6630 $admin_mail_list = self::getAdminMailList();
6631 $sendermail = self::getSenderMail();
6632 $adminname = VAPFactory::getConfig()->get('agencyname');
6633
6634 $subject = JText::sprintf('VAPADMINEMAILSUBJECT', $adminname);
6635
6636 /**
6637 * Parse e-mail subject to replace tags with the
6638 * related order details.
6639 *
6640 * @since 1.6.6
6641 */
6642 static::parseEmailSubject($subject, $order_details);
6643
6644 $admin_tmpl = self::loadAdminEmailTemplate($order_details);
6645 $html_mess = self::parseAdminEmailTemplate($admin_tmpl, $order_details);
6646
6647 $emp_details = self::filterOrdersByEmployee($order_details);
6648
6649 $ics_prop = self::getAttachmentPropertiesICS();
6650 $csv_prop = self::getAttachmentPropertiesCSV();
6651
6652 $vik = VAPApplication::getInstance();
6653
6654 // CUSTOM FIELDS ATTACHMENTS
6655 $order_details[0]['uploads'] = json_decode($order_details[0]['uploads']);
6656 $custom_f_attach = self::includeMailAttachments($order_details);
6657
6658 if ($send_when['admin'] != 0 && ($send_when['admin'] == 2 || $order_details[0]['status'] == 'CONFIRMED'))
6659 {
6660 $admin_attachments = array();
6661
6662 // ADMIN ICS GENERATOR //
6663 $ics_file_path = "";
6664 if ($ics_prop['admin'])
6665 {
6666 $ics_file_path = self::composeFileICS($order_details[0]['id'], true, -1);
6667
6668 if (!empty($ics_file_path))
6669 {
6670 $admin_attachments[] = $ics_file_path;
6671 }
6672 }
6673
6674 // ADMIN CSV GENERATOR //
6675 $csv_file_path = "";
6676 if ($csv_prop['admin'])
6677 {
6678 $csv_file_path = self::composeFileCSV($order_details[0]['id'], true, -1);
6679 if (!empty($csv_file_path))
6680 {
6681 $admin_attachments[] = $csv_file_path;
6682 }
6683 }
6684
6685 $admin_attachments = array_merge($admin_attachments, $custom_f_attach);
6686
6687 foreach ($admin_mail_list as $_m)
6688 {
6689 $vik->sendMail($sendermail, $adminname, $_m, $sendermail, $subject, $html_mess, $admin_attachments, true);
6690 }
6691
6692 if (!empty($ics_file_path) && file_exists($ics_file_path))
6693 {
6694 unlink($ics_file_path);
6695 }
6696
6697 if (!empty($csv_file_path) && file_exists($csv_file_path))
6698 {
6699 unlink($csv_file_path);
6700 }
6701 }
6702
6703 if ($send_when['employee'] != 0 && ($send_when['employee'] == 2 || $order_details[0]['status'] == 'CONFIRMED'))
6704 {
6705 foreach ($emp_details as $emp_mail => $emp_order_details)
6706 {
6707 $emp_attachments = array();
6708
6709 // EMPLOYEE ICS GENERATOR //
6710 $ics_file_path = "";
6711 if ($ics_prop['employee'])
6712 {
6713 $ics_file_path = self::composeFileICS($order_details[0]['id'], true, $emp_order_details[0]['id_employee']);
6714 if (!empty($ics_file_path))
6715 {
6716 $emp_attachments[] = $ics_file_path;
6717 }
6718 }
6719
6720 // EMPLOYEE CSV GENERATOR //
6721 $csv_file_path = "";
6722 if ($csv_prop['employee'])
6723 {
6724 $csv_file_path = self::composeFileCSV($order_details[0]['id'], true, $emp_order_details[0]['id_employee']);
6725 if (!empty($csv_file_path))
6726 {
6727 $emp_attachments[] = $csv_file_path;
6728 }
6729 }
6730
6731 $emp_attachments = array_merge($emp_attachments, $custom_f_attach);
6732
6733 /**
6734 * Reload employee e-mail template using the orders related to the specified employee.
6735 *
6736 * @since 1.6
6737 */
6738 $emp_tmpl = self::loadEmployeeEmailTemplate($emp_order_details);
6739 $_html = self::parseEmployeesEmailTemplate($emp_tmpl, $emp_order_details);
6740
6741 $vik->sendMail($sendermail, $adminname, $emp_mail, $admin_mail_list[0], $subject, $_html, $emp_attachments, true);
6742
6743 if (!empty($ics_file_path) && file_exists($ics_file_path))
6744 {
6745 unlink($ics_file_path);
6746 }
6747
6748 if (!empty($csv_file_path) && file_exists($csv_file_path))
6749 {
6750 unlink($csv_file_path);
6751 }
6752 }
6753 }
6754
6755 self::destroyMailAttachments($custom_f_attach);
6756 }
6757
6758 /**
6759 * Method used to parse the e-mail template for the administrator(s).
6760 *
6761 * @param string $tmpl The template string to parse.
6762 * @param array $order_details The orders list.
6763 *
6764 * @return string The parsed template.
6765 *
6766 * @deprecated 1.8 Without replacement.
6767 */
6768 public static function parseAdminEmailTemplate($tmpl, $order_details)
6769 {
6770 // parse coupon string
6771
6772 if (!empty($order_details[0]['coupon_str']))
6773 {
6774 list($code, $pt, $value) = explode(';;', $order_details[0]['coupon_str']);
6775 $coupon_str = $code . " : " . ($pt == 1 ? $value . '%' : self::printPriceCurrencySymb($value));
6776 }
6777 else
6778 {
6779 $coupon_str = JText::translate('VAPADMINEMAILNOCOUPON');
6780 }
6781
6782 // parse payment name
6783
6784 $payment_name = !empty($order_details[0]['payment_name']) ? $order_details[0]['payment_name'] : JText::translate('VAPADMINEMAILNOPAYMENT');
6785
6786 // parse order total cost
6787
6788 $order_total = self::printPriceCurrencySymb($order_details[0]['total_cost'] + $order_details[0]['payment_charge']);
6789
6790 // fetch appointment details
6791
6792 /**
6793 * @deprecated 1.8 the appointment details are parsed within the e-mail template
6794 */
6795 $appointment_details = "";
6796 for ($i = ($order_details[0]['id_service'] == -1 ? 1 : 0); $i < count($order_details); $i++)
6797 {
6798 $row = $order_details[$i];
6799
6800 $appointment_details .= '<div class="appointment">';
6801 $appointment_details .= '<div class="content ' . ($row['total_cost'] > 0 || count($row['options']) ? '' : 'fill-bottom') . '">';
6802 $appointment_details .= $row['sname'] . ' - ' . $row['ename'] . '<br />';
6803 $appointment_details .= $row['formatted_checkin'] . ' - ' . $row['formatted_duration'];
6804 $appointment_details .= '</div>';
6805
6806 if (count($row['options']))
6807 {
6808 $appointment_details .= '<div class="options-list'.($row['total_cost'] > 0 ? '' : ' fill-bottom').'">';
6809
6810 foreach ($row['options'] as $opt)
6811 {
6812 $appointment_details .= '<div class="option">';
6813 $appointment_details .= '<div class="name">- ' . $opt['full_name'] . '</div>';
6814 $appointment_details .= '<div class="quantity">' . $opt['formatted_quantity'] . '</div>';
6815 if ($opt['price'] != 0)
6816 {
6817 $appointment_details .= '<div class="price">' . $opt['formatted_price'] . '</div>';
6818 }
6819 $appointment_details .= '</div>';
6820 }
6821 $appointment_details .= '</div>';
6822 }
6823
6824 if ($row['total_cost'] > 0)
6825 {
6826 $appointment_details .= '<div class="cost"><span>' . $row['formatted_total'] . '</span></div>';
6827 }
6828
6829 $appointment_details .= '</div>';
6830 }
6831
6832 // customer details
6833
6834 $custom_fields = json_decode($order_details[0]['custom_f'], true);
6835
6836 /**
6837 * @deprecated 1.8 the customer details are parsed within the e-mail template
6838 */
6839 $customer_details = "";
6840 foreach ($custom_fields as $kc => $vc)
6841 {
6842 $customer_details .= '<div class="info">';
6843 $customer_details .= '<div class="label">'.JText::translate($kc).':</div>';
6844 $customer_details .= '<div class="value">'.$vc.'</div>';
6845 $customer_details .= '</div>';
6846 }
6847
6848 // joomla user details
6849 $user_details = '';
6850 if (strlen($order_details[0]['user_email']) > 0)
6851 {
6852 /**
6853 * @deprecated 1.8 the joomla user details should be parsed within the e-mail template (if needed)
6854 */
6855
6856 $user_details = '<div class="separator"> </div>
6857 <div class="customer-details-wrapper">
6858 <div class="title">'.JText::translate('VAPUSERDETAILS').'</div>
6859 <div class="customer-details">
6860 <div class="info">
6861 <div class="label">'.JText::translate('VAPREGFULLNAME').':</div>
6862 <div class="value">'.$order_details[0]['user_name'].'</div>
6863 </div>
6864 <div class="info">
6865 <div class="label">'.JText::translate('VAPREGUNAME').':</div>
6866 <div class="value">'.$order_details[0]['user_uname'].'</div>
6867 </div>
6868 <div class="info">
6869 <div class="label">'.JText::translate('VAPREGEMAIL').':</div>
6870 <div class="value">'.$order_details[0]['user_email'].'</div>
6871 </div>
6872 </div>
6873 </div>';
6874 }
6875
6876 $vik = VAPApplication::getInstance();
6877
6878 // order link
6879
6880 $order_link_href = "index.php?option=com_vikappointments&view=order&ordnum={$order_details[0]['id']}&ordkey={$order_details[0]['sid']}";
6881 $order_link_href = $vik->routeForExternalUse($order_link_href);
6882
6883 $confirmation_link = "";
6884 if ($order_details[0]['status'] == 'PENDING')
6885 {
6886 $confirmation_link = "index.php?option=com_vikappointments&task=confirmord&oid={$order_details[0]['id']}&conf_key={$order_details[0]['conf_key']}";
6887 $confirmation_link = $vik->routeForExternalUse($confirmation_link);
6888
6889 // $confirmation_link .= '<div class="order-link">';
6890 // $confirmation_link .= '<div class="title">'.JText::translate('VAPCONFIRMATIONLINK').'</div>';
6891 // $confirmation_link .= '<div class="content">';
6892 // $confirmation_link .= '<a href="'.$confirmation_link_href.'">'.$confirmation_link_href.'</a>';
6893 // $confirmation_link .= '</div>';
6894 // $confirmation_link .= '</div>';
6895 }
6896
6897 // logo
6898
6899 $logo_name = VAPFactory::getConfig()->get('companylogo');
6900 $agency_name = VAPFactory::getConfig()->get('agencyname');
6901
6902 $logo_str = "";
6903 if (!empty($logo_name) && file_exists(VAPMEDIA . DIRECTORY_SEPARATOR . $logo_name))
6904 {
6905 $logo_str = '<img src="' . VAPMEDIA_URI . $logo_name . '" alt="' . htmlspecialchars($agency_name) . '" />';
6906 }
6907
6908 // order status color
6909
6910 switch ($order_details[0]['status'])
6911 {
6912 case 'CONFIRMED':
6913 $order_status_color = '#006600';
6914 break;
6915
6916 case 'PENDING':
6917 $order_status_color = '#D9A300';
6918 break;
6919
6920 case 'REMOVED':
6921 $order_status_color = '#B20000';
6922 break;
6923
6924 case 'CANCELED':
6925 $order_status_color = '#F01B17';
6926 break;
6927
6928 default:
6929 $order_status_color = 'inherit';
6930 }
6931
6932 // replace tags from template
6933
6934 $tmpl = str_replace('{company_name}' , $agency_name , $tmpl);
6935 $tmpl = str_replace('{order_number}' , $order_details[0]['id'] , $tmpl);
6936 $tmpl = str_replace('{order_key}' , $order_details[0]['sid'] , $tmpl);
6937 $tmpl = str_replace('{order_status_class}' , strtolower($order_details[0]['status']) , $tmpl);
6938 $tmpl = str_replace('{order_status}' , JText::translate('VAPSTATUS' . $order_details[0]['status']) , $tmpl);
6939 $tmpl = str_replace('{order_status_color}' , $order_status_color , $tmpl);
6940 $tmpl = str_replace('{order_payment}' , $payment_name , $tmpl);
6941 $tmpl = str_replace('{order_coupon_code}' , $coupon_str , $tmpl);
6942 $tmpl = str_replace('{order_total_cost}' , $order_total , $tmpl);
6943 $tmpl = str_replace('{order_link}' , $order_link_href , $tmpl);
6944 $tmpl = str_replace('{confirmation_link}' , $confirmation_link , $tmpl);
6945 $tmpl = str_replace('{logo}' , $logo_str , $tmpl);
6946
6947 /**
6948 * @deprecated 1.8
6949 */
6950 $tmpl = str_replace('{appointment_details}' , $appointment_details , $tmpl);
6951 $tmpl = str_replace('{customer_details}' , $customer_details , $tmpl);
6952 $tmpl = str_replace('{user_details}' , $user_details , $tmpl);
6953
6954 return $tmpl;
6955 }
6956
6957 /////////////////////////////////////////////
6958 ///////////// EMPLOYEE E-MAIL ///////////////
6959 /////////////////////////////////////////////
6960
6961 /**
6962 * Filters the orders (in case of shop enabled) by employee.
6963 * The method will return an associative key built as follows:
6964 * - the key is the e-mail of the employee;
6965 * - the value is the list of all the related orders.
6966 *
6967 * @param array $order_details The orders to filter.
6968 *
6969 * @return array The resulting list.
6970 */
6971 public static function filterOrdersByEmployee($order_details)
6972 {
6973 $arr = array();
6974
6975 for ($i = ($order_details[0]['id_service'] == -1 ? 1 : 0); $i < count($order_details); $i++)
6976 {
6977 $row = $order_details[$i];
6978
6979 if (!empty($row['empmail']))
6980 {
6981 if (empty($arr[$row['empmail']]))
6982 {
6983 $arr[$row['empmail']] = array();
6984
6985 if ($i != 0)
6986 {
6987 // push always the parent order
6988 $arr[$row['empmail']][] = $order_details[0];
6989 // unset total cost
6990 $arr[$row['empmail']][0]['total_cost'] = 0.0;
6991 }
6992 }
6993
6994 $arr[$row['empmail']][] = $row;
6995
6996 if ($i != 0)
6997 {
6998 // recalculate the sum of the related orders
6999 $arr[$row['empmail']][0]['total_cost'] += $row['total_cost'];
7000 }
7001 }
7002 }
7003
7004 return $arr;
7005 }
7006
7007 /**
7008 * Loads the employee e-mail template that should be parsed.
7009 *
7010 * @param array $orders The orders list.
7011 *
7012 * @return string The e-mail template.
7013 *
7014 * @deprecated 1.8 Without replacement.
7015 */
7016 public static function loadEmployeeEmailTemplate(array $orders)
7017 {
7018 ob_start();
7019 include VAPHELPERS . DIRECTORY_SEPARATOR . 'mail_tmpls' . DIRECTORY_SEPARATOR . VAPFactory::getConfig()->get('empmailtmpl');
7020 $content = ob_get_contents();
7021 ob_end_clean();
7022
7023 return $content;
7024 }
7025
7026 /**
7027 * Method used to parse the e-mail template for the employee(s).
7028 *
7029 * @param string $tmpl The template string to parse.
7030 * @param array $order_details The orders list.
7031 *
7032 * @return string The parsed template.
7033 *
7034 * @deprecated 1.8 Without replacement.
7035 */
7036 public static function parseEmployeesEmailTemplate($tmpl, $order_details)
7037 {
7038 // parse coupon string
7039
7040 if (!empty($order_details[0]['coupon_str']))
7041 {
7042 list($code, $pt, $value) = explode(';;', $order_details[0]['coupon_str']);
7043 $coupon_str = $code . " : " . ($pt == 1 ? $value . '%' : self::printPriceCurrencySymb($value));
7044 }
7045 else
7046 {
7047 $coupon_str = JText::translate('VAPADMINEMAILNOCOUPON');
7048 }
7049
7050 // parse payment name
7051
7052 $payment_name = !empty($order_details[0]['payment_name']) ? $order_details[0]['payment_name'] : JText::translate('VAPADMINEMAILNOPAYMENT');
7053
7054 // parse order total cost
7055
7056 $order_total = self::printPriceCurrencySymb($order_details[0]['total_cost'] + $order_details[0]['payment_charge']);
7057
7058 // fetch appointment details
7059
7060 /**
7061 * @deprecated 1.8 the appointment details are parsed within the e-mail template
7062 */
7063 $appointment_details = "";
7064 for ($i = ($order_details[0]['id_service'] == -1 ? 1 : 0); $i < count($order_details); $i++)
7065 {
7066 $row = $order_details[$i];
7067
7068 $appointment_details .= '<div class="appointment">';
7069 $appointment_details .= '<div class="content ' . ($row['total_cost'] > 0 || count($row['options']) ? '' : 'fill-bottom') . '">';
7070 $appointment_details .= $row['id'] . '-' . $row['sid'] . '<br />';
7071 $appointment_details .= $row['sname'] . '<br />';
7072 $appointment_details .= $row['formatted_checkin'] . ' - ' . $row['formatted_duration'];
7073 $appointment_details .= '</div>';
7074
7075 if (count($row['options']))
7076 {
7077 $appointment_details .= '<div class="options-list'.($row['total_cost'] > 0 ? '' : ' fill-bottom').'">';
7078
7079 foreach ($row['options'] as $opt)
7080 {
7081 $appointment_details .= '<div class="option">';
7082 $appointment_details .= '<div class="name">- ' . $opt['full_name'] . '</div>';
7083 $appointment_details .= '<div class="quantity">' . $opt['formatted_quantity'] . '</div>';
7084 if ($opt['price'] != 0)
7085 {
7086 $appointment_details .= '<div class="price">' . $opt['formatted_price'] . '</div>';
7087 }
7088 $appointment_details .= '</div>';
7089 }
7090 $appointment_details .= '</div>';
7091 }
7092
7093 if ($row['total_cost'] > 0)
7094 {
7095 $appointment_details .= '<div class="cost"><span>' . $row['formatted_total'] . '</span></div>';
7096 }
7097
7098 $appointment_details .= '</div>';
7099 }
7100
7101 // customer details
7102
7103 $custom_fields = json_decode($order_details[0]['custom_f'], true);
7104
7105 /**
7106 * @deprecated 1.8 the customer details are parsed within the e-mail template
7107 */
7108 $customer_details = "";
7109 foreach ($custom_fields as $kc => $vc)
7110 {
7111 $customer_details .= '<div class="info">';
7112 $customer_details .= '<div class="label">'.JText::translate($kc).':</div>';
7113 $customer_details .= '<div class="value">'.$vc.'</div>';
7114 $customer_details .= '</div>';
7115 }
7116
7117 // joomla user details
7118 $user_details = '';
7119 if (strlen($order_details[0]['user_email']) > 0)
7120 {
7121 /**
7122 * @deprecated 1.8 the joomla user details should be parsed within the e-mail template (if needed)
7123 */
7124
7125 $user_details = '<div class="separator"> </div>
7126 <div class="customer-details-wrapper">
7127 <div class="title">'.JText::translate('VAPUSERDETAILS').'</div>
7128 <div class="customer-details">
7129 <div class="info">
7130 <div class="label">'.JText::translate('VAPREGFULLNAME').':</div>
7131 <div class="value">'.$order_details[0]['user_name'].'</div>
7132 </div>
7133 <div class="info">
7134 <div class="label">'.JText::translate('VAPREGUNAME').':</div>
7135 <div class="value">'.$order_details[0]['user_uname'].'</div>
7136 </div>
7137 <div class="info">
7138 <div class="label">'.JText::translate('VAPREGEMAIL').':</div>
7139 <div class="value">'.$order_details[0]['user_email'].'</div>
7140 </div>
7141 </div>
7142 </div>';
7143 }
7144
7145 $vik = VAPApplication::getInstance();
7146
7147 // order link
7148
7149 $order_link_href = "index.php?option=com_vikappointments&view=order&ordnum={$order_details[0]['id']}&ordkey={$order_details[0]['sid']}";
7150 $order_link_href = $vik->routeForExternalUse($order_link_href);
7151
7152 $confirmation_link = "";
7153 if ($order_details[0]['status'] == 'PENDING' && count($order_details) == 1)
7154 {
7155 $confirmation_link = "index.php?option=com_vikappointments&task=confirmord&oid={$order_details[0]['id']}&conf_key={$order_details[0]['conf_key']}";
7156 $confirmation_link = $vik->routeForExternalUse($confirmation_link);
7157
7158 // $confirmation_link .= '<div class="order-link">';
7159 // $confirmation_link .= '<div class="title">'.JText::translate('VAPCONFIRMATIONLINK').'</div>';
7160 // $confirmation_link .= '<div class="content">';
7161 // $confirmation_link .= '<a href="'.$confirmation_link_href.'">'.$confirmation_link_href.'</a>';
7162 // $confirmation_link .= '</div>';
7163 // $confirmation_link .= '</div>';
7164 }
7165
7166 // logo
7167
7168 $logo_name = VAPFactory::getConfig()->get('companylogo');
7169 $agency_name = VAPFactory::getConfig()->get('agencyname');
7170
7171 $logo_str = "";
7172 if (!empty($logo_name) && file_exists(VAPMEDIA . DIRECTORY_SEPARATOR . $logo_name))
7173 {
7174 $logo_str = '<img src="' . VAPMEDIA_URI . $logo_name . '" alt="' . htmlspecialchars($agency_name) . '" />';
7175 }
7176
7177 // order status color
7178
7179 switch ($order_details[0]['status'])
7180 {
7181 case 'CONFIRMED':
7182 $order_status_color = '#006600';
7183 break;
7184
7185 case 'PENDING':
7186 $order_status_color = '#D9A300';
7187 break;
7188
7189 case 'REMOVED':
7190 $order_status_color = '#B20000';
7191 break;
7192
7193 case 'CANCELED':
7194 $order_status_color = '#F01B17';
7195 break;
7196
7197 default:
7198 $order_status_color = 'inherit';
7199 }
7200
7201 // replace tags from template
7202
7203 $tmpl = str_replace('{company_name}' , $agency_name , $tmpl);
7204 $tmpl = str_replace('{order_status_class}' , strtolower($order_details[0]['status']) , $tmpl);
7205 $tmpl = str_replace('{order_status}' , JText::translate('VAPSTATUS' . $order_details[0]['status']) , $tmpl);
7206 $tmpl = str_replace('{order_status_color}' , $order_status_color , $tmpl);
7207 $tmpl = str_replace('{order_payment}' , $payment_name , $tmpl);
7208 $tmpl = str_replace('{order_coupon_code}' , $coupon_str , $tmpl);
7209 $tmpl = str_replace('{order_total_cost}' , $order_total , $tmpl);
7210 $tmpl = str_replace('{order_link}' , $order_link_href , $tmpl);
7211 $tmpl = str_replace('{confirmation_link}' , $confirmation_link , $tmpl);
7212 $tmpl = str_replace('{logo}' , $logo_str , $tmpl);
7213
7214 /**
7215 * @deprecated 1.8
7216 */
7217 $tmpl = str_replace('{appointment_details}' , $appointment_details, $tmpl);
7218 $tmpl = str_replace('{customer_details}' , $customer_details, $tmpl);
7219 $tmpl = str_replace('{user_details}' , $user_details, $tmpl);
7220
7221 return $tmpl;
7222 }
7223
7224 /////////////////////////////////////////////
7225 ///////////// CUSTOMER E-MAIL ///////////////
7226 /////////////////////////////////////////////
7227
7228 /**
7229 * Loads the e-mail template that should be parsed.
7230 *
7231 * @param array $orders The orders list.
7232 *
7233 * @return string The e-mail template.
7234 *
7235 * @deprecated 1.8 Without replacement.
7236 */
7237 public static function loadEmailTemplate(array $orders)
7238 {
7239 ob_start();
7240 include VAPHELPERS . DIRECTORY_SEPARATOR . 'mail_tmpls' . DIRECTORY_SEPARATOR . VAPFactory::getConfig()->get('mailtmpl');
7241 $content = ob_get_contents();
7242 ob_end_clean();
7243
7244 return $content;
7245 }
7246
7247 /**
7248 * Sends the notification e-mail to the customer.
7249 *
7250 * @param array $order_details The orders that should be notified.
7251 *
7252 * @return void
7253 *
7254 * @deprecated 1.9 Use VAPMailFactory instead.
7255 */
7256 public static function sendCustomerEmail($order_details)
7257 {
7258 VAPLoader::import('libraries.mail.factory');
7259
7260 $mail = VAPMailFactory::getInstance('customer', $order_details['id']);
7261
7262 if ($mail->shouldSend())
7263 {
7264 $mail->send();
7265 }
7266 }
7267
7268 /**
7269 * Method used to parse the e-mail template for the customers.
7270 *
7271 * @param string $tmpl The template string to parse.
7272 * @param array $order_details The orders list.
7273 *
7274 * @return string The parsed template.
7275 *
7276 * @deprecated 1.8 Without replacement.
7277 */
7278 public static function parseEmailTemplate($tmpl, $order_details)
7279 {
7280 // parse payment name
7281
7282 $payment_name = "";
7283 if (!empty($order_details[0]['payment_name']))
7284 {
7285 $payment_name = $order_details[0]['payment_name'];
7286 // $payment_name = '<div class="box'.($order_details[0]['total_cost'] > 0 ? '' : ' large').'">'.$order_details[0]['payment_name'].'</div>';
7287 }
7288
7289 // parse total cost
7290
7291 $total_cost = "";
7292 if ($order_details[0]['total_cost'] > 0)
7293 {
7294 $total_cost = self::printPriceCurrencySymb($order_details[0]['total_cost'] + $order_details[0]['payment_charge']);
7295 // $total_cost = '<div class="box'.(!empty($order_details[0]['payment_name']) ? '' : ' large').'">'.$total_cost.'</div>';
7296 }
7297
7298 // parse coupon string
7299
7300 $coupon_str = "";
7301 if (!empty($order_details[0]['coupon_str']))
7302 {
7303 list($code, $pt, $value) = explode(';;', $order_details[0]['coupon_str']);
7304 $coupon_str = $code . " : " . ($pt == 1 ? $value . '%' : self::printPriceCurrencySymb($value));
7305 // $coupon_str = '<div class="box large">'.$coupon_str.'</div>';
7306 }
7307
7308 // fetch appointment details
7309
7310 /**
7311 * @deprecated 1.8 the appointment details are parsed within the e-mail template
7312 */
7313 $appointment_details = "";
7314 for ($i = ($order_details[0]['id_service'] == -1 ? 1 : 0); $i < count($order_details); $i++)
7315 {
7316 $row = $order_details[$i];
7317
7318 $appointment_details .= '<div class="appointment">';
7319 $appointment_details .= '<div class="content ' . ($row['total_cost'] > 0 || count($row['options']) ? '' : 'fill-bottom') . '">';
7320 $appointment_details .= $row['sname'] . ' - ' . $row['ename'] . '<br />';
7321 $appointment_details .= $row['formatted_checkin'] . ' - ' . $row['formatted_duration'];
7322 $appointment_details .= '</div>';
7323
7324 if (count($row['options']))
7325 {
7326 $appointment_details .= '<div class="options-list'.($row['total_cost'] > 0 ? '' : ' fill-bottom').'">';
7327
7328 foreach ($row['options'] as $opt)
7329 {
7330 $appointment_details .= '<div class="option">';
7331 $appointment_details .= '<div class="name">- ' . $opt['full_name'] . '</div>';
7332 $appointment_details .= '<div class="quantity">' . $opt['formatted_quantity'] . '</div>';
7333 if ($opt['price'] != 0)
7334 {
7335 $appointment_details .= '<div class="price">' . $opt['formatted_price'] . '</div>';
7336 }
7337 $appointment_details .= '</div>';
7338 }
7339 $appointment_details .= '</div>';
7340 }
7341
7342 if ($row['total_cost'] > 0)
7343 {
7344 $appointment_details .= '<div class="cost"><span>' . $row['formatted_total'] . '</span></div>';
7345 }
7346
7347 $appointment_details .= '</div>';
7348 }
7349
7350 // customer details
7351
7352 $custom_fields = json_decode($order_details[0]['custom_f'], true);
7353
7354 /**
7355 * @deprecated 1.8 the customer details are parsed within the e-mail template
7356 */
7357 $customer_details = "";
7358 foreach ($custom_fields as $kc => $vc)
7359 {
7360 $customer_details .= '<div class="info">';
7361 $customer_details .= '<div class="label">'.JText::translate($kc).':</div>';
7362 $customer_details .= '<div class="value">'.$vc.'</div>';
7363 $customer_details .= '</div>';
7364 }
7365
7366 // joomla user details
7367 $user_details = '';
7368
7369 if (strlen($order_details[0]['user_email']) > 0)
7370 {
7371 /**
7372 * @deprecated 1.8 the joomla user details should be parsed within the e-mail template (if needed)
7373 */
7374
7375 $user_details = '<div class="separator"> </div>
7376 <div class="customer-details-wrapper">
7377 <div class="title">'.JText::translate('VAPUSERDETAILS').'</div>
7378 <div class="customer-details">
7379 <div class="info">
7380 <div class="label">'.JText::translate('VAPREGFULLNAME').':</div>
7381 <div class="value">'.$order_details[0]['user_name'].'</div>
7382 </div>
7383 <div class="info">
7384 <div class="label">'.JText::translate('VAPREGUNAME').':</div>
7385 <div class="value">'.$order_details[0]['user_uname'].'</div>
7386 </div>
7387 <div class="info">
7388 <div class="label">'.JText::translate('VAPREGEMAIL').':</div>
7389 <div class="value">'.$order_details[0]['user_email'].'</div>
7390 </div>
7391 </div>
7392 </div>';
7393 }
7394
7395 // order link
7396
7397 $order_link_href = "index.php?option=com_vikappointments&view=order&ordnum={$order_details[0]['id']}&ordkey={$order_details[0]['sid']}";
7398 $order_link_href = VAPApplication::getInstance()->routeForExternalUse($order_link_href);
7399
7400 $cancellation_link = "";
7401 if ($order_details[0]['status'] == 'CONFIRMED' && VAPFactory::getConfig()->getBool('enablecanc'))
7402 {
7403 $cancellation_link = $order_link_href . "#cancel";
7404
7405 // $cancellation_link .= '<div class="order-link">';
7406 // $cancellation_link .= '<div class="title">'.JText::translate('VAPCANCELLATIONLINK').'</div>';
7407 // $cancellation_link .= '<div class="content">';
7408 // $cancellation_link .= '<a href="'.$cancellation_link_href.'">'.$cancellation_link_href.'</a>';
7409 // $cancellation_link .= '</div>';
7410 // $cancellation_link .= '</div>';
7411 }
7412
7413 // logo
7414
7415 $logo_name = VAPFactory::getConfig()->get('companylogo');
7416 $agency_name = VAPFactory::getConfig()->get('agencyname');
7417
7418 $logo_str = "";
7419 if (!empty($logo_name) && file_exists(VAPMEDIA . DIRECTORY_SEPARATOR . $logo_name))
7420 {
7421 $logo_str = '<img src="' . VAPMEDIA_URI . $logo_name . '" alt="' . htmlspecialchars($agency_name) . '" />';
7422 }
7423
7424 // order status color
7425
7426 switch ($order_details[0]['status'])
7427 {
7428 case 'CONFIRMED':
7429 $order_status_color = '#006600';
7430 break;
7431
7432 case 'PENDING':
7433 $order_status_color = '#D9A300';
7434 break;
7435
7436 case 'REMOVED':
7437 $order_status_color = '#B20000';
7438 break;
7439
7440 case 'CANCELED':
7441 $order_status_color = '#F01B17';
7442 break;
7443
7444 default:
7445 $order_status_color = 'inherit';
7446 }
7447
7448 // replace tags from template
7449
7450 $tmpl = str_replace('{company_name}' , $agency_name , $tmpl);
7451 $tmpl = str_replace('{order_number}' , $order_details[0]['id'] , $tmpl);
7452 $tmpl = str_replace('{order_key}' , $order_details[0]['sid'] , $tmpl);
7453 $tmpl = str_replace('{order_status_class}' , strtolower($order_details[0]['status']) , $tmpl);
7454 $tmpl = str_replace('{order_status}' , JText::translate('VAPSTATUS' . $order_details[0]['status']) , $tmpl);
7455 $tmpl = str_replace('{order_status_color}' , $order_status_color , $tmpl);
7456 $tmpl = str_replace('{order_payment}' , $payment_name , $tmpl);
7457 $tmpl = str_replace('{order_payment_notes}' , $order_details[0]['payment_note'] , $tmpl);
7458 $tmpl = str_replace('{order_coupon_code}' , $coupon_str , $tmpl);
7459 $tmpl = str_replace('{order_total_cost}' , $total_cost , $tmpl);
7460 $tmpl = str_replace('{order_link}' , $order_link_href , $tmpl);
7461 $tmpl = str_replace('{cancellation_link}' , $cancellation_link , $tmpl);
7462 $tmpl = str_replace('{logo}' , $logo_str , $tmpl);
7463
7464 /**
7465 * @deprecated 1.8
7466 */
7467 $tmpl = str_replace('{appointment_details}' , $appointment_details , $tmpl);
7468 $tmpl = str_replace('{customer_details}' , $customer_details , $tmpl);
7469 $tmpl = str_replace('{user_details}' , $user_details , $tmpl);
7470
7471 // apply custom text
7472
7473 /**
7474 * Retrieve the list of the services and employees booked within this order.
7475 *
7476 * @since 1.6
7477 */
7478 $services_booked = array();
7479 $employees_booked = array();
7480
7481 foreach ($order_details as $order)
7482 {
7483 if ($order['id_service'] > 0)
7484 {
7485 $services_booked[] = $order['id_service'];
7486 }
7487
7488 if ($order['id_employee'] > 0)
7489 {
7490 $employees_booked[] = $order['id_employee'];
7491 }
7492 }
7493
7494 $services_booked = array_unique($services_booked);
7495 //
7496
7497 /**
7498 * Search for an e-mail custom text to manually inject.
7499 *
7500 * @since 1.6.5
7501 */
7502 if (isset($order_details[0]['mail_custom_text']))
7503 {
7504 // take specified IDs
7505 $cust_ids = $order_details[0]['mail_custom_text'];
7506 }
7507 else
7508 {
7509 // no custom IDs
7510 $cust_ids = null;
7511 }
7512
7513 /**
7514 * Check if we should include/exclude the default custom texts
7515 *
7516 * @since 1.6.5
7517 */
7518 if (!empty($order_details[0]['exclude_default_mail_texts']))
7519 {
7520 // do not use default custom texts
7521 $default_texts = false;
7522 }
7523 else
7524 {
7525 // use them too
7526 $default_texts = true;
7527 }
7528
7529 $tmpl = self::parseEmailCustomText($tmpl, $order_details[0]['status'], $order_details[0]['langtag'], $services_booked, $employees_booked, $cust_ids, $default_texts);
7530
7531 return $tmpl;
7532 }
7533
7534 /**
7535 * Parses the e-mail custom texts.
7536 *
7537 * @param string $tmpl The e-mail template (HTML).
7538 * @param string $status The required status.
7539 * @param string $lang The required language.
7540 * @param array $services A list of requested services (@since 1.6).
7541 * @param array $employees A list of requested employees (@since 1.6).
7542 * @param mixed $id Either an ID or a list of custom text to take (@since 1.6.5).
7543 * @param boolean $default True to load the default custom fields, false to use only the specified ID (@since 1.6.6).
7544 *
7545 * @return string The parsed HTML template.
7546 *
7547 * @deprecated 1.8 Use VikAppointmentsModelMailtext::parseTemplate() instead.
7548 */
7549 private static function parseEmailCustomText($tmpl, $status, $lang = null, array $services = array(), array $employees = array(), $id = null, $default = true)
7550 {
7551 $model = JModelVAP::getInstance('mailtext');
7552
7553 $order = new stdClass;
7554 $order->status = $status;
7555 $order->langtag = $lang;
7556 $order->appointments = array();
7557
7558 $n = max(array(count($services), count($employees)));
7559
7560 for ($i = 0; $i < $n; $i++)
7561 {
7562 $app = new stdClass;
7563
7564 $app->service = new stdClass;
7565 $app->service->id = isset($services[$i]) ? $services[$i] : 0;
7566
7567 $app->employee = new stdClass;
7568 $app->employee->id = isset($employees[$i]) ? $employees[$i] : 0;
7569
7570 $order->appointments[] = $app;
7571 }
7572
7573 $options = array(
7574 'lang' => $lang,
7575 'file' => VAPFactory::getConfig()->get('mailtmpl'),
7576 'id' => $id,
7577 'default' => $default,
7578 );
7579
7580 return $model->parseTemplate($tmpl, $order, $options);
7581 }
7582
7583 /**
7584 * Includes the files uploaded by the customers as attachment.
7585 * See the custom fields of type "file".
7586 *
7587 * @param mixed $order The order object.
7588 *
7589 * @return array The attachments array.
7590 */
7591 public static function includeMailAttachments($order)
7592 {
7593 if (is_array($order))
7594 {
7595 /**
7596 * Extract uploads from old array version for BC.
7597 *
7598 * @deprecated 1.8
7599 */
7600 $uploads = $order_details[0]['uploads'];
7601 }
7602 else
7603 {
7604 $uploads = $order->uploads;
7605 }
7606
7607 $attachments = array();
7608
7609 foreach ($uploads as $files)
7610 {
7611 if (!is_array($files))
7612 {
7613 // always treat as a list of files
7614 $files = array($files);
7615 }
7616
7617 foreach ($files as $filename)
7618 {
7619 // extract readable name from path
7620 $pretty = preg_replace("/^[a-f0-9]+_/", '', $filename);
7621
7622 $original = VAPCUSTOMERS_UPLOADS . DIRECTORY_SEPARATOR . $filename;
7623 $rename = VAPCUSTOMERS_UPLOADS . DIRECTORY_SEPARATOR . $pretty;
7624
7625 // copy original file to have a more readable file name
7626 if (is_file($original) && copy($original, $rename))
7627 {
7628 $attachments[] = $rename;
7629 }
7630 }
7631 }
7632
7633 return $attachments;
7634 }
7635
7636 /**
7637 * Destroys the attachments that have been sent to the administrator.
7638 * See the custom fields of type "file".
7639 *
7640 * @param array $attachments The list of attachments to remove.
7641 *
7642 * @return void
7643 *
7644 * @deprecated 1.9 Without replacement.
7645 */
7646 public static function destroyMailAttachments(array $attachments)
7647 {
7648 foreach ($attachments as $file)
7649 {
7650 unlink($file);
7651 }
7652 }
7653
7654 /**
7655 * Creates the ICS file to attach within the e-mail.
7656 *
7657 * @param integer $id_order The order to export.
7658 * @param boolean $is_admin True if the e-mail is sent to an administrator.
7659 * @param integer $id_emp The ID of the employee.
7660 *
7661 * @return mixed The path of the ICS file on success, otherwise null.
7662 *
7663 * @uses composeExportableFile()
7664 */
7665 public static function composeFileICS($id_order, $is_admin = false, $id_emp = 0)
7666 {
7667 return self::composeExportableFile('ics', $id_order, $is_admin, $id_emp);
7668 }
7669
7670 /**
7671 * Creates the CSV file to attach within the e-mail.
7672 *
7673 * @param integer $id_order The order to export.
7674 * @param boolean $is_admin True if the e-mail is sent to an administrator.
7675 * @param integer $id_emp The ID of the employee.
7676 *
7677 * @return mixed The path of the CSV file on success, otherwise null.
7678 *
7679 * @uses composeExportableFile()
7680 */
7681 public static function composeFileCSV($id_order, $is_admin = false, $id_emp = 0)
7682 {
7683 return self::composeExportableFile('csv', $id_order, $is_admin, $id_emp);
7684 }
7685
7686 /**
7687 * Creates the exportable file to attach within the e-mail.
7688 *
7689 * @param integer $id_order The order to export.
7690 * @param boolean $is_admin True if the e-mail is sent to an administrator.
7691 * @param integer $id_emp The ID of the employee.
7692 *
7693 * @return mixed The path of the exported file on success, otherwise null.
7694 */
7695 protected static function composeExportableFile($class, $id_order, $is_admin = false, $id_emp = 0)
7696 {
7697 // create destination path
7698 $path = VAPMAIL_ATTACHMENTS . DIRECTORY_SEPARATOR . JHtml::fetch('date', 'now', 'Y-m-d H_i_s');
7699 $tmp = $path;
7700
7701 $cont = 1;
7702
7703 while (is_file($tmp . '.' . $class))
7704 {
7705 $tmp = $path . '-' . $cont;
7706 $cont++;
7707 }
7708
7709 $path = $tmp . '.' . $class;
7710
7711 // prepare driver options
7712 $options = array(
7713 'cid' => array((int) $id_order),
7714 'id_employee' => (int) $id_emp,
7715 'admin' => $is_admin,
7716 );
7717
7718 // load driver instance
7719 VAPLoader::import('libraries.order.export.factory');
7720 $driver = VAPOrderExportFactory::getInstance($class, 'appointment', $options);
7721
7722 // load previously saved driver parameters
7723 $params = $driver->getParams();
7724
7725 foreach ($params as $k => $v)
7726 {
7727 // inject them as driver options
7728 $driver->setOption($k, $v);
7729 }
7730
7731 // export data and write into a file
7732 jimport('joomla.filesystem.file');
7733 if (JFile::write($path, $driver->export()) !== false)
7734 {
7735 return $path;
7736 }
7737
7738 return null;
7739 }
7740
7741 /////////////////////////////////////////////
7742 /////////// CANCELLATION E-MAIL /////////////
7743 /////////////////////////////////////////////
7744
7745 /**
7746 * Sends the notification e-mail to the administrator(s)
7747 * and related employee(s).
7748 *
7749 * @param array $order_details The orders that should be notified.
7750 *
7751 * @return void
7752 */
7753 public static function sendCancellationAdminEmail($order_details)
7754 {
7755 if (!$order_details)
7756 {
7757 return;
7758 }
7759
7760 self::loadLanguage(self::getDefaultLanguage('site'));
7761
7762 $subject = JText::translate('VAPORDERCANCELEDSUBJECT');
7763
7764 /**
7765 * Parse e-mail subject to replace tags with the
7766 * related order details.
7767 *
7768 * @since 1.6.6
7769 */
7770 static::parseEmailSubject($subject, $order_details);
7771
7772 $admin_mail_list = self::getAdminMailList();
7773 $sendermail = self::getSenderMail();
7774 $adminname = VAPFactory::getConfig()->get('agencyname');
7775
7776 $canc_tmpl = self::loadCancellationEmailTemplate($order_details, 1);
7777 $html_mess = self::parseCancellationEmailTemplate($canc_tmpl, $order_details, 1);
7778 $emp_details = self::filterOrdersByEmployee($order_details);
7779
7780 $vik = VAPApplication::getInstance();
7781
7782 foreach ($admin_mail_list as $_m)
7783 {
7784 $vik->sendMail($sendermail, $adminname, $_m, $_m, $subject, $html_mess, array(), true);
7785 }
7786
7787 foreach ($emp_details as $emp_mail => $emp_order_details)
7788 {
7789 /**
7790 * Reload cancellation e-mail template using the orders related to the specified employee.
7791 *
7792 * @since 1.6
7793 */
7794 $canc_tmpl = self::loadCancellationEmailTemplate($emp_order_details, 2);
7795 $_html = self::parseCancellationEmailTemplate($canc_tmpl, $emp_order_details, 2);
7796
7797 $vik->sendMail($sendermail, $adminname, $emp_mail, $admin_mail_list[0], $subject, $_html, array(), true);
7798 }
7799 }
7800
7801 /**
7802 * Loads the cancellation e-mail template that should be parsed.
7803 *
7804 * @param array $orders The orders list.
7805 * @param integer $type The entity type to render the template (1 for administrator, 2 for employee).
7806 *
7807 * @return string The e-mail template.
7808 *
7809 * @deprecated 1.8 Without replacement.
7810 */
7811 public static function loadCancellationEmailTemplate(array $orders, $type)
7812 {
7813 ob_start();
7814 include VAPHELPERS . DIRECTORY_SEPARATOR . 'mail_tmpls' . DIRECTORY_SEPARATOR . VAPFactory::getConfig()->get('cancmailtmpl');
7815 $content = ob_get_contents();
7816 ob_end_clean();
7817
7818 return $content;
7819 }
7820
7821 /**
7822 * Method used to parse the e-mail template for the administrator(s).
7823 *
7824 * @param string $tmpl The template string to parse.
7825 * @param array $order_details The orders list.
7826 * @param integer $type The entity type to render the template (1 for administrator, 2 for employee).
7827 *
7828 * @return string The parsed template.
7829 *
7830 * @deprecated 1.8 Without replacement.
7831 */
7832 public static function parseCancellationEmailTemplate($tmpl, $order_details, $type)
7833 {
7834 $vik = VAPApplication::getInstance();
7835
7836 // retrieve cancellation content
7837
7838 if ($type == 1)
7839 {
7840 $cancellation_content = JText::translate('VAPORDERCANCELEDCONTENT');
7841 }
7842 else
7843 {
7844 $cancellation_content = JText::translate('VAPORDERCANCELEDCONTENTEMP');
7845 }
7846
7847 // fetch appointment details
7848
7849 /**
7850 * @deprecated 1.8 the appointment details are parsed within the e-mail template
7851 */
7852 $appointment_details = "";
7853 for ($i = ($order_details[0]['id_service'] == -1 ? 1 : 0); $i < count($order_details); $i++)
7854 {
7855 $row = $order_details[$i];
7856
7857 if ($type == 1)
7858 {
7859 /**
7860 * Route administrator URL depending on the current platform.
7861 *
7862 * @since 1.6.3
7863 */
7864 $url = $vik->adminUrl('index.php?option=com_vikappointments&task=editreservation&cid[]=' . $row['id']);
7865 }
7866 else
7867 {
7868 $url = 'index.php?option=com_vikappointments&view=empmanres&cid[]=' . $row['id'];
7869 $url = $vik->routeForExternalUse($url);
7870 }
7871
7872 $appointment_details .= '<div class="appointment">';
7873
7874 $appointment_details .= '<div class="content">';
7875 $appointment_details .= '<div class="left">' . $row['id'] . ' - ' . $row['sid'] . '</div>';
7876 $appointment_details .= '<div class="right">' . JText::translate('VAPSTATUSCANCELED') . '</div>';
7877 $appointment_details .= '</div>';
7878
7879 $appointment_details .= '<div class="subcontent">';
7880 $appointment_details .= $row['sname'] . ($type == 1 ? ' - '.$row['ename'] : '') . '<br />';
7881 $appointment_details .= $row['formatted_checkin'] . ' - ' . $row['formatted_duration'];
7882 $appointment_details .= '</div>';
7883
7884 $appointment_details .= '<div class="link"><a href="' . $url . '">' . $url . '</a></div>';
7885
7886 $appointment_details .= '</div>';
7887 }
7888
7889 // customer details
7890
7891 $custom_fields = json_decode($order_details[0]['custom_f'], true);
7892
7893 /**
7894 * @deprecated 1.8 the customer details are parsed within the e-mail template
7895 */
7896 $customer_details = "";
7897 foreach ($custom_fields as $kc => $vc)
7898 {
7899 $customer_details .= '<div class="info">';
7900 $customer_details .= '<div class="label">'.JText::translate($kc).':</div>';
7901 $customer_details .= '<div class="value">'.$vc.'</div>';
7902 $customer_details .= '</div>';
7903 }
7904
7905 // order link
7906
7907 if ($type == 1)
7908 {
7909 /**
7910 * Route administrator URL depending on the current platform.
7911 *
7912 * @since 1.6.3
7913 */
7914 $order_link = $vik->adminUrl('index.php?option=com_vikappointments&view=reservations&res_id=' . $order_details[0]['id']);
7915 }
7916 else
7917 {
7918 $order_link = '';
7919 }
7920
7921 // logo
7922
7923 $logo_name = VAPFactory::getConfig()->get('companylogo');
7924 $agency_name = VAPFactory::getConfig()->get('agencyname');
7925
7926 $logo_str = "";
7927 if (!empty($logo_name) && file_exists(VAPMEDIA . DIRECTORY_SEPARATOR . $logo_name))
7928 {
7929 $logo_str = '<img src="' . VAPMEDIA_URI . $logo_name . '" alt="' . htmlspecialchars($agency_name) . '" />';
7930 }
7931
7932 // replace tags from template
7933
7934 $tmpl = str_replace('{company_name}' , $agency_name , $tmpl);
7935 $tmpl = str_replace('{cancellation_content}' , $cancellation_content , $tmpl);
7936 $tmpl = str_replace('{logo}' , $logo_str , $tmpl);
7937 $tmpl = str_replace('{order_link}' , $order_link , $tmpl);
7938
7939 /**
7940 * @deprecated 1.8
7941 */
7942 $tmpl = str_replace('{appointment_details}' , $appointment_details , $tmpl);
7943 $tmpl = str_replace('{customer_details}' , $customer_details , $tmpl);
7944
7945 return $tmpl;
7946 }
7947
7948 /**
7949 * Parses e-mail subject to replace tags with the
7950 * related order details.
7951 *
7952 * @param string &$subject The subject to parse.
7953 * @param mixed $order The appointments details.
7954 *
7955 * @return void
7956 *
7957 * @since 1.6.6
7958 */
7959 public static function parseEmailSubject(&$subject, $order)
7960 {
7961 $config = VAPFactory::getConfig();
7962
7963 $lookup = array();
7964
7965 // look for multi-appointments
7966 if (count($order->appointments) != 1)
7967 {
7968 // display number of booked appointments
7969 $lookup['service'] = JText::sprintf('VAPPACKAGESNUMAPP', count($order->appointments));
7970 // do not use employees
7971 $lookup['employee'] = '/';
7972 // use creation date
7973 $lookup['checkin_date'] = JHtml::fetch('date', $order->createdon, $config->get('dateformat'));
7974 // use creation time
7975 $lookup['checkin_time'] = JHtml::fetch('date', $order->createdon, $config->get('timeformat'));
7976 }
7977 else
7978 {
7979 // replicate service name
7980 $lookup['service'] = $order->appointments[0]->service->name;
7981 // replicate employee name
7982 $lookup['employee'] = $order->appointments[0]->employee->name;
7983 // use creation date
7984 $lookup['checkin_date'] = JHtml::fetch('date', $order->appointments[0]->checkin->utc, $config->get('dateformat'));
7985 // use creation time
7986 $lookup['checkin_time'] = JHtml::fetch('date', $order->appointments[0]->checkin->utc, $config->get('timeformat'));
7987 }
7988
7989 // include oid and sid
7990 $lookup['ordnum'] = $order->id;
7991 $lookup['ordkey'] = $order->sid;
7992 // specify date time too
7993 $lookup['checkin_datetime'] = $lookup['checkin_date'] . ' ' . $lookup['checkin_time'];
7994 // format total cost
7995 $lookup['total_cost'] = VAPFactory::getCurrency()->format($order->totals->gross);
7996 // translate status
7997 $lookup['status'] = JHtml::fetch('vaphtml.status.display', $order->status, 'plain');
7998 // replicate customer name
7999 $lookup['customer'] = $order->purchaser_nominative;
8000
8001 // look for any placeholders
8002 $subject = preg_replace_callback("/{([a-zA-Z0-9\_]+)}/i", function($match) use ($lookup)
8003 {
8004 // obtain tag
8005 $tag = end($match);
8006
8007 if (isset($lookup[$tag]))
8008 {
8009 // return related value
8010 return $lookup[$tag];
8011 }
8012
8013 // unsupported tag, leave as is
8014 return $match[0];
8015 }, $subject);
8016 }
8017
8018 /////////////////////////////////////////////
8019 /////////////// WAITING LIST ////////////////
8020 /////////////////////////////////////////////
8021
8022 /**
8023 * Checks all the customers subscribed to the waiting list that should
8024 * be notified after a cancellation of a confirmed appointment.
8025 *
8026 * @param array $order The details of the order that is no more confirmed.
8027 *
8028 * @return void
8029 *
8030 * @deprecated 1.8 Use VikAppointmentsModelWaitinglist::notify() instead.
8031 */
8032 public static function notifyCustomersInWaitingList($order)
8033 {
8034 $model = JModelVAP::getInstance('waitinglist');
8035
8036 if (is_array($order))
8037 {
8038 $order = $order[0]['id'];
8039 }
8040 else
8041 {
8042 $order = $order->id;
8043 }
8044
8045 return $model->notify($order);
8046 }
8047
8048 /**
8049 * Loads the e-mail template that will be used to notify
8050 * the waiting list subscriptions.
8051 *
8052 * @return string The HTML contents of the template.
8053 *
8054 * @deprecated 1.8 Without replacement.
8055 */
8056 public static function loadWaitListEmailTemplate()
8057 {
8058 ob_start();
8059 include VAPBASE . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "mail_tmpls" . DIRECTORY_SEPARATOR . VAPFactory::getConfig()->get('waitlistmailtmpl');
8060 $content = ob_get_contents();
8061 ob_end_clean();
8062
8063 return $content;
8064 }
8065
8066 /**
8067 * Removes from the waiting list the subscription that has been notified.
8068 * Usually, when a customer receives the notification, it proceeds with the
8069 * purchase of the appointment. At the end of this process, its subscription
8070 * is automatically removed by using this method.
8071 *
8072 * In addition, removes all the waiting list subscriptions
8073 * that are older than the current day.
8074 *
8075 * @param object $order The order details.
8076 *
8077 * @return void
8078 */
8079 public static function flushWaitingList($order)
8080 {
8081 $dbo = JFactory::getDbo();
8082
8083 $q = $dbo->getQuery(true)
8084 ->delete($dbo->qn('#__vikappointments_waitinglist'))
8085 ->where($dbo->qn('timestamp') . ' < ' . $dbo->q(JFactory::getDate()->toSql()));
8086
8087 $dbo->setQuery($q);
8088 $dbo->execute();
8089
8090 $waitModel = JModelVAP::getInstance('waitinglist');
8091
8092 foreach ($order->appointments as $app)
8093 {
8094 // the appointment was registered, unsubscribe the customer from
8095 // the related waiting list
8096 $waitModel->unsubscribe(array(
8097 'jid' => $order->createdby,
8098 'email' => $order->purchaser_mail,
8099 'phone_number' => $order->purchaser_phone,
8100 'timestamp' => $app->checkin->utc,
8101 'id_service' => $app->service->id,
8102 ));
8103 }
8104 }
8105
8106 /////////////////////////////////////////////
8107 ///////////// PACKAGES E-MAIL ///////////////
8108 /////////////////////////////////////////////
8109
8110 /**
8111 * Returns the packages order details of the given ID.
8112 *
8113 * @param integer $order_id The order number (ID).
8114 * @param string $order_key The order key (sid). If not provided,
8115 * this field won't used while fetching the records.
8116 * @param string $langtag The translation language. If not provided
8117 * it will be used the default one.
8118 *
8119 * @return mixed A list of orders on success, otherwise false.
8120 *
8121 * @deprecated 1.8 Use VAPOrderFactory::getPackages() instead.
8122 */
8123 public static function fetchPackagesOrderDetails($order_id, $order_key = null, $langtag = null)
8124 {
8125 try
8126 {
8127 VAPLoader::import('libraries.order.factory');
8128 return VAPOrderFactory::getPackages($order_id, $langtag, array('sid' => $order_key));
8129 }
8130 catch (Exception $e)
8131 {
8132
8133 }
8134
8135 return false;
8136 }
8137
8138 // PACKAGES E-MAIL
8139
8140 /**
8141 * Loads the packages e-mail template that should be parsed.
8142 *
8143 * @param array $order The order details.
8144 *
8145 * @return string The e-mail template.
8146 *
8147 * @deprecated 1.8 Without replacement.
8148 */
8149 public static function loadPackagesEmailTemplate(array $order)
8150 {
8151 ob_start();
8152 include VAPHELPERS . DIRECTORY_SEPARATOR . 'mail_tmpls' . DIRECTORY_SEPARATOR . VAPFactory::getConfig()->get('packmailtmpl');
8153 $content = ob_get_contents();
8154 ob_end_clean();
8155
8156 return $content;
8157 }
8158
8159 /**
8160 * Sends the notification e-mail to the customer for the packages.
8161 *
8162 * @param array $order_details The orders that should be notified.
8163 *
8164 * @return void
8165 *
8166 * @deprecated 1.9 Use VAPMailFactory instead.
8167 */
8168 public static function sendPackagesCustomerEmail($order_details)
8169 {
8170 VAPLoader::import('libraries.mail.factory');
8171
8172 $mail = VAPMailFactory::getInstance('package', $order_details['id']);
8173
8174 if ($mail->shouldSend())
8175 {
8176 $mail->send();
8177 }
8178 }
8179
8180 /**
8181 * Sends the notification e-mail to the administrator(s) for the packages.
8182 *
8183 * @param array $order_details The orders that should be notified.
8184 *
8185 * @return void
8186 *
8187 * @deprecated 1.9 Use VAPMailFactory instead.
8188 */
8189 public static function sendPackagesAdminEmail($order_details)
8190 {
8191 VAPLoader::import('libraries.mail.factory');
8192
8193 $mail = VAPMailFactory::getInstance('packadmin', $order_details['id']);
8194
8195 if ($mail->shouldSend())
8196 {
8197 $mail->send();
8198 }
8199 }
8200
8201 /**
8202 * Method used to parse the e-mail template for packages (admin and customers).
8203 *
8204 * @param string $tmpl The template string to parse.
8205 * @param array $order_details The orders list.
8206 *
8207 * @return string The parsed template.
8208 *
8209 * @deprecated 1.8 Without replacement.
8210 */
8211 public static function parsePackagesEmailTemplate($tmpl, $order_details)
8212 {
8213 // parse payment name
8214
8215 $payment_name = "";
8216 if (!empty($order_details['payment_name']))
8217 {
8218 $payment_name = $order_details['payment_name'];
8219 // $payment_name = '<div class="box'.($order_details['total_cost'] > 0 ? '' : ' large').'">'.$order_details['payment_name'].'</div>';
8220 }
8221
8222 // parse total cost
8223
8224 $total_cost = "";
8225 if ($order_details['total_cost'] > 0)
8226 {
8227 $total_cost = self::printPriceCurrencySymb($order_details['total_cost']);
8228 // $total_cost = '<div class="box'.(!empty($order_details['payment_name']) ? '' : ' large').'">'.$total_cost.'</div>';
8229 }
8230
8231 // fetch package details
8232
8233 /**
8234 * @deprecated 1.8 the package details are parsed within the e-mail template
8235 */
8236 $packages_details = "";
8237 foreach ($order_details['items'] as $p)
8238 {
8239 $packages_details .= '<div class="package">';
8240 $packages_details .= '<div class="content '.($p['price'] > 0 ? '' : 'fill-bottom').'">';
8241 $packages_details .= '<span class="name">'.$p['name'].'</span>';
8242 $packages_details .= '<span class="numapp">'.JText::sprintf('VAPPACKAGESMAILAPP', $p['num_app']).'</span>';
8243 $packages_details .= '<span class="quantity">x'.$p['quantity'].'</span>';
8244 $packages_details .= '</div>';
8245
8246 if ($p['price'] > 0)
8247 {
8248 $packages_details .= '<div class="cost"><span>'.self::printPriceCurrencySymb($p['price']*$p['quantity']).'</span></div>';
8249 }
8250
8251 $packages_details .= '</div>';
8252 }
8253
8254 // customer details
8255
8256 $custom_fields = json_decode($order_details['custom_f'], true);
8257
8258 /**
8259 * @deprecated 1.8 the customer details are parsed within the e-mail template
8260 */
8261 $customer_details = "";
8262 foreach ($custom_fields as $kc => $vc)
8263 {
8264 $customer_details .= '<div class="info">';
8265 $customer_details .= '<div class="label">'.JText::translate($kc).':</div>';
8266 $customer_details .= '<div class="value">'.$vc.'</div>';
8267 $customer_details .= '</div>';
8268 }
8269
8270 // joomla user details
8271
8272 $user_details = '';
8273 if (strlen($order_details['user_email']))
8274 {
8275 /**
8276 * @deprecated 1.8 the joomla user details should be parsed within the e-mail template (if needed)
8277 */
8278
8279 $user_details = '<div class="separator"> </div>
8280 <div class="customer-details-wrapper">
8281 <div class="title">'.JText::translate('VAPUSERDETAILS').'</div>
8282 <div class="customer-details">
8283 <div class="info">
8284 <div class="label">'.JText::translate('VAPREGFULLNAME').':</div>
8285 <div class="value">'.$order_details['user_name'].'</div>
8286 </div>
8287 <div class="info">
8288 <div class="label">'.JText::translate('VAPREGUNAME').':</div>
8289 <div class="value">'.$order_details['user_uname'].'</div>
8290 </div>
8291 <div class="info">
8292 <div class="label">'.JText::translate('VAPREGEMAIL').':</div>
8293 <div class="value">'.$order_details['user_email'].'</div>
8294 </div>
8295 </div>
8296 </div>';
8297 }
8298
8299 // order link
8300
8301 $order_link_href = "index.php?option=com_vikappointments&view=packagesorder&ordnum={$order_details['id']}&ordkey={$order_details['sid']}";
8302 $order_link_href = VAPApplication::getInstance()->routeForExternalUse($order_link_href);
8303
8304 // logo
8305
8306 $logo_name = VAPFactory::getConfig()->get('companylogo');
8307 $agency_name = VAPFactory::getConfig()->get('agencyname');
8308
8309 $logo_str = "";
8310 if (!empty($logo_name) && file_exists(VAPMEDIA . DIRECTORY_SEPARATOR . $logo_name))
8311 {
8312 $logo_str = '<img src="' . VAPMEDIA_URI . $logo_name . '" alt="' . htmlspecialchars($agency_name) . '" />';
8313 }
8314
8315 // order status color
8316
8317 switch ($order_details['status'])
8318 {
8319 case 'CONFIRMED':
8320 $order_status_color = '#006600';
8321 break;
8322
8323 case 'PENDING':
8324 $order_status_color = '#D9A300';
8325 break;
8326
8327 case 'REMOVED':
8328 $order_status_color = '#B20000';
8329 break;
8330
8331 case 'CANCELED':
8332 $order_status_color = '#F01B17';
8333 break;
8334
8335 default:
8336 $order_status_color = 'inherit';
8337 }
8338
8339 // replace tags from template
8340
8341 $tmpl = str_replace('{company_name}' , $agency_name , $tmpl);
8342 $tmpl = str_replace('{order_number}' , $order_details['id'] , $tmpl);
8343 $tmpl = str_replace('{order_key}' , $order_details['sid'] , $tmpl);
8344 $tmpl = str_replace('{order_status_class}' , strtolower($order_details['status']) , $tmpl);
8345 $tmpl = str_replace('{order_status}' , JText::translate('VAPSTATUS' . $order_details['status']) , $tmpl);
8346 $tmpl = str_replace('{order_status_color}' , $order_status_color , $tmpl);
8347 $tmpl = str_replace('{order_payment}' , $payment_name , $tmpl);
8348 $tmpl = str_replace('{order_payment_notes}' , $order_details['payment_note'] , $tmpl);
8349 $tmpl = str_replace('{order_total_cost}' , $total_cost , $tmpl);
8350 $tmpl = str_replace('{order_link}' , $order_link_href , $tmpl);
8351 $tmpl = str_replace('{logo}' , $logo_str , $tmpl);
8352
8353 /**
8354 * @deprecated 1.8
8355 */
8356 $tmpl = str_replace('{packages_details}', $packages_details , $tmpl);
8357 $tmpl = str_replace('{customer_details}', $customer_details , $tmpl);
8358 $tmpl = str_replace('{user_details}' , $user_details , $tmpl);
8359
8360 return $tmpl;
8361 }
8362
8363 /**
8364 * Sends a notification about the purchased order to the specified e-mail.
8365 * If allowed, a notification will be sent also to the employees and the administrators.
8366 *
8367 * @param mixed $order Either an order ID or an object.
8368 *
8369 * @return boolean True on success, false otherwise.
8370 *
8371 * @since 1.7
8372 */
8373 public static function sendMailAction($order)
8374 {
8375 if (is_numeric($order))
8376 {
8377 VAPLoader::import('libraries.order.factory');
8378 $order = VAPOrderFactory::getAppointments($order);
8379 }
8380
8381 // get appointment model
8382 $model = JModelVAP::getInstance('reservation');
8383
8384 $mailOptions = array();
8385 // validate e-mail rules before sending
8386 $mailOptions['check'] = true;
8387
8388 // send e-mail notification to the customer
8389 $model->sendEmailNotification($order->id, $mailOptions);
8390
8391 // send e-mail notification to the administrator(s)
8392 $mailOptions['client'] = $order->statusRole === 'CANCELLED' ? 'cancellation' : 'admin';
8393 $model->sendEmailNotification($order->id, $mailOptions);
8394
8395 // send e-mail notification to all the booked employees
8396 $mailOptions['client'] = 'employee';
8397
8398 $employees = array();
8399
8400 // iterate all appointments to look for the employees to notify
8401 foreach ($order->appointments as $appointment)
8402 {
8403 // make sure the same employees hasn't been yet notified
8404 if (!in_array($appointment->employee->id, $employees))
8405 {
8406 $employees[] = $appointment->employee->id;
8407
8408 $mailOptions['id_employee'] = (int) $appointment->employee->id;
8409 $model->sendEmailNotification($order->id, $mailOptions);
8410 }
8411 }
8412 }
8413
8414 /////////////////////////////////////////////
8415 //////////////////// SMS ////////////////////
8416 /////////////////////////////////////////////
8417
8418 /**
8419 * Sends a notification about the purchased order to the specified number.
8420 * If allowed, a notification will be sent also to the employees and the administrators.
8421 *
8422 * Removed the first parameter $phone_number @since 1.7 version, because it is
8423 * automatically retrieved from the details of the given order.
8424 *
8425 * @param mixed $order Either an order ID or an object.
8426 *
8427 * @return boolean True on success, false otherwise.
8428 *
8429 * @uses sendAdminMailSmsFailed()
8430 */
8431 public static function sendSmsAction($order)
8432 {
8433 $config = VAPFactory::getConfig();
8434
8435 if (!$config->getBool('smsenabled'))
8436 {
8437 // automatic SMS disabled
8438 return false;
8439 }
8440
8441 try
8442 {
8443 // get current SMS instance
8444 $smsapi = VAPApplication::getInstance()->getSmsInstance();
8445 }
8446 catch (Exception $e)
8447 {
8448 // SMS API not configured
8449 return false;
8450 }
8451
8452 /**
8453 * Check whether we received the phone number as first argument.
8454 * In that case, we need to extract the order details array from
8455 * the method arguments
8456 *
8457 * @deprecated 1.8
8458 */
8459 if (is_string($order) && func_num_args() > 1)
8460 {
8461 // phone number given, take the second argument for BC
8462 $order = func_get_arg(1);
8463 // extract order ID from array
8464 $order = $order[0]['id'];
8465 }
8466
8467 if (is_numeric($order))
8468 {
8469 try
8470 {
8471 // load appointment details
8472 VAPLoader::import('libraries.order.factory');
8473 $order = VAPOrderFactory::getAppointments($order);
8474 }
8475 catch (Exception $e)
8476 {
8477 // order not found
8478 return false;
8479 }
8480 }
8481
8482 $dispatcher = VAPFactory::getEventDispatcher();
8483
8484 $errors = array();
8485
8486 // check whether the customer should receive the SMS
8487 $should_send = VikAppointments::getSmsApiToCustomer();
8488
8489 $text = '';
8490
8491 /**
8492 * Choose at runtime whether the customer should receive SMS notifications.
8493 *
8494 * @param object $order The order details.
8495 * @param string &$text Fill to override the default SMS text (@since 1.7.3).
8496 *
8497 * @return boolean Return true to send the notification. Return false to deny
8498 * the notification. Return null to rely on the default setting.
8499 *
8500 * @since 1.7
8501 */
8502 $result = $dispatcher->falseOrTrue('onBeforeSendCustomerSmsNotification', array($order, &$text));
8503
8504 if (!is_null($result))
8505 {
8506 // use the condition fetched by the plugin
8507 $should_send = $result;
8508 }
8509 else
8510 {
8511 // no attached plugin, make sure the status role of the order is APPROVED
8512 $should_send = $should_send && $order->statusRole === 'APPROVED';
8513 }
8514
8515 // try to send SMS to the customer
8516 if ($order->purchaser_phone && $should_send)
8517 {
8518 // check whether the plugin built a custom message
8519 if (empty($text) || !is_string($text))
8520 {
8521 // fetch sms message
8522 $text = VikAppointments::getSmsCustomerTextMessage($order);
8523 }
8524
8525 /**
8526 * Inject tags within the API provider as well.
8527 *
8528 * @since 1.7.8
8529 */
8530 if (method_exists($smsapi, 'setOrder'))
8531 {
8532 $smsapi->setOrder(VikAppointments::getTagsSms($order));
8533 }
8534
8535 // send message
8536 $response = $smsapi->sendMessage($order->purchaser_phone, $text);
8537
8538 // validate response
8539 if (!$smsapi->validateResponse($response))
8540 {
8541 // unable to send the notification, register error
8542 $errors[] = $smsapi->getLog();
8543 }
8544 }
8545
8546 // try to send SMS to the administrator
8547 $admin_phone = $config->get('smsapiadminphone');
8548
8549 // check whether the administrator should receive the SMS
8550 $should_send = VikAppointments::getSmsApiToAdmin();
8551
8552 $text = '';
8553
8554 /**
8555 * Choose at runtime whether the administrator should receive SMS notifications.
8556 *
8557 * @param object $order The order details.
8558 * @param string &$text Fill to override the default SMS text (@since 1.7.3).
8559 *
8560 * @return boolean Return true to send the notification. Return false to deny
8561 * the notification. Return null to rely on the default setting.
8562 *
8563 * @since 1.7
8564 */
8565 $result = $dispatcher->falseOrTrue('onBeforeSendAdminSmsNotification', array($order, &$text));
8566
8567 if (!is_null($result))
8568 {
8569 // use the condition fetched by the plugin
8570 $should_send = $result;
8571 }
8572 else
8573 {
8574 // no attached plugin, make sure the status role of the order is APPROVED
8575 $should_send = $should_send && $order->statusRole === 'APPROVED';
8576 }
8577
8578 if ($admin_phone && $should_send)
8579 {
8580 // check whether the plugin built a custom message
8581 if (empty($text) || !is_string($text))
8582 {
8583 // fetch sms message (reload contents into the correct language)
8584 $text = VikAppointments::getSmsAdminTextMessage($order->id);
8585 }
8586
8587 // send message
8588 $response = $smsapi->sendMessage($admin_phone, $text);
8589
8590 // validate response
8591 if (!$smsapi->validateResponse($response))
8592 {
8593 // unable to send the notification, register error
8594 $errors[] = $smsapi->getLog();
8595 }
8596 }
8597
8598 $emp_lookup = array();
8599
8600 // group appointments by employee
8601 foreach ($order->appointments as $app)
8602 {
8603 if (!isset($emp_lookup[$app->employee->phone]))
8604 {
8605 $emp_lookup[$app->employee->phone] = array();
8606 }
8607
8608 // register appointment ID
8609 $emp_lookup[$app->employee->phone][] = $app->id;
8610 }
8611
8612 // iterate all the employees to notify
8613 foreach ($emp_lookup as $phone => $ids)
8614 {
8615 // check whether the employee should receive the SMS
8616 $should_send = VikAppointments::getSmsApiToEmployee();
8617
8618 $text = '';
8619
8620 /**
8621 * Choose at runtime whether the employee should receive SMS notifications.
8622 *
8623 * @param string $phone The phone number of the employee to notify.
8624 * @param object $order The order details.
8625 * @param string &$text Fill to override the default SMS text (@since 1.7.3).
8626 *
8627 * @return boolean Return true to send the notification. Return false to deny
8628 * the notification. Return null to rely on the default setting.
8629 *
8630 * @since 1.7
8631 */
8632 $result = $dispatcher->falseOrTrue('onBeforeSendEmployeeSmsNotification', array($phone, $order, &$text));
8633
8634 if (!is_null($result))
8635 {
8636 // use the condition fetched by the plugin
8637 $should_send = $result;
8638 }
8639 else
8640 {
8641 // no attached plugin, make sure the status role of the order is APPROVED
8642 $should_send = $should_send && $order->statusRole === 'APPROVED';
8643 }
8644
8645 if (!$should_send)
8646 {
8647 // go to the next employee
8648 continue;
8649 }
8650
8651 // check whether the plugin built a custom message
8652 if (empty($text) || !is_string($text))
8653 {
8654 if (count($ids) == 1)
8655 {
8656 // fetch sms message for the found order
8657 $text = VikAppointments::getSmsAdminTextMessage($ids[0]);
8658 }
8659 else
8660 {
8661 // the employee received more than an order, use generic message
8662 $text = VikAppointments::getSmsAdminTextMessage($order->id);
8663 }
8664 }
8665
8666 // send message
8667 $response = $smsapi->sendMessage($phone, $text);
8668
8669 // validate response
8670 if (!$smsapi->validateResponse($response))
8671 {
8672 // unable to send the notification, register error
8673 $errors[] = $smsapi->getLog();
8674 }
8675 }
8676
8677 if ($errors)
8678 {
8679 // inform the administrator about all the fetched errors
8680 self::sendAdminMailSmsFailed($errors);
8681 return false;
8682 }
8683
8684 return true;
8685 }
8686
8687 /**
8688 * Returns the SMS message that should be sent to the customers.
8689 *
8690 * @param mixed $order Either an order ID or an object.
8691 *
8692 * @return string The SMS message to send to the customers.
8693 *
8694 * @uses parseContentSMS()
8695 */
8696 public static function getSmsCustomerTextMessage($order)
8697 {
8698 // store current language tag
8699 $curr_lang = JFactory::getLanguage()->getTag();
8700
8701 if (is_numeric($order))
8702 {
8703 VAPLoader::import('libraries.order.factory');
8704
8705 // load order details without caring of the exceptions
8706 // that this method might throw
8707 $order = VAPOrderFactory::getAppointments($order);
8708 }
8709
8710 if (empty($order->langtag))
8711 {
8712 // use default site language tag
8713 $order->langtag = static::getDefaultLanguage();
8714 }
8715
8716 // load front-end language
8717 static::loadLanguage($order->langtag, JPATH_SITE);
8718
8719 if (count($order->appointments) == 1)
8720 {
8721 // load content for single appointment
8722 $setting = 'smstmplcust';
8723 }
8724 else
8725 {
8726 // load content for multiple appointments (or none)
8727 $setting = 'smstmplcustmulti';
8728 }
8729
8730 // get JSON array from configuration
8731 $sms_map = VAPFactory::getConfig()->getArray($setting);
8732
8733 // make sure the SMS lookup specifies a template to
8734 // be used for the given language
8735 if (!empty($sms_map[$order->langtag]))
8736 {
8737 // use template
8738 $sms = $sms_map[$order->langtag];
8739 }
8740 else
8741 {
8742 // fallback to default template
8743 if (count($order->appointments) == 1)
8744 {
8745 // single-appointment template
8746 $sms = JText::translate('VAPSMSMESSAGECUSTOMER');
8747 }
8748 else
8749 {
8750 // multi-appointments template
8751 $sms = JText::translate('VAPSMSMESSAGECUSTOMERMULTI');
8752 }
8753 }
8754
8755 // parse SMS template
8756 $sms = static::parseContentSMS($order, $sms);
8757
8758 // restore previous language according to the current cllient
8759 static::loadLanguage($curr_lang);
8760
8761 return $sms;
8762 }
8763
8764 /**
8765 * Returns the SMS message that should be sent to the administrator.
8766 *
8767 * @param mixed $order Either an order ID or an object.
8768 *
8769 * @return string The SMS message to send to the administrator.
8770 *
8771 * @uses parseContentSMS()
8772 */
8773 public static function getSmsAdminTextMessage($order)
8774 {
8775 // store current language tag
8776 $curr_lang = JFactory::getLanguage()->getTag();
8777 // get default site language tag
8778 $def_lang = static::getDefaultLanguage();
8779
8780 // load front-end language
8781 static::loadLanguage($def_lang, JPATH_SITE);
8782
8783 if (is_numeric($order))
8784 {
8785 VAPLoader::import('libraries.order.factory');
8786
8787 // load order details without caring of the exceptions
8788 // that this method might throw
8789 $order = VAPOrderFactory::getAppointments($order, $def_lang);
8790 }
8791
8792 if (count($order->appointments) == 1)
8793 {
8794 // load content for single appointment
8795 $setting = 'smstmpladmin';
8796 }
8797 else
8798 {
8799 // load content for multiple appointments (or none)
8800 $setting = 'smstmpladminmulti';
8801 }
8802
8803 // get template from configuration
8804 $sms = VAPFactory::getConfig()->getString($setting);
8805
8806 // make sure the template exists
8807 if (!trim($sms))
8808 {
8809 // fallback to default template
8810 if (count($order->appointments) == 1)
8811 {
8812 // single-appointment template
8813 $sms = JText::translate('VAPSMSMESSAGEADMIN');
8814 }
8815 else
8816 {
8817 // multi-appointments template
8818 $sms = JText::translate('VAPSMSMESSAGEADMINMULTI');
8819 }
8820 }
8821
8822 // parse SMS template
8823 $sms = self::parseContentSMS($order, $sms);
8824
8825 // restore previous language according to the current cllient
8826 static::loadLanguage($curr_lang);
8827
8828 return $sms;
8829 }
8830
8831 /**
8832 * Parses the SMS template to inject the details of the given order.
8833 *
8834 * @param object $order The object containing the order details.
8835 * @param string $sms The SMS template.
8836 *
8837 * @return string The SMS message to send.
8838 *
8839 * @since 1.7.7 Changed visibilty from private to public.
8840 */
8841 public static function parseContentSMS($order, $sms)
8842 {
8843 $data = self::getTagsSMS($order, $sms);
8844
8845 // look for any placeholders
8846 $sms = preg_replace_callback("/{([a-zA-Z0-9\_]+)}/i", function($match) use ($data) {
8847 // obtain tag
8848 $tag = end($match);
8849
8850 if (isset($data[$tag]))
8851 {
8852 // return related value
8853 return $data[$tag];
8854 }
8855
8856 // unsupported tag, leave as is
8857 return $match[0];
8858 }, $sms);
8859
8860 return $sms;
8861 }
8862
8863 /**
8864 * Parses the SMS template to inject the details of the given order.
8865 *
8866 * @param object $order The object containing the order details.
8867 * @param string $sms The SMS template.
8868 *
8869 * @return string The SMS message to send.
8870 *
8871 * @since 1.7.8
8872 */
8873 public static function getTagsSMS($order, $sms = null)
8874 {
8875 $config = VAPFactory::getConfig();
8876 $currency = VAPFactory::getCurrency();
8877
8878 // order placeholders
8879 $data = [
8880 'total_cost' => $currency->format($order->totals->gross),
8881 'company' => $config->get('agencyname'),
8882 'customer' => $order->purchaser_nominative ? $order->purchaser_nominative : JText::translate('VAPMANAGERESERVATION29'),
8883 'created_on' => JHtml::fetch('date', $order->createdon, JText::translate('DATE_FORMAT_LC2'), $order->customerTimezone),
8884 ];
8885
8886 // appointment placeholders
8887 if (count($order->appointments) == 1)
8888 {
8889 $appointment = $order->appointments[0];
8890
8891 $data['checkin'] = $appointment->customerCheckin->lc2;
8892 $data['checkin_time'] = JHtml::fetch('date', $appointment->checkin->utc, $config->get('timeformat'), $appointment->customerCheckin->timezone);
8893 $data['service'] = $appointment->service->name;
8894 $data['employee'] = $appointment->employee->name;
8895 $data['employee_mail'] = $appointment->employee->email;
8896 $data['location'] = $appointment->location ? $appointment->location->text : '';
8897 $data['location_short'] = $appointment->location ? $appointment->location->short : '';
8898 $data['location_name'] = $appointment->location ? $appointment->location->name : '';
8899 }
8900
8901 /**
8902 * This event can be used to extend/alter the value of the available
8903 * placeholders that are going to be injected within a SMS template.
8904 *
8905 * @param array &$data The array with the available tags.
8906 * @param object $order The order details.
8907 * @param string $tmpl The SMS template.
8908 *
8909 * @return void
8910 *
8911 * @since 1.7
8912 */
8913 VAPFactory::getEventDispatcher()->trigger('onPopulateSmsPlaceholders', [&$data, $order, $sms]);
8914
8915 return $data;
8916 }
8917
8918 /**
8919 * Sends a notification e-mail to the administrator(s) to
8920 * inform that a SMS was not sent correctly.
8921 *
8922 * @param string $text The error message.
8923 *
8924 * @return void
8925 */
8926 public static function sendAdminMailSmsFailed($text)
8927 {
8928 $vik = VAPApplication::getInstance();
8929
8930 $admin_mail_list = self::getAdminMailList();
8931 $sendermail = self::getSenderMail();
8932 $subject = JText::translate('VAPSMSFAILEDSUBJECT');
8933
8934 if (is_array($text))
8935 {
8936 // get rid of repeated messages
8937 $text = array_unique(array_filter($text, 'trim'));
8938 // join them within a string
8939 $text = implode('<br />', $text);
8940 }
8941
8942 if (!$text)
8943 {
8944 // nothing to notify
8945 return;
8946 }
8947
8948 foreach ($admin_mail_list as $_m)
8949 {
8950 $vik->sendMail($sendermail, $sendermail, $_m, null, $subject, $text, null, true);
8951 }
8952 }
8953
8954 /**
8955 * Sends a notification e-mail to the administrator(s) every
8956 * time an error occurs while trying to validate a payment.
8957 *
8958 * @param integer $id The order number.
8959 * @param mixed $text Either an array of messages or a string.
8960 *
8961 * @return boolean True in case the notification was sent, false otherwise.
8962 *
8963 * @since 1.7
8964 */
8965 public static function sendAdminMailPaymentFailed($id, $text)
8966 {
8967 if (is_array($text))
8968 {
8969 // join messages, separated by an empty line
8970 $text = implode('<br /><br />', $text);
8971 }
8972
8973 $config = VAPFactory::getConfig();
8974
8975 // get administrators e-mail
8976 $adminmails = self::getAdminMailList();
8977 // get sender e-mail address
8978 $sendermail = self::getSenderMail();
8979 // get company name
8980 $fromname = $config->getString('agencyname');
8981
8982 // fetch e-mail subject
8983 $subject = sprintf('%s #%d - %s', JText::translate('VAPINVALIDPAYMENTSUBJECT'), $id, $fromname);
8984
8985 $vik = VAPApplication::getInstance();
8986
8987 $sent = false;
8988
8989 // iterate e-mails to notify
8990 foreach ($adminmails as $recipient)
8991 {
8992 // send the e-mail notification
8993 $sent = $vik->sendMail($sendermail, $fromname, $recipient, $recipient, $subject, $text) || $sent;
8994 }
8995
8996 return $sent;
8997 }
8998
8999 /////////////////////////////////////////////
9000 ///////////// LANGUAGE & i18n ///////////////
9001 /////////////////////////////////////////////
9002
9003 /**
9004 * Returns the default language of the specified section.
9005 *
9006 * @param string $section The section to check (site or administrator).
9007 *
9008 * @return atring The default language tag.
9009 */
9010 public static function getDefaultLanguage($section = 'site')
9011 {
9012 return JComponentHelper::getParams('com_languages')->get($section);
9013 }
9014
9015 /**
9016 * Method used to force the site language of VikAppointments
9017 * according to the specified language tag. If the language is
9018 * not specified, the default one will be used.
9019 *
9020 * @param string $tag The language tag.
9021 * @param mixed $client The base path of the language.
9022 *
9023 * @return void
9024 */
9025 public static function loadLanguage($tag = null, $client = null)
9026 {
9027 if (!empty($tag))
9028 {
9029 /**
9030 * Added support for client argument to allow also
9031 * the loading of back-end languages.
9032 *
9033 * @since 1.7
9034 */
9035 if (is_null($client))
9036 {
9037 if (JFactory::getApplication()->isClient('site'))
9038 {
9039 $client = JPATH_SITE;
9040 }
9041 else
9042 {
9043 $client = JPATH_ADMINISTRATOR;
9044 }
9045 }
9046
9047 $lang = JFactory::getLanguage();
9048
9049 /**
9050 * In case the extension doesn't support the specified language,
9051 * Joomla loads by default the default en-GB version.
9052 * So, we don't need to add a fallback.
9053 */
9054 $lang->load('com_vikappointments', $client, $tag, true);
9055
9056 /**
9057 * Reload system language too.
9058 *
9059 * @since 1.7
9060 */
9061 $lang->load('joomla', $client, $tag, true);
9062 }
9063 }
9064
9065 /**
9066 * Returns a list of the installed languages.
9067 *
9068 * @param boolean $assoc True to return an associative array with the language details,
9069 * false to obtain a linear array with the supported language tags
9070 * (added @since 1.7.1).
9071 *
9072 * @return array The languages list.
9073 */
9074 public static function getKnownLanguages($assoc = false)
9075 {
9076 // get default language
9077 $def_lang = self::getDefaultLanguage('site');
9078
9079 // get installed languages
9080 $known_languages = VAPApplication::getInstance()->getKnownLanguages();
9081
9082 $languages = array();
9083
9084 foreach ($known_languages as $k => $v)
9085 {
9086 if ($assoc)
9087 {
9088 $languages[$k] = $v;
9089 }
9090 else
9091 {
9092 if ($k == $def_lang)
9093 {
9094 // move default language in first position
9095 array_unshift($languages, $k);
9096 }
9097 else
9098 {
9099 // otherwise insert at the end
9100 array_push($languages, $k);
9101 }
9102 }
9103 }
9104
9105 return $languages;
9106 }
9107
9108 /**
9109 * Translates a list of services groups.
9110 *
9111 * @param array &$groups A list of groups (objects or arrays).
9112 * @param string $lang An optional language to use. If not
9113 * specified, the current one will be used.
9114 *
9115 * @return void
9116 *
9117 * @since 1.7
9118 *
9119 * @uses translateRecords()
9120 */
9121 public static function translateServicesGroups(&$groups, $lang = null)
9122 {
9123 self::translateRecords('group', $groups, $lang);
9124 }
9125
9126 /**
9127 * Translates a list of services.
9128 *
9129 * @param array &$services A list of services (objects or arrays).
9130 * @param string $lang An optional language to use. If not
9131 * specified, the current one will be used.
9132 *
9133 * @return void
9134 *
9135 * @since 1.7
9136 *
9137 * @uses translateRecords()
9138 */
9139 public static function translateServices(&$services, $lang = null)
9140 {
9141 self::translateRecords('service', $services, $lang);
9142 }
9143
9144 /**
9145 * Translates a list of employees.
9146 *
9147 * @param array &$employees A list of employees (objects or arrays).
9148 * @param string $lang An optional language to use. If not
9149 * specified, the current one will be used.
9150 *
9151 * @return void
9152 *
9153 * @since 1.7
9154 *
9155 * @uses translateRecords()
9156 */
9157 public static function translateEmployees(&$employees, $lang = null)
9158 {
9159 self::translateRecords('employee', $employees, $lang);
9160 }
9161
9162 /**
9163 * Translates a list of payments.
9164 *
9165 * @param array &$payments A list of payments (objects or arrays).
9166 * @param string $lang An optional language to use. If not
9167 * specified, the current one will be used.
9168 *
9169 * @return void
9170 *
9171 * @since 1.7
9172 *
9173 * @uses translateRecords()
9174 */
9175 public static function translatePayments(&$payments, $lang = null)
9176 {
9177 self::translateRecords('payment', $payments, $lang);
9178 }
9179
9180 /**
9181 * Translates a list of subscriptions.
9182 *
9183 * @param array &$subscriptions A list of subscriptions (objects or arrays).
9184 * @param string $lang An optional language to use. If not
9185 * specified, the current one will be used.
9186 *
9187 * @return void
9188 *
9189 * @since 1.7
9190 *
9191 * @uses translateRecords()
9192 */
9193 public static function translateSubscriptions(&$subscriptions, $lang = null)
9194 {
9195 self::translateRecords('subscription', $subscriptions, $lang);
9196 }
9197
9198 /**
9199 * Translates a list of generic translatable records.
9200 *
9201 * @param string $table The translatable table name.
9202 * @param array &$records A list of records (objects or arrays).
9203 * @param string $lang An optional language to use. If not
9204 * specified, the current one will be used.
9205 *
9206 * @return void
9207 *
9208 * @since 1.7
9209 */
9210 public static function translateRecords($table, &$records, $lang = null)
9211 {
9212 // make sure multi-language is supported
9213 if (!$records || !static::isMultilanguage())
9214 {
9215 return false;
9216 }
9217
9218 if (!$lang)
9219 {
9220 // get current language tag if not specified
9221 $lang = JFactory::getLanguage()->getTag();
9222 }
9223
9224 // get translator
9225 $translator = VAPFactory::getTranslator();
9226
9227 // get translation table foreign key
9228 $fk = $translator->getTable($table)->getLinkedPrimaryKey();
9229
9230 if (!is_array($records))
9231 {
9232 // always use an array
9233 $records = array($records);
9234 // remember that the argument was NOT an array
9235 $was_array = false;
9236 }
9237 else
9238 {
9239 // remember that the argument was already an array
9240 $was_array = true;
9241 }
9242
9243 // extract IDs from records
9244 $ids = array();
9245
9246 foreach ($records as $item)
9247 {
9248 $ids[] = is_object($item) ? $item->{$fk} : $item[$fk];
9249 }
9250
9251 // preload table translations
9252 $tbLang = $translator->load($table, array_unique($ids), $lang);
9253
9254 foreach ($records as &$item)
9255 {
9256 $id = is_object($item) ? $item->{$fk} : $item[$fk];
9257
9258 // translate record for the given language
9259 $tx = $tbLang->getTranslation($id, $lang);
9260
9261 if ($tx)
9262 {
9263 // get translations columns lookup
9264 $columns = $tbLang->getContentColumns($original = true);
9265
9266 // iterate all the columns
9267 foreach ($columns as $colName)
9268 {
9269 // inject translation within the record
9270 if (is_object($item))
9271 {
9272 // treat record as object
9273 $item->{$colName} = $tx->{$colName};
9274 }
9275 else
9276 {
9277 // treat record as associative array
9278 $item[$colName] = $tx->{$colName};
9279 }
9280 }
9281 }
9282 }
9283
9284 if (!$was_array)
9285 {
9286 // revert to original value
9287 $records = array_shift($records);
9288 }
9289 }
9290
9291 /**
9292 * Returns a list of translated groups.
9293 *
9294 * @param mixed $id The ID of the record or a list of IDs. Leave empty to retrieve all the records.
9295 * @param string $tag The language tag. Leave empty to get the default one.
9296 * @param mixed $dbo The database object.
9297 *
9298 * @return array The translated groups. Each object can be easily accessed by using its PK.
9299 *
9300 * @uses getTranslatedObjects()
9301 *
9302 * @deprecated 1.8 Without replacement.
9303 */
9304 public static function getTranslatedGroups($id = null, $tag = null, $dbo = null)
9305 {
9306 return self::getTranslatedObjects('group', 'id_group', $id, $tag, $dbo);
9307 }
9308
9309 /**
9310 * Returns a list of translated employees groups.
9311 *
9312 * @param mixed $id The ID of the record or a list of IDs. Leave empty to retrieve all the records.
9313 * @param string $tag The language tag. Leave empty to get the default one.
9314 * @param mixed $dbo The database object.
9315 *
9316 * @return array The translated groups. Each object can be easily accessed by using its PK.
9317 *
9318 * @uses getTranslatedObjects()
9319 *
9320 * @deprecated 1.8 Without replacement.
9321 */
9322 public static function getTranslatedEmployeeGroups($id = null, $tag = null, $dbo = null)
9323 {
9324 return self::getTranslatedObjects('empgroup', 'id_empgroup', $id, $tag, $dbo);
9325 }
9326
9327 /**
9328 * Returns a list of translated services.
9329 *
9330 * @param mixed $id The ID of the record or a list of IDs. Leave empty to retrieve all the records.
9331 * @param string $tag The language tag. Leave empty to get the default one.
9332 * @param mixed $dbo The database object.
9333 *
9334 * @return array The translated services. Each object can be easily accessed by using its PK.
9335 *
9336 * @uses getTranslatedObjects()
9337 *
9338 * @deprecated 1.8 Without replacement.
9339 */
9340 public static function getTranslatedServices($id = null, $tag = null, $dbo = null)
9341 {
9342 return self::getTranslatedObjects('service', 'id_service', $id, $tag, $dbo);
9343 }
9344
9345 /**
9346 * Returns a list of translated employees.
9347 *
9348 * @param mixed $id The ID of the record or a list of IDs. Leave empty to retrieve all the records.
9349 * @param string $tag The language tag. Leave empty to get the default one.
9350 * @param mixed $dbo The database object.
9351 *
9352 * @return array The translated employees. Each object can be easily accessed by using its PK.
9353 *
9354 * @uses getTranslatedObjects()
9355 *
9356 * @deprecated 1.8 Without replacement.
9357 */
9358 public static function getTranslatedEmployees($id = null, $tag = null, $dbo = null)
9359 {
9360 return self::getTranslatedObjects('employee', 'id_employee', $id, $tag, $dbo);
9361 }
9362
9363 /**
9364 * Returns a list of translated options.
9365 *
9366 * @param mixed $id The ID of the record or a list of IDs. Leave empty to retrieve all the records.
9367 * @param string $tag The language tag. Leave empty to get the default one.
9368 * @param mixed $dbo The database object.
9369 *
9370 * @return array The translated options. Each object can be easily accessed by using its PK.
9371 *
9372 * @uses getTranslatedObjects()
9373 *
9374 * @deprecated 1.8 Without replacement.
9375 */
9376 public static function getTranslatedOptions($id = null, $tag = null, $dbo = null)
9377 {
9378 $options = self::getTranslatedObjects('option', 'id_option', $id, $tag, $dbo);
9379
9380 foreach ($options as $k => $opt)
9381 {
9382 // decode variations, which are stored in JSON format
9383 $options[$k]['vars_json'] = json_decode($opt['vars_json'], true);
9384 }
9385
9386 return $options;
9387 }
9388
9389 /**
9390 * Returns a list of translated packages.
9391 *
9392 * @param mixed $id The ID of the record or a list of IDs. Leave empty to retrieve all the records.
9393 * @param string $tag The language tag. Leave empty to get the default one.
9394 * @param mixed $dbo The database object.
9395 *
9396 * @return array The translated packages. Each object can be easily accessed by using its PK.
9397 *
9398 * @uses getTranslatedObjects()
9399 *
9400 * @deprecated 1.8 Without replacement.
9401 */
9402 public static function getTranslatedPackages($id = null, $tag = null, $dbo = null)
9403 {
9404 return self::getTranslatedObjects('package', 'id_package', $id, $tag, $dbo);
9405 }
9406
9407 /**
9408 * Returns a list of translated packages groups.
9409 *
9410 * @param mixed $id The ID of the record or a list of IDs. Leave empty to retrieve all the records.
9411 * @param string $tag The language tag. Leave empty to get the default one.
9412 * @param mixed $dbo The database object.
9413 *
9414 * @return array The translated groups. Each object can be easily accessed by using its PK.
9415 *
9416 * @uses getTranslatedObjects()
9417 *
9418 * @deprecated 1.8 Without replacement.
9419 */
9420 public static function getTranslatedPackGroups($id = null, $tag = null, $dbo = null)
9421 {
9422 return self::getTranslatedObjects('package_group', 'id_package_group', $id, $tag, $dbo);
9423 }
9424
9425 /**
9426 * Returns a list of translated subscriptions.
9427 *
9428 * @param mixed $id The ID of the record or a list of IDs. Leave empty to retrieve all the records.
9429 * @param string $tag The language tag. Leave empty to get the default one.
9430 * @param mixed $dbo The database object.
9431 *
9432 * @return array The translated subscriptions. Each object can be easily accessed by using its PK.
9433 *
9434 * @uses getTranslatedObjects()
9435 *
9436 * @since 1.6
9437 *
9438 * @deprecated 1.8 Without replacement.
9439 */
9440 public static function getTranslatedSubscriptions($id = null, $tag = null, $dbo = null)
9441 {
9442 return self::getTranslatedObjects('subscr', 'id_subscr', $id, $tag, $dbo);
9443 }
9444
9445 /**
9446 * Returns a list of translated payments.
9447 *
9448 * @param mixed $id The ID of the record or a list of IDs. Leave empty to retrieve all the records.
9449 * @param string $tag The language tag. Leave empty to get the default one.
9450 * @param mixed $dbo The database object.
9451 *
9452 * @return array The translated payments. Each object can be easily accessed by using its PK.
9453 *
9454 * @uses getTranslatedObjects()
9455 *
9456 * @since 1.6
9457 *
9458 * @deprecated 1.8 Without replacement.
9459 */
9460 public static function getTranslatedPayments($id = null, $tag = null, $dbo = null)
9461 {
9462 return self::getTranslatedObjects('payment', 'id_payment', $id, $tag, $dbo);
9463 }
9464
9465 /**
9466 * Returns a list of translated objects.
9467 *
9468 * @param string $object The table suffix of the objects to get.
9469 * @param string $column The column name used to match the specified IDs.
9470 * @param mixed $id The ID of the record or a list of IDs. Leave empty to retrieve all the records.
9471 * @param string $tag The language tag. Leave empty to get the default one.
9472 * @param mixed $dbo The database object.
9473 *
9474 * @return array The translated objects. Each object can be easily accessed by using its PK.
9475 *
9476 * @deprecated 1.8 Without replacement.
9477 */
9478 private static function getTranslatedObjects($object, $column, $id = null, $tag = null, $dbo = null)
9479 {
9480 if (!self::isMultilanguage())
9481 {
9482 return array();
9483 }
9484
9485 if (!$tag)
9486 {
9487 $tag = JFactory::getLanguage()->getTag();
9488 }
9489
9490 if (!$dbo)
9491 {
9492 $dbo = JFactory::getDbo();
9493 }
9494
9495 $lim = null;
9496
9497 $q = $dbo->getQuery(true)
9498 ->select('*')
9499 ->from($dbo->qn('#__vikappointments_lang_' . $object))
9500 ->where($dbo->qn('tag') . ' = ' . $dbo->q($tag));
9501
9502 if ($id)
9503 {
9504 if (is_array($id))
9505 {
9506 $q->where($dbo->qn($column) . ' IN (' . implode(',', array_map('intval', $id)) . ')');
9507 $lim = count($id);
9508 }
9509 else
9510 {
9511 $q->where($dbo->qn($column) . ' = ' . (int) $id);
9512 $lim = 1;
9513 }
9514 }
9515
9516 $dbo->setQuery($q, 0, $lim);
9517
9518 $list = [];
9519
9520 foreach ($dbo->loadAssocList() as $r)
9521 {
9522 $list[$r[$column]] = $r;
9523 }
9524
9525 return $list;
9526 }
9527
9528 /**
9529 * Obtains the translated value. If the element is not translated, the default one will be used.
9530 *
9531 * @param integer $id The ID of the record to translate.
9532 * @param array $original The original record (associative array).
9533 * @param array $transl The array containing all the translations.
9534 * @param string $match1 The column name of the record to translate.
9535 * @param string $match2 The column name of the translation.
9536 * @param mixed $default The default value to return in case it is empty.
9537 *
9538 * @return mixed The translated value.
9539 *
9540 * @deprecated 1.8 Without replacement.
9541 */
9542 public static function getTranslation($id, $original, $transl, $match1, $match2, $default = '')
9543 {
9544 if (empty($transl[$id][$match2]))
9545 {
9546 // the record doesn't own a translation of this column
9547 if (!empty($original[$match1]))
9548 {
9549 // get the original value
9550 return $original[$match1];
9551 }
9552 else
9553 {
9554 // get the default value
9555 return $default;
9556 }
9557 }
9558
9559 // get the translated value
9560 return $transl[$id][$match2];
9561 }
9562
9563 /////////////////////////////////////////////
9564 ////////////// EMPLOYEES AREA ///////////////
9565 /////////////////////////////////////////////
9566
9567 /**
9568 * Returns the settings of the given employee.
9569 *
9570 * @return array The settings associative array.
9571 *
9572 * @deprecated 1.8 Use VAPEmployeeAuth::getSettings() instead.
9573 */
9574 public static function getEmployeeSettings()
9575 {
9576 return (array) VAPEmployeeAuth::getInstance()->getSettings();
9577 }
9578
9579 /**
9580 * Refreshes the employee settings stored within the user state.
9581 *
9582 * @return void
9583 *
9584 * @deprecated 1.8 Without replacement, since the settings are no more cached.
9585 */
9586 public static function refreshEmployeeSettings($id_employee)
9587 {
9588 // do nothing...
9589 }
9590
9591 // PDF
9592
9593 /**
9594 * Returns an array containing the invoice arguments.
9595 *
9596 * @param string $group The invoice group.
9597 *
9598 * @return object The invoice arguments.
9599 *
9600 * @deprecated 1.8 Use VAPInvoiceGenerator::getParams() instead.
9601 */
9602 public static function getPdfParams($group = 'appointments')
9603 {
9604 VAPLoader::import('libraries.invoice.factory');
9605 return VAPInvoiceFactory::getGenerator()->getParams();
9606 }
9607
9608 /**
9609 * Returns an object containing the invoice properties.
9610 *
9611 * @param string $group The invoice group.
9612 *
9613 * @return object The invoice properties.
9614 *
9615 * @deprecated 1.8 Use VAPInvoiceGenerator::getConstraints() instead.
9616 */
9617 public static function getPdfConstraints($group = 'appointments')
9618 {
9619 // load old constraints class for BC
9620 VAPLoader::registerAlias('lib.constraints', 'constraints');
9621 VAPLoader::import('pdf.constraints');
9622
9623 VAPLoader::import('libraries.invoice.factory');
9624 return VAPInvoiceFactory::getGenerator()->getConstraints();
9625 }
9626
9627 /**
9628 * Helper method used to generate the invoices related to the specified
9629 * order, which belong to the given group.
9630 *
9631 * @param mixed $order Either the order details object or an ID.
9632 * @param string $group The invoices group (appointments by default).
9633 *
9634 * @return boolean True on success, otherwise false.
9635 *
9636 * @since 1.6
9637 */
9638 public static function generateInvoice($order, $group = null)
9639 {
9640 // check whether the invoices should be automatically generated
9641 if (!VAPFactory::getConfig()->getBool('invoiceorders'))
9642 {
9643 // nope...
9644 return false;
9645 }
9646
9647 if (is_array($order))
9648 {
9649 /**
9650 * Extract order ID from given array for BC.
9651 *
9652 * @deprecated 1.8
9653 */
9654 $id_order = $order[0]['id'];
9655 }
9656 else if (is_object($order))
9657 {
9658 $id_order = $order->id;
9659 }
9660 else
9661 {
9662 $id_order = (int) $order;
9663 }
9664
9665 if (!$group)
9666 {
9667 $group = 'appointments';
9668 }
9669
9670 // prepare invoice data
9671 $data = array(
9672 'group' => $group,
9673 'id_order' => $id_order,
9674 );
9675
9676 // get invoice model
9677 $model = JModelVAP::getInstance('invoice');
9678 // generate the invoice and dispatch e-mail notification (if configured)
9679 $id = $model->save($data);
9680
9681 return (bool) $id;
9682 }
9683
9684 // EMPLOYEES FILTERING
9685
9686 /**
9687 * Extends the search query using the custom filters.
9688 *
9689 * @param mixed &$q The query builder object.
9690 * @param array $filters The associative array of filters.
9691 * @param string $alias The alias used for "employees" DB table.
9692 * @param mixed $dbo The database object.
9693 *
9694 * @return boolean True if the query has been altered, otherwise false.
9695 *
9696 * @since 1.6
9697 */
9698 public static function extendQueryWithCustomFilters(&$q, array $filters = array(), $alias = null, $dbo = null)
9699 {
9700 if (!$dbo)
9701 {
9702 $dbo = JFactory::getDbo();
9703 }
9704
9705 $lookup = array();
9706
9707 foreach ($filters as $k => $v)
9708 {
9709 if ($v && strpos($k, 'field_') === 0)
9710 {
9711 $lookup[] = substr($k, 6);
9712 }
9713 }
9714
9715 if (!$lookup)
9716 {
9717 // no custom filters
9718 return false;
9719 }
9720
9721 $lookup = array_map(array($dbo, 'q'), $lookup);
9722
9723 $q2 = $dbo->getQuery(true)
9724 ->select($dbo->qn('formname'))
9725 ->from($dbo->qn('#__vikappointments_custfields'))
9726 ->where(array(
9727 $dbo->qn('group') . ' = 1',
9728 $dbo->qn('formname') . ' IN (' . implode(',', $lookup) . ')',
9729 ));
9730
9731 $dbo->setQuery($q2);
9732 $fields = $dbo->loadColumn();
9733
9734 if (!$fields)
9735 {
9736 // no custom fields, possible hack attempt
9737 return false;
9738 }
9739
9740 foreach ($fields as $field)
9741 {
9742 $key = 'field_' . $field;
9743
9744 $q->where($dbo->qn(($alias ? $alias . '.' : '') . $key) . ' = ' . $dbo->q($filters[$key]));
9745 }
9746
9747 return true;
9748 }
9749
9750 // FRONT BUILDING
9751
9752 /**
9753 * Prepares the document related to the specified view.
9754 * Used also to implement OPEN GRAPH protocol and to include
9755 * global meta data.
9756 *
9757 * @param mixed $page The view object.
9758 *
9759 * @return void
9760 */
9761 public static function prepareContent($page)
9762 {
9763 VAPLoader::import('libraries.view.contents');
9764
9765 $handler = VAPViewContents::getInstance($page);
9766
9767 /**
9768 * Set the browser page title.
9769 *
9770 * @since 1.6.1
9771 */
9772 $handler->setPageTitle();
9773
9774 // show the page heading (if not provided, an empty string will be returned)
9775 $handler->getPageHeading(true);
9776
9777 // set the META description of the page
9778 $handler->setMetaDescription();
9779
9780 // set the META keywords of the page
9781 $handler->setMetaKeywords();
9782
9783 // set the META robots of the page
9784 $handler->setMetaRobots();
9785
9786 // create OPEN GRAPH protocol
9787 $handler->buildOpenGraph();
9788
9789 // create MICRODATA
9790 $handler->buildMicrodata();
9791 }
9792
9793 // USERS
9794
9795 /**
9796 * Tries to populate the custom fields values according to the details
9797 * of the currently logged-in user.
9798 *
9799 * @param array $list A list of custom fields.
9800 * @param array &$fields Where to inject the fetched data.
9801 * @param boolean $first True whether the first name is usually
9802 * specified before the last name.
9803 *
9804 * @return void
9805 *
9806 * @since 1.7
9807 */
9808 public static function populateFields(array $list, array &$fields, $first = true)
9809 {
9810 // we do not need to import this file because when we call this method we
9811 // are one step away from rendering the custom fields, so we can expect to
9812 // have that class already loaded
9813 VAPCustomFieldsRenderer::autoPopulate($fields, $list, $user = null, $first);
9814 }
9815
9816 /**
9817 * Helper method used to check if the current user is logged.
9818 *
9819 * @param mixed $user The user object.
9820 *
9821 * @return boolean True if logged, false otherwise.
9822 */
9823 public static function isUserLogged($user = null)
9824 {
9825 if (!$user)
9826 {
9827 $user = JFactory::getUser();
9828 }
9829
9830 return !$user->guest;
9831 }
9832
9833 /**
9834 * Helper method used to check if the provided arguments are correct
9835 * in order to register a new Joomla user.
9836 *
9837 * @param array $args The arguments to check.
9838 *
9839 * @return boolean True if correct, false otherwise.
9840 */
9841 public static function checkUserArguments(array $args)
9842 {
9843 if (!self::isUserLogged())
9844 {
9845 // proceed only in case the user is not logged
9846 return (
9847 !empty($args['firstname'])
9848 && !empty($args['lastname'])
9849 && !empty($args['username'])
9850 && !empty($args['password'])
9851 && self::validateUserEmail($args['email'])
9852 && !strcmp($args['password'], $args['confpassword'])
9853 );
9854 }
9855
9856 return false;
9857 }
9858
9859 /**
9860 * Validates the specified e-mail.
9861 *
9862 * @param string $email The email to check.
9863 *
9864 * @return boolean True if valid, false otherwise.
9865 */
9866 public static function validateUserEmail($email = '')
9867 {
9868 $isValid = true;
9869 $atIndex = strrpos($email, "@");
9870
9871 if (is_bool($atIndex) && !$atIndex)
9872 {
9873 return false;
9874 }
9875
9876 $domain = substr($email, $atIndex +1);
9877 $local = substr($email, 0, $atIndex);
9878 $localLen = strlen($local);
9879 $domainLen = strlen($domain);
9880
9881 if ($localLen < 1 || $localLen > 64)
9882 {
9883 // local part length exceeded or too short
9884 return false;
9885 }
9886
9887 if ($domainLen < 1 || $domainLen > 255)
9888 {
9889 // domain part length exceeded or too short
9890 return false;
9891 }
9892
9893 if ($local[0] == '.' || $local[$localLen -1] == '.')
9894 {
9895 // local part starts or ends with '.'
9896 return false;
9897 }
9898
9899 if (preg_match('/\\.\\./', $local))
9900 {
9901 // local part has two consecutive dots
9902 return false;
9903 }
9904
9905 if (!preg_match('/^[A-Za-z0-9\\-\\.]+$/', $domain))
9906 {
9907 // character not valid in domain part
9908 return false;
9909 }
9910
9911 if (preg_match('/\\.\\./', $domain))
9912 {
9913 // domain part has two consecutive dots
9914 return false;
9915 }
9916
9917 if (!preg_match('/^(\\\\.|[A-Za-z0-9!#%&`_=\\/$\'*+?^{}|~.-])+$/', str_replace("\\\\", "", $local)))
9918 {
9919 // character not valid in local part unless local part is quoted
9920 if (!preg_match('/^"(\\\\"|[^"])+"$/', str_replace("\\\\", "", $local)))
9921 {
9922 return false;
9923 }
9924 }
9925
9926 if (!checkdnsrr($domain, "MX") && !checkdnsrr($domain, "A"))
9927 {
9928 // domain not found in DNS
9929 return false;
9930 }
9931
9932 return true;
9933 }
9934
9935 /**
9936 * Registers a new Joomla User with the details specified in the given $args associative array.
9937 *
9938 * @param array $args The user details.
9939 * @param integer $type The registration type (for employee [1] or for users [2]).
9940 *
9941 * @return mixed The user ID on success, false on failure,
9942 * the string status during the activation.
9943 *
9944 * @since 1.0
9945 * @since 1.7 Alias for deprecated createNewJoomlaUser() method.
9946 */
9947 public static function createNewUserAccount(array $args, $type = 2)
9948 {
9949 $app = JFactory::getApplication();
9950
9951 // load com_users site language
9952 JFactory::getLanguage()->load('com_users', JPATH_SITE, JFactory::getLanguage()->getTag(), true);
9953
9954 // save registration data within the user state, so that in case of
9955 // errors we can recover the entered details to auto-fill the form
9956 $app->setUserState('vap.cms.user.register', $args);
9957
9958 if (VersionListener::isJoomla())
9959 {
9960 /**
9961 * Autoload the form fields of com_users to avoid fatal errors, since Joomla 3.9.27
9962 * seems to autoload the model forms/fields according to the current component.
9963 *
9964 * @since 1.7
9965 */
9966 JForm::addFormPath(JPATH_SITE . '/components/com_users/models/forms');
9967
9968 /**
9969 * Joomla 4.0 moved the XML forms into a different folder.
9970 *
9971 * @since 1.7.1
9972 */
9973 JForm::addFormPath(JPATH_SITE . '/components/com_users/forms');
9974 }
9975
9976 // load UsersModelRegistration
9977 JModelLegacy::addIncludePath(JPATH_SITE . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'com_users' . DIRECTORY_SEPARATOR . 'models');
9978 $model = JModelLegacy::getInstance('registration', 'UsersModel');
9979
9980 // adapt data for model
9981 $args['name'] = trim($args['firstname'] . ' ' . $args['lastname']);
9982 $args['email1'] = $args['email'];
9983 $args['password1'] = $args['password'];
9984 $args['block'] = 0;
9985
9986 if ($type == self::REGISTER_EMPLOYEE)
9987 {
9988 $args['groups'] = array(VAPEmployeeAreaManager::getSignUpUserGroup());
9989 }
9990
9991 /**
9992 * Attempt to hijack the Privacy Policy plugin to auto-flag
9993 * the privacy consent of the newly registered user.
9994 *
9995 * @since 1.7
9996 */
9997
9998 // get current request arguments
9999 $option = $app->input->get('option');
10000 $task = $app->input->get->get('task');
10001 $form = $app->input->post->get('jform', array());
10002
10003 if (VAPFactory::getConfig()->getBool('gdpr'))
10004 {
10005 // force privacy consent in case GDPR setting was enabled
10006 $form['privacyconsent'] = array('privacy' => 1);
10007 }
10008
10009 // hijack the Privacy Policy plugin condition
10010 $app->input->set('option', 'com_users');
10011 $app->input->get->set('task', 'registration.register');
10012 $app->input->post->set('jform', $form);
10013
10014 /**
10015 * It is now possible to validate the password against the com_users configuration.
10016 * Compatible only with J4.x
10017 *
10018 * @since 1.7.4
10019 */
10020 if (VersionListener::isJoomla4x())
10021 {
10022 // obtain com_users registration form
10023 $form = $model->getForm();
10024
10025 if ($form)
10026 {
10027 try
10028 {
10029 // validate the password against the com_users configuration
10030 $validate = $form->getField('password1')->validate($args['password']);
10031
10032 if ($validate instanceof Exception)
10033 {
10034 $app->enqueueMessage($validate->getMessage(), 'error');
10035 return false;
10036 }
10037 }
10038 catch (Throwable $t)
10039 {
10040 // ignore in case of fatal errors
10041 }
10042 }
10043 }
10044
10045 // register user
10046 $return = $model->register($args);
10047
10048 // restore previous request arguments
10049 $app->input->set('option', $option);
10050 $app->input->get->set('task', $task);
10051
10052 if ($return === false)
10053 {
10054 // impossible to save the user
10055 $app->enqueueMessage($model->getError(), 'error');
10056 }
10057 else if ($return === 'adminactivate')
10058 {
10059 // user saved: admin activation required
10060 $app->enqueueMessage(JText::translate('COM_USERS_REGISTRATION_COMPLETE_VERIFY'));
10061 }
10062 else if ($return === 'useractivate')
10063 {
10064 // user saved: self activation required
10065 $app->enqueueMessage(JText::translate('COM_USERS_REGISTRATION_COMPLETE_ACTIVATE'));
10066 }
10067 else
10068 {
10069 // user saved: can login immediately
10070 $app->enqueueMessage(JText::translate('COM_USERS_REGISTRATION_SAVE_SUCCESS'));
10071 }
10072
10073 if ($return !== false)
10074 {
10075 // unset user registration data on success
10076 $app->setUserState('vap.cms.user.register', null);
10077 }
10078
10079 return $return;
10080 }
10081
10082 /**
10083 * Registers a new Joomla User with the details
10084 * specified in the given $args associative array.
10085 *
10086 * @param array $args The user details.
10087 * @param integer $type The registration type (for employee [1] or for users [2]).
10088 *
10089 * @return mixed The user ID on success, false on failure,
10090 * the string status during the activation.
10091 *
10092 * @deprecated 1.8 Use VikAppointments::createNewUserAccount() instead.
10093 * Get rid of "Joomla" word whenever is possible.
10094 */
10095 public static function createNewJoomlaUser(array $args, $type = 2)
10096 {
10097 return static::createNewUserAccount($args, $type);
10098 }
10099
10100 const REGISTER_EMPLOYEE = 1;
10101 const REGISTER_USERS = 2;
10102 }
10103