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 / libraries / models / restrictions.php
vikappointments / site / helpers / libraries / models Last commit date
conversion.php 3 days ago customer.php 3 days ago customfields.php 3 days ago index.html 3 days ago locations.php 3 days ago orderstatus.php 3 days ago restrictions.php 3 days ago specialrates.php 3 days ago statistics.php 3 days ago subscriptions.php 3 days ago
restrictions.php
402 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 special restrictions class handler.
16 *
17 * @since 1.6.5
18 */
19 abstract class VAPSpecialRestrictions
20 {
21 /**
22 * Map used to cache the user appointments.
23 *
24 * @var array
25 */
26 protected static $orders = array();
27
28 /**
29 * Checks whether the current user is still allowed
30 * to book the selected service.
31 *
32 * @param integer $id The service ID.
33 * @param integer $checkin The check-in UNIX timestamp.
34 * @param mixed &$fail It is possible to use this reference
35 * to obtain the restriction that failed.
36 * @param boolean $add True if the service has to be added, otherwise
37 * false is the service is already in the cart.
38 *
39 * @return boolean True if allowed, false otherwise.
40 */
41 public static function canBookService($id, $checkin, &$fail = null, $add = true)
42 {
43 // get compatible restrictions
44 $restrictions = static::getRestrictions($id);
45
46 if (!$restrictions)
47 {
48 // no restrictions available, booking allowed
49 return true;
50 }
51
52 // iterate restriction
53 foreach ($restrictions as $restr)
54 {
55 // make sure the user didn't reach yet the thereshold
56 if (!static::validateRestriction($restr, $checkin, $add))
57 {
58 // assign restriction to $fail argument
59 $fail = $restr;
60
61 // threshold reached, not allowed to book
62 return false;
63 }
64 }
65
66 // booking allowed
67 return true;
68 }
69
70 /**
71 * Validates the restriction against the user details.
72 *
73 * @param object $restr The restriction object.
74 * @param string $checkin The check-in in military format.
75 * @param boolean $add True if the service has to be added, otherwise
76 * false is the service is already in the cart.
77 *
78 * @return boolean True in case the booking is allowed, false otherwise.
79 */
80 public static function validateRestriction($restr, $checkin, $add = true)
81 {
82 /**
83 * Trigger event to allow the plugins to alter the validation of a booking restriction.
84 *
85 * It is possible to return FALSE to consider the restriction as applied and prevent the
86 * customer from booking the selected service. Otherwise it is possible to return TRUE
87 * to consider the booking process valid without relying on the default validation
88 * process applied by the system. Return null to fallback to the default behavior.
89 *
90 * @param object $restr The restriction object.
91 * @param string $checkin The check-in in military format.
92 * @param boolean $add True if the service has to be added, otherwise
93 * false is the service is already in the cart.
94 *
95 * @return bool|null False to prevent the booking process, true to allow it.
96 * Otherwise null to preserve the default behavior.
97 *
98 * @since 1.7.7
99 */
100 $customResult = VAPFactory::getEventDispatcher()->falseOrTrue('onBeforeValidateServiceRestriction', [$restr, $checkin, $add]);
101
102 if ($customResult !== null)
103 {
104 // immediately return the result fetched by the attached plugin
105 return $customResult;
106 }
107
108 if ($restr->mode == 1)
109 {
110 // use current date
111 $checkin = 'now';
112 $column = 'createdon';
113 }
114 else
115 {
116 // use check-in date
117 $column = 'checkin_ts';
118 }
119
120 // get checkin date details
121 $date = JFactory::getDate($checkin);
122
123 // get current user
124 $user = JFactory::getUser();
125
126 if ($user->guest)
127 {
128 // in case of guest user, immediately abort
129 return false;
130 }
131
132 // get appointments of current user
133 $orders = static::getAppointments($user->id);
134
135 $count = 0;
136
137 // count orders that match the specified interval
138 foreach ($orders as $o)
139 {
140 // make sure the service should be considered by this restriction
141 if (!$restr->services || in_array($o->id_service, $restr->services))
142 {
143 // service ok, make sure the checkin is within the
144 // interval defined by the restriction
145
146 $range = new stdClass;
147 $range->start = clone $date;
148 $range->end = clone $date;
149
150 switch ($restr->interval)
151 {
152 case 'day':
153 $range->start->modify('00:00:00');
154 $range->end->modify('23:59:59');
155 break;
156
157 case 'week':
158 // week starts from Monday
159 $w = (int) $date->format('w');
160 $w = ($w == 0 ? 7 : $w) - 1;
161
162 $range->start->modify('-' . $w . ' days 00:00:00');
163 $range->end->modify('-' . $w . ' days 23:59:59');
164 $range->end->modify('+6 days');
165 break;
166
167 case 'week2':
168 // week starts from Monday
169 $w = (int) $date->format('w');
170 $w = ($w == 0 ? 7 : $w) - 1;
171
172 // get week of the year and check if even/odd
173 if ((int) $date->format('W') % 2 == 0)
174 {
175 // subtract 7 days in case of even week (go to previous week)
176 $sub = 7;
177 // extend to the end of the current week
178 $add = 6;
179 }
180 else
181 {
182 // go to the beginning of the current week
183 $sub = 0;
184 // exetend to the end of the next week
185 $add = 13;
186 }
187
188 $range->start->modify('-' . ($w + $sub) . ' days 00:00:00');
189 $range->end->modify('-' . $w . ' days 23:59:59');
190 $range->end->modify('+' . $add . ' days');
191 break;
192
193 case 'month':
194 $range->start->modify($date->format('Y-m-01') . ' 00:00:00');
195 $range->end->modify($date->format('Y-m-t') . ' 23:59:59');
196 break;
197
198 case 'month2':
199 case 'month3':
200 case 'month4':
201 case 'month6':
202 // extract index from name
203 $index = (int) preg_replace("/^month/", '', $restr->interval);
204 // find initial month
205 $mon = (int) $date->format('m');
206 $mon = $mon - (($mon - 1) % $index);
207
208 $range->start->modify($date->format("Y-{$mon}-01") . ' 00:00:00');
209 $range->end = clone $range->start;
210 $range->end->modify('+' . ($mon + $index) . ' months');
211 $range->end->modify('-1 day 23:59:59');
212 break;
213
214 case 'year':
215 $range->start->modify('Y-01-01 00:00:00');
216 $range->end->modify('Y-12-31 23:59:59');
217 break;
218
219 default:
220 // abort, restriction interval not supported
221 return false;
222 }
223
224 // get timestamp
225 $ts = $o->{$column};
226
227 if ($range->start->format('Y-m-d H:i:s') <= $ts && $ts <= $range->end->format('Y-m-d H:i:s'))
228 {
229 // increase counter in the the appointment stays
230 // between the fatched interval
231 $count++;
232 }
233 }
234 }
235
236 if (!$add)
237 {
238 // decrease count by one in case the service checked
239 // is already in the cart
240 $count--;
241 }
242
243 // make sure the total count of appointments doesn't
244 // exceed the maximum threshold defined by the restriction
245 return $count < (int) $restr->maxapp;
246 }
247
248 /**
249 * Returns a list of special restrictions to be applied
250 * to the specified service. The method also filters the
251 * restrictions according to the allowed user groups.
252 *
253 * @param integer $id The service ID.
254 *
255 * @return array A list of restrictions.
256 */
257 public static function getRestrictions($id)
258 {
259 static $restrictions = null;
260
261 // load restrictions only once
262 if (is_null($restrictions))
263 {
264 $restrictions = [];
265
266 $dbo = JFactory::getDbo();
267
268 $q = $dbo->getQuery(true)
269 ->select('r.*')
270 ->select($dbo->qn('a.id_service'))
271 ->from($dbo->qn('#__vikappointments_special_restrictions', 'r'))
272 ->leftjoin($dbo->qn('#__vikappointments_ser_restr_assoc', 'a') . ' ON ' . $dbo->qn('a.id_restriction') . ' = ' . $dbo->qn('r.id'))
273 ->where($dbo->qn('r.published') . ' = 1')
274 ->order($dbo->qn('r.maxapp') . ' DESC');
275
276 $dbo->setQuery($q);
277
278 foreach ($dbo->loadObjectList() as $r)
279 {
280 if (!isset($restrictions[$r->id]))
281 {
282 // convert the usergroups (separated by a comma) into an array
283 $r->usergroups = strlen($r->usergroups) ? explode(',', $r->usergroups) : array();
284 // init services list
285 $r->services = array();
286
287 $restrictions[$r->id] = $r;
288 }
289
290 if ($r->id_service)
291 {
292 $restrictions[$r->id]->services[] = $r->id_service;
293 }
294 }
295
296 // get rid of keys
297 $restrictions = array_values($restrictions);
298 }
299
300 $user = JFactory::getUser();
301
302 // filter restrictions by service and user group
303 return array_filter($restrictions, function($r) use ($id, $user)
304 {
305 // make sure the restriction support the service
306 if ($r->services && !in_array($id, $r->services))
307 {
308 // service not supported
309 return false;
310 }
311
312 // check if the intersection between the arrays returns
313 // at least an element (in common)
314 if ($r->usergroups && !array_intersect($user->groups, $r->usergroups))
315 {
316 // user groups not supported
317 return false;
318 }
319
320 // restriction compatible
321 return true;
322 });
323 }
324
325 /**
326 * Returns the list of all the appointments already
327 * booked by the customer.
328 *
329 * @param integer $id The user ID.
330 *
331 * @return array A list of orders.
332 */
333 public static function getAppointments($id_user = null)
334 {
335 if (is_null($id_user))
336 {
337 $user = JFactory::getUser();
338 // get ID of current user
339 $id_user = $user->guest ? 0 : $user->id;
340 }
341
342 if (!$id_user)
343 {
344 // return empty list in case of guest user
345 return array();
346 }
347
348 if (!isset(static::$orders[$id_user]))
349 {
350 $dbo = JFactory::getDbo();
351
352 // get any reserved codes
353 $reserved = JHtml::fetch('vaphtml.status.find', 'code', array('appointments' => 1, 'reserved' => 1));
354
355 $q = $dbo->getQuery(true)
356 ->select($dbo->qn('r.id'))
357 ->select($dbo->qn('r.checkin_ts'))
358 ->select($dbo->qn('r.createdon'))
359 ->select($dbo->qn('r.id_service'))
360 ->from($dbo->qn('#__vikappointments_reservation', 'r'))
361 ->leftjoin($dbo->qn('#__vikappointments_users', 'c') . ' ON ' . $dbo->qn('r.id_user') . ' = ' . $dbo->qn('c.id'))
362 // exclude multi-orders (parent)
363 ->where($dbo->qn('id_parent') . ' <> -1');
364
365 // take only reserved appointments
366 if ($reserved)
367 {
368 // filter by reserved status
369 $q->where($dbo->qn('r.status') . ' IN (' . implode(',', array_map(array($dbo, 'q'), $reserved)) . ')');
370 }
371
372 // search by author or assigned customer
373 $q->andWhere(array(
374 $dbo->qn('c.jid') . ' = ' . (int) $id_user,
375 '(' . $dbo->qn('r.createdby') . ' = ' . (int) $id_user . ' AND ' . $dbo->qn('c.jid') . ' IS NULL)',
376 ), 'OR');
377
378 $dbo->setQuery($q);
379 static::$orders[$id_user] = $dbo->loadObjectList();
380 }
381
382 // load cart instance
383 $cart = JModelVAP::getInstance('cart')->getCart();
384
385 $cart_list = array();
386
387 // extract appointments from cart
388 foreach ($cart->getItemsList() as $item)
389 {
390 $tmp = new stdClass;
391 $tmp->id_service = $item->getServiceID();
392 $tmp->checkin_ts = $item->getCheckinDate();
393 $tmp->createdon = JFactory::getDate();
394
395 $cart_list[] = $tmp;
396 }
397
398 // merge customer orders and appointments in cart
399 return array_merge(static::$orders[$id_user], $cart_list);
400 }
401 }
402