PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / trunk
VikAppointments Services Booking Calendar vtrunk
trunk 1.2.17 1.2.18 1.2.19
vikappointments / admin / models / customer.php
vikappointments / admin / models Last commit date
apiban.php 4 years ago apilog.php 2 years ago apiplugin.php 4 years ago apiuser.php 2 years ago apiuseroptions.php 2 years ago backup.php 4 months ago caldays.php 1 month ago calendar.php 1 month ago city.php 4 years ago closure.php 1 month ago configapp.php 4 years ago configcldays.php 4 years ago configcron.php 4 years ago configemp.php 4 years ago configsmsapi.php 4 years ago configuration.php 4 months ago conversion.php 4 years ago country.php 2 years ago coupon.php 2 years ago couponemployee.php 4 years ago coupongroup.php 2 years ago couponservice.php 4 years ago cronjob.php 2 years ago cronjoblog.php 4 years ago customer.php 4 months ago customf.php 2 years ago customfservice.php 4 years ago customizer.php 4 years ago empgroup.php 2 years ago employee.php 2 years ago empsettings.php 4 years ago file.php 4 years ago findreservation.php 1 month ago group.php 2 years ago import.php 4 years ago index.html 4 years ago invoice.php 1 month ago langcustomf.php 4 years ago langempgroup.php 4 years ago langemployee.php 4 years ago langgroup.php 4 years ago langmedia.php 4 years ago langoption.php 2 years ago langoptiongroup.php 4 years ago langoptionvar.php 4 years ago langpackage.php 4 years ago langpackgroup.php 4 years ago langpayment.php 4 years ago langservice.php 4 years ago langstatuscode.php 4 years ago langsubscr.php 4 years ago langtax.php 2 years ago langtaxrule.php 4 years ago location.php 2 years ago mailtext.php 2 years ago makerecurrence.php 1 month ago media.php 2 years ago multiorder.php 1 month ago option.php 1 year ago optiongroup.php 2 years ago optionvar.php 1 year ago orderstatus.php 2 years ago package.php 2 years ago packageservice.php 4 years ago packgroup.php 2 years ago packorder.php 2 years ago packorderitem.php 1 month ago payment.php 2 years ago rate.php 2 years ago reportsemp.php 4 months ago reportsser.php 4 months ago reservation.php 1 month ago resoptassoc.php 2 years ago restriction.php 2 years ago review.php 3 years ago serempassoc.php 1 year ago seroptassoc.php 4 years ago serrateassoc.php 4 years ago serrestrassoc.php 4 years ago service.php 2 years ago state.php 2 years ago statswidget.php 2 years ago statuscode.php 2 years ago subscription.php 1 month ago subscrorder.php 1 month ago tag.php 4 years ago tax.php 2 years ago taxrule.php 2 years ago updateprogram.php 4 years ago usernote.php 2 years ago waitinglist.php 1 month ago webhook.php 1 year ago worktime.php 1 month ago
customer.php
659 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 VAPLoader::import('libraries.mvc.model');
15
16 /**
17 * VikAppointments customer model.
18 *
19 * @since 1.7
20 */
21 class VikAppointmentsModelCustomer extends JModelVAP
22 {
23 /**
24 * The list view pagination object.
25 *
26 * @var JPagination
27 */
28 protected $pagination = null;
29
30 /**
31 * The total number of fetched rows.
32 *
33 * @var integer
34 */
35 protected $total = 0;
36
37 /**
38 * Basic save implementation.
39 *
40 * @param mixed $data Either an array or an object of data to save.
41 *
42 * @return mixed The ID of the record on success, false otherwise.
43 */
44 public function save($data)
45 {
46 $data = (array) $data;
47
48 if (empty($data['billing_name']) && isset($data['purchaser_nominative']))
49 {
50 // used the reservation notation, normalize it
51 $data['billing_name'] = $data['purchaser_nominative'];
52 unset($data['purchaser_nominative']);
53 }
54
55 if (empty($data['billing_mail']) && isset($data['purchaser_mail']))
56 {
57 // used the reservation notation, normalize it
58 $data['billing_mail'] = $data['purchaser_mail'];
59 unset($data['purchaser_mail']);
60 }
61
62 if (empty($data['billing_phone']) && isset($data['purchaser_phone']))
63 {
64 // used the reservation notation, normalize it
65 $data['billing_phone'] = $data['purchaser_phone'];
66 unset($data['purchaser_phone']);
67 }
68
69 if (empty($data['country_code']) && isset($data['purchaser_country']))
70 {
71 // used the reservation notation, normalize it
72 $data['country_code'] = $data['purchaser_country'];
73 unset($data['purchaser_country']);
74 }
75
76 $item = null;
77
78 if (empty($data['id']) && !empty($data['jid']))
79 {
80 // user logged-in, check whether we already have an existing customer
81 $item = $this->getItem(array('jid' => $data['jid']));
82
83 if ($item)
84 {
85 // customer found, do update
86 $data['id'] = $item->id;
87 }
88 }
89
90 if (empty($data['id']) && !empty($data['billing_mail']))
91 {
92 // search also by e-mail (and the user ID is null)
93 $item = $this->getItem(array('jid' => 0, 'billing_mail' => $data['billing_mail']));
94
95 if ($item)
96 {
97 // customer found by mail, do update
98 $data['id'] = $item->id;
99 }
100 }
101
102 if (!empty($data['used_credit']) && $item)
103 {
104 // subtract the used credit to the existing one
105 $data['credit'] = max(array(0, $item->credit - $data['used_credit']));
106 }
107
108 if (isset($data['active_to_date']) && !VAPDateHelper::isNull($data['active_to_date']))
109 {
110 // an expiration date was specified, unset lifetime flag
111 $data['lifetime'] = 0;
112
113 // register first active date
114 $data['active_since'] = JFactory::getDate()->toSql();
115
116 if (!empty($data['id']))
117 {
118 if (!$item)
119 {
120 // load current item
121 $item = $this->getItem($data['id']);
122 }
123
124 // make sure the item exists and the activation date was already set
125 if ($item && !VAPDateHelper::isNull($item->active_since))
126 {
127 // activation date already registered, avoid updating it
128 unset($data['active_since']);
129 }
130 }
131 }
132
133 // attempt to save the customer
134 return parent::save($data);
135 }
136
137 /**
138 * Increases (or decreases) the user credit by the given amount.
139 *
140 * @param mixed $pk The user primary key(s).
141 * @param float $credit A positive float to increase the credit,
142 * anegative amount to decrease it.
143 *
144 * @return boolean True on success, false otherwise.
145 */
146 public function addCredit($id, $credit)
147 {
148 if ($credit == 0)
149 {
150 // no credit to update
151 return false;
152 }
153
154 // load current user credit
155 $item = $this->getItem($id);
156
157 if (!$item)
158 {
159 // user not found
160 return false;
161 }
162
163 $data = array(
164 'id' => $item->id,
165 'credit' => $item->credit + (float) $credit,
166 );
167
168 // make sure the credit is not lower than 0
169 $data['credit'] = max(array(0, $data['credit']));
170
171 // update used credit
172 return (bool) $this->save($data);
173 }
174
175 /**
176 * Extend delete implementation to delete any related records
177 * stored within a separated table.
178 *
179 * @param mixed $ids Either the record ID or a list of records.
180 *
181 * @return boolean True on success, false otherwise.
182 */
183 public function delete($ids)
184 {
185 // only int values are accepted
186 $ids = array_map('intval', (array) $ids);
187
188 // invoke parent first
189 if (!parent::delete($ids))
190 {
191 // nothing to delete
192 return false;
193 }
194
195 $dbo = JFactory::getDbo();
196
197 // load any assigned notes
198 $q = $dbo->getQuery(true)
199 ->select($dbo->qn('id'))
200 ->from($dbo->qn('#__vikappointments_user_notes'))
201 ->where($dbo->qn('id_user') . ' IN (' . implode(',', $ids) . ')' );
202
203 $dbo->setQuery($q);
204
205 if ($note_ids = $dbo->loadColumn())
206 {
207 // get user notes model
208 $model = JModelVAP::getInstance('usernote');
209 // delete records
210 $model->delete($note_ids);
211 }
212
213 // load any assigned reservations
214 $q = $dbo->getQuery(true)
215 ->select($dbo->qn('id'))
216 ->from($dbo->qn('#__vikappointments_reservation'))
217 ->where($dbo->qn('id_user') . ' IN (' . implode(',', $ids) . ')' );
218
219 $dbo->setQuery($q);
220
221 if ($app_ids = $dbo->loadColumn())
222 {
223 // get appointment model
224 $model = JModelVAP::getInstance('reservation');
225
226 // detach customers from appointments
227 foreach ($app_ids as $app_id)
228 {
229 $data = array(
230 'id' => (int) $app_id,
231 'id_user' => 0,
232 );
233
234 $model->save($data);
235 }
236 }
237
238 // load any assigned packages orders
239 $q = $dbo->getQuery(true)
240 ->select($dbo->qn('id'))
241 ->from($dbo->qn('#__vikappointments_package_order'))
242 ->where($dbo->qn('id_user') . ' IN (' . implode(',', $ids) . ')' );
243
244 $dbo->setQuery($q);
245
246 if ($ord_ids = $dbo->loadColumn())
247 {
248 // get package order model
249 $model = JModelVAP::getInstance('packorder');
250
251 // detach customers from orders
252 foreach ($ord_ids as $ord_id)
253 {
254 $data = array(
255 'id' => (int) $ord_id,
256 'id_user' => 0,
257 );
258
259 $model->save($data);
260 }
261 }
262
263 // load any assigned subscriptions orders
264 $q = $dbo->getQuery(true)
265 ->select($dbo->qn('id'))
266 ->from($dbo->qn('#__vikappointments_subscr_order'))
267 ->where($dbo->qn('id_user') . ' IN (' . implode(',', $ids) . ')' );
268
269 $dbo->setQuery($q);
270
271 if ($ord_ids = $dbo->loadColumn())
272 {
273 // get subscription order model
274 $model = JModelVAP::getInstance('subscrorder');
275
276 // detach customers from orders
277 foreach ($ord_ids as $ord_id)
278 {
279 $data = array(
280 'id' => (int) $ord_id,
281 'id_user' => 0,
282 );
283
284 $model->save($data);
285 }
286 }
287
288 return true;
289 }
290
291 /**
292 * Searches for the CMS users that match the specified query.
293 * It is possible to search the users by name, username and
294 * e-mail address.
295 *
296 * @param string $search The search string.
297 * @param mixed $id The user ID. When specified, the system will
298 * fetch also the user status, to check if the
299 * user has been assigned to another user.
300 * @param array $options An array of options:
301 * - start int|null the query offset;
302 * - limit int|null the query limit;
303 *
304 * @return array A list of matching users.
305 */
306 public function searchUsers($search = '', $id = null, array $options = array())
307 {
308 // always reset pagination and total count
309 $this->pagination = null;
310 $this->total = 0;
311
312 $options = new JRegistry($options);
313
314 $dbo = JFactory::getDbo();
315
316 $q = $dbo->getQuery(true);
317
318 $q->select($dbo->qn('u.id'));
319 $q->select($dbo->qn('u.name'));
320 $q->select($dbo->qn('u.email'));
321 $q->select($dbo->qn('u.username'));
322
323 if (!is_null($id))
324 {
325 // create inner query to fetch enabled/disabled status
326 $inner = $dbo->getQuery(true)
327 ->select(1)
328 ->from($dbo->qn('#__vikappointments_users', 'a'))
329 ->where($dbo->qn('a.jid') . ' = ' . $dbo->qn('u.id'));
330
331 $q->select('(' . $dbo->qn('id') . ' <> ' . (int) $id . ' AND EXISTS (' . $inner . ')) AS ' . $dbo->qn('disabled'));
332 }
333
334 $q->from($dbo->qn('#__users', 'u'));
335
336 if ($search)
337 {
338 /**
339 * Reverse the search key in order to try finding
340 * users by name even if it was wrote in the opposite way.
341 * If we search by "John Smith", the system will search
342 * for "Smith John" too.
343 *
344 * @since 1.7
345 */
346 $reverse = preg_split("/\s+/", $search);
347 $reverse = array_reverse($reverse);
348 $reverse = implode(' ', $reverse);
349
350 $q->where(array(
351 $dbo->qn('u.name') . ' LIKE ' . $dbo->q("%$search%"),
352 $dbo->qn('u.name') . ' LIKE ' . $dbo->q("%$reverse%"),
353 $dbo->qn('u.username') . ' LIKE ' . $dbo->q("%$search%"),
354 $dbo->qn('u.email') . ' LIKE ' . $dbo->q("%$search%"),
355 ), 'OR');
356 }
357
358 $q->order($dbo->qn('u.name') . ' ASC');
359 $q->order($dbo->qn('u.username') . ' ASC');
360
361 /**
362 * Fetch list limit.
363 *
364 * @since 1.7
365 */
366 $start = $options->get('start', 0);
367 $limit = $options->get('limit', null);
368
369 $dbo->setQuery($q, $start, $limit);
370 $rows = $dbo->loadObjectList();
371
372 if (!$rows)
373 {
374 // no users found
375 return [];
376 }
377
378 $users = array();
379
380 /**
381 * Reverse lookup used to check whether there is already
382 * a user with the same name.
383 *
384 * @since 1.7
385 */
386 $namesakesLookup = array();
387
388 foreach ($rows as $u)
389 {
390 $u->text = $u->name;
391
392 $users[$u->id] = $u;
393
394 // insert name-id relation within the lookup
395 if (!isset($namesakesLookup[$u->name]))
396 {
397 $namesakesLookup[$u->name] = array();
398 }
399
400 $namesakesLookup[$u->name][] = $u->id;
401 }
402
403 // iterate names lookup
404 foreach ($namesakesLookup as $name => $ids)
405 {
406 // in case a name owns more than 1 ID, we have a homonym
407 if (count($ids) > 1)
408 {
409 // iterate the list of IDS and append the e-mail to the name
410 foreach ($ids as $id)
411 {
412 $users[$id]->text .= ' : ' . $users[$id]->username;
413 }
414 }
415 }
416
417 return $users;
418 }
419
420 /**
421 * Searches for the customers that match the specified query.
422 * It is possible to search the users by name, e-mail and
423 * phone number.
424 *
425 * @param string $search The search string.
426 * @param array $options An array of options:
427 * - start int|null the query offset;
428 * - limit int|null the query limit;
429 * - id_employee int|null the employee filter.
430 *
431 * @return array A list of matching customers.
432 */
433 public function search($search = '', array $options = array())
434 {
435 // always reset pagination and total count
436 $this->pagination = null;
437 $this->total = 0;
438
439 $options = new JRegistry($options);
440
441 $dbo = JFactory::getDbo();
442
443 // fetch list based on search key
444 $q = $dbo->getQuery(true);
445
446 $q->select('SQL_CALC_FOUND_ROWS c.id');
447 $q->select($dbo->qn('c.billing_name'));
448 $q->select($dbo->qn('c.billing_mail'));
449 $q->select($dbo->qn('c.billing_phone'));
450 $q->select($dbo->qn('c.country_code'));
451 $q->select($dbo->qn('c.billing_state'));
452 $q->select($dbo->qn('c.billing_city'));
453 $q->select($dbo->qn('c.billing_address'));
454 $q->select($dbo->qn('c.billing_zip'));
455 $q->select($dbo->qn('c.company'));
456 $q->select($dbo->qn('c.vatnum'));
457 $q->select($dbo->qn('c.jid'));
458 $q->select($dbo->qn('c.image'));
459 $q->select($dbo->qn('c.fields'));
460
461 $q->from($dbo->qn('#__vikappointments_users', 'c'));
462
463 $q->where(1);
464
465 if ($search)
466 {
467 /**
468 * Reverse the search key in order to try finding
469 * users by name even if it was wrote in the opposite way.
470 * If we searched by "John Smith", the system will search
471 * for "Smith John" too.
472 *
473 * @since 1.7
474 */
475 $reverse = preg_split("/\s+/", $search);
476 $reverse = array_reverse($reverse);
477 $reverse = implode(' ', $reverse);
478
479 $q->andWhere(array(
480 $dbo->qn('c.billing_name') . ' LIKE ' . $dbo->q("%$search%"),
481 $dbo->qn('c.billing_name') . ' LIKE ' . $dbo->q("%$reverse%"),
482 $dbo->qn('c.billing_mail') . ' LIKE ' . $dbo->q("%$search%"),
483 $dbo->qn('c.billing_phone') . ' LIKE ' . $dbo->q("%$search%"),
484 ), 'OR');
485 }
486
487 /**
488 * When specified, take only the customers that booked at least an appointment
489 * with the given employee.
490 *
491 * @since 1.7
492 */
493 if (isset($options['id_employee']))
494 {
495 $q->leftjoin($dbo->qn('#__vikappointments_reservation', 'r') . ' ON ' . $dbo->qn('c.id') . ' = ' . $dbo->qn('r.id_user'));
496 $q->andWhere($dbo->qn('r.id_employee') . ' = ' . (int) $options['id_employee']);
497 $q->group($dbo->qn('c.id'));
498 }
499
500 $q->order($dbo->qn('c.billing_name') . ' ASC');
501 $q->order($dbo->qn('c.billing_mail') . ' ASC');
502
503 /**
504 * Fetch list limit.
505 *
506 * @since 1.7
507 */
508 $start = $options->get('start', 0);
509 $limit = $options->get('limit', null);
510
511 $dbo->setQuery($q, $start, $limit);
512 $rows = $dbo->loadObjectList();
513
514 if (!$rows)
515 {
516 // no customers found
517 return [];
518 }
519
520 $users = array();
521
522 /**
523 * Reverse lookup used to check whether there is already
524 * a user with the same name.
525 *
526 * @since 1.7
527 */
528 $namesakesLookup = array();
529
530 foreach ($rows as $u)
531 {
532 // decode JSON fields
533 $u->fields = $u->fields ? json_decode($u->fields) : array();
534
535 $u->text = $u->billing_name;
536
537 // register user by ID
538 $users[$u->id] = $u;
539
540 // insert name-id relation within the lookup
541 if (!isset($namesakesLookup[$u->billing_name]))
542 {
543 $namesakesLookup[$u->billing_name] = array();
544 }
545
546 $namesakesLookup[$u->billing_name][] = $u->id;
547 }
548
549 // iterate names lookup
550 foreach ($namesakesLookup as $name => $ids)
551 {
552 // in case a name owns more than 1 ID, we have a homonym
553 if (count($ids) > 1)
554 {
555 // iterate the list of IDS and append the e-mail to the name
556 foreach ($ids as $id)
557 {
558 $users[$id]->text .= ' : ' . $users[$id]->billing_mail;
559 }
560 }
561 }
562
563 return $users;
564 }
565
566 /**
567 * Returns the list pagination.
568 *
569 * @param array $filters An array of filters.
570 * @param array $options An array of options.
571 *
572 * @return JPagination
573 */
574 public function getPagination(array $filters = array(), array $options = array())
575 {
576 if (!$this->pagination)
577 {
578 jimport('joomla.html.pagination');
579 $dbo = JFactory::getDbo();
580 $dbo->setQuery('SELECT FOUND_ROWS();');
581 $this->total = (int) $dbo->loadResult();
582
583 $this->pagination = new JPagination($this->total, $options['start'], $options['limit']);
584
585 foreach ($filters as $k => $v)
586 {
587 // append only filters that own a value as it doesn't
588 // make sense to populate the URL using empty variables
589 if ($v)
590 {
591 $this->pagination->setAdditionalUrlParam($k, $v);
592 }
593 }
594 }
595
596 return $this->pagination;
597 }
598
599 /**
600 * Returns the total number of employees matching the search query.
601 *
602 * @return integer
603 */
604 public function getTotal()
605 {
606 return $this->total;
607 }
608
609 /**
610 * Counts the total number of purchased packages and the total number of redeemed ones.
611 *
612 * @param int|null $userId The ID of the user that owns the packages. If not specified,
613 * the current used will be taken.
614 *
615 * @return object The count information (`num_app` and `used_app`).
616 *
617 * @since 1.7.4
618 */
619 public function countPackages(?int $userId = null)
620 {
621 if (!$userId)
622 {
623 // get customer ID assigned to the current user
624 $userId = $this->getItem(['jid' => JFactory::getUser()->id], $blank = true)->id;
625 }
626
627 $db = JFactory::getDbo();
628
629 // get any approved codes
630 $approved = JHtml::fetch('vaphtml.status.find', 'code', ['packages' => 1, 'approved' => 1]);
631
632 $query = $db->getQuery(true)
633 ->select(sprintf('SUM(%s) AS %s', $db->qn('i.used_app'), $db->qn('used_app')))
634 ->select(sprintf('SUM(%s) AS %s', $db->qn('i.num_app'), $db->qn('num_app')))
635 ->from($db->qn('#__vikappointments_package_order', 'o'))
636 ->leftjoin($db->qn('#__vikappointments_package_order_item', 'i') . ' ON ' . $db->qn('i.id_order') . ' = ' . $db->qn('o.id'))
637 ->where($db->qn('o.id_user') . ' = ' . $userId);
638
639 if ($approved)
640 {
641 // filter by approved status
642 $query->where($db->qn('o.status') . ' IN (' . implode(',', array_map(array($db, 'q'), $approved)) . ')');
643 }
644
645 $db->setQuery($query);
646 $count = $db->loadObject();
647
648 if (!$count)
649 {
650 $count = new stdClass;
651 }
652
653 $count->used_app = (int) ($count->used_app ?? 0);
654 $count->num_app = (int) ($count->num_app ?? 0);
655
656 return $count;
657 }
658 }
659