PluginProbe
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent / trunk
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent vtrunk
3.5.3 3.5.2 3.5.1 3.4.9 3.5.0 3.4.8 3.4.7 trunk 2.3.1 3.3.6 3.3.7 3.3.8 3.3.9 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.6
supportcandy / includes / models / class-wpsc-customer.php

class-wpsc-customer.php in SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent trunk, at includes/models/class-wpsc-customer.php

831 lines 19.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 exit; // Exit if accessed directly!
4 }
5
6 if ( ! class_exists( 'WPSC_Customer' ) ) :
7
8 final class WPSC_Customer {
9
10 /**
11 * Object data in key => val pair.
12 *
13 * @var array
14 */
15 private $data = array();
16
17 /**
18 * Set whether or not current object properties modified
19 *
20 * @var boolean
21 */
22 private $is_modified = false;
23
24 /**
25 * Schema for this model
26 *
27 * @var array
28 */
29 public static $schema = array();
30
31 /**
32 * Prevent fields to modify
33 *
34 * @var array
35 */
36 public static $prevent_modify = array();
37
38 /**
39 * DB object caching
40 *
41 * @var array
42 */
43 private static $cache = array();
44
45 /**
46 * Initialize this class
47 *
48 * @return void
49 */
50 public static function init() {
51
52 // Apply schema for this model.
53 add_action( 'init', array( __CLASS__, 'apply_schema' ), 2 );
54
55 // Get object of this class.
56 add_filter( 'wpsc_load_ref_classes', array( __CLASS__, 'load_ref_class' ) );
57
58 // after customer profile update.
59 add_action( 'profile_update', array( __CLASS__, 'customer_profile_update' ), 10, 2 );
60
61 // after user registration.
62 add_action( 'user_register', array( __CLASS__, 'register_customer' ), 10, 2 );
63 }
64
65 /**
66 * Apply schema for this model
67 *
68 * @return void
69 */
70 public static function apply_schema() {
71
72 $schema = array(
73 'id' => array(
74 'has_ref' => false,
75 'ref_class' => '',
76 'has_multiple_val' => false,
77 ),
78 'user' => array(
79 'has_ref' => true,
80 'ref_class' => 'wp_user',
81 'has_multiple_val' => false,
82 ),
83 'ticket_count' => array(
84 'has_ref' => false,
85 'ref_class' => '',
86 'has_multiple_val' => false,
87 ),
88 );
89
90 foreach ( WPSC_Custom_Field::$custom_fields as $cf ) {
91
92 if ( $cf->field == 'customer' ) {
93 $schema[ $cf->slug ] = array(
94 'has_ref' => $cf->type::$has_ref,
95 'ref_class' => $cf->type::$ref_class,
96 'has_multiple_val' => $cf->type::$has_multiple_val,
97 );
98 }
99 }
100
101 self::$schema = apply_filters( 'wpsc_customer_schema', $schema );
102
103 // Prevent modify.
104 $prevent_modify = array( 'id' );
105 self::$prevent_modify = apply_filters( 'wpsc_customer_prevent_modify', $prevent_modify );
106 }
107
108 /**
109 * Model constructor
110 *
111 * @param int $id - Optional. Data record id to retrive object for.
112 */
113 public function __construct( $id = 0 ) {
114
115 global $wpdb;
116
117 $id = intval( $id );
118
119 if ( isset( self::$cache[ $id ] ) ) {
120 $this->data = self::$cache[ $id ]->data;
121 return;
122 }
123
124 if ( $id > 0 ) {
125
126 $customer = $wpdb->get_row( "SELECT * FROM {$wpdb->prefix}psmsc_customers WHERE id = " . $id, ARRAY_A );
127 if ( ! is_array( $customer ) ) {
128 return;
129 }
130
131 foreach ( $customer as $key => $val ) {
132 $this->data[ $key ] = $val !== null ? $val : '';
133 }
134
135 self::$cache[ $id ] = $this;
136 }
137 }
138
139 /**
140 * Convert object into an array
141 *
142 * @return array
143 */
144 public function to_array() {
145
146 return $this->data;
147 }
148
149 /**
150 * Magic get function to use with object arrow function
151 *
152 * @param string $var_name - variable name.
153 * @return mixed
154 */
155 public function __get( $var_name ) {
156
157 if ( ! isset( $this->data[ $var_name ] ) ||
158 $this->data[ $var_name ] == null ||
159 $this->data[ $var_name ] == ''
160 ) {
161 return self::$schema[ $var_name ]['has_multiple_val'] ? array() : '';
162 }
163
164 if ( self::$schema[ $var_name ]['has_multiple_val'] ) {
165
166 $response = array();
167 $values = $this->data[ $var_name ] ? explode( '|', $this->data[ $var_name ] ) : array();
168 foreach ( $values as $val ) {
169 $response[] = self::$schema[ $var_name ]['has_ref'] ?
170 WPSC_Functions::get_object( self::$schema[ $var_name ]['ref_class'], $val ) :
171 $val;
172 }
173 return $response;
174
175 } else {
176
177 return self::$schema[ $var_name ]['has_ref'] && $this->data[ $var_name ] ?
178 WPSC_Functions::get_object( self::$schema[ $var_name ]['ref_class'], $this->data[ $var_name ] ) :
179 $this->data[ $var_name ];
180 }
181 }
182
183 /**
184 * Magic function to use setting object field with arrow function
185 *
186 * @param string $var_name - (Required) property slug.
187 * @param mixed $value - (Required) value to set for a property.
188 * @return void
189 */
190 public function __set( $var_name, $value ) {
191
192 if (
193 ! isset( $this->data[ $var_name ] ) ||
194 in_array( $var_name, self::$prevent_modify )
195 ) {
196 return;
197 }
198
199 $data_val = '';
200 if ( self::$schema[ $var_name ]['has_multiple_val'] ) {
201
202 $data_vals = array_map(
203 fn( $val ) => is_object( $val ) ? WPSC_Functions::set_object( self::$schema[ $var_name ]['ref_class'], $val ) : $val,
204 $value
205 );
206
207 $data_val = $data_vals ? implode( '|', $data_vals ) : '';
208
209 } else {
210
211 $data_val = is_object( $value ) ? WPSC_Functions::set_object( self::$schema[ $var_name ]['ref_class'], $value ) : $value;
212 }
213
214 if ( $this->data[ $var_name ] == $data_val ) {
215 return;
216 }
217
218 $this->data[ $var_name ] = $data_val;
219 $this->is_modified = true;
220 }
221
222 /**
223 * Save changes made
224 *
225 * @return boolean
226 */
227 public function save() {
228
229 global $wpdb;
230
231 if ( ! $this->is_modified ) {
232 return true;
233 }
234
235 $data = $this->data;
236 $success = true;
237
238 if ( ! isset( $data['id'] ) ) {
239
240 $cr = self::insert( $data );
241 if ( $cr ) {
242 $this->data = $cr->data;
243 $success = true;
244 } else {
245 $success = false;
246 }
247 } else {
248
249 unset( $data['id'] );
250 $success = $wpdb->update(
251 $wpdb->prefix . 'psmsc_customers',
252 $data,
253 array( 'id' => $this->data['id'] )
254 );
255 }
256 $this->is_modified = false;
257 self::$cache[ $this->id ] = $this;
258 return $success ? true : false;
259 }
260
261 /**
262 * Insert new record
263 *
264 * @param array $data - insert data.
265 * @return WPSC_Customer
266 */
267 public static function insert( $data ) {
268
269 global $wpdb;
270 $user = self::get_by_email( $data['email'] );
271 if ( $user->id ) {
272
273 return $user;
274 }
275
276 $success = $wpdb->insert(
277 $wpdb->prefix . 'psmsc_customers',
278 $data
279 );
280
281 if ( ! $success ) {
282 return false;
283 }
284
285 $customer = new WPSC_Customer( $wpdb->insert_id );
286 return $customer;
287 }
288
289 /**
290 * Delete record of given ID
291 *
292 * @param WPSC_Customer $customer - customer object.
293 * @return boolean
294 */
295 public static function destroy( $customer ) {
296
297 global $wpdb;
298
299 // Delete attachments of the customer.
300 $success = $wpdb->update(
301 $wpdb->prefix . 'psmsc_attachments',
302 array( 'is_active' => '0' ),
303 array( 'customer_id' => $customer->id )
304 );
305
306 $success = $wpdb->delete(
307 $wpdb->prefix . 'psmsc_customers',
308 array( 'id' => $customer->id )
309 );
310 if ( ! $success ) {
311 return false;
312 }
313
314 unset( self::$cache[ $customer->id ] );
315 return true;
316 }
317
318 /**
319 * Set data to create new object using direct data. Used in find method
320 *
321 * @param array $data - data to set for object.
322 * @return void
323 */
324 private function set_data( $data ) {
325
326 foreach ( $data as $var_name => $val ) {
327 $this->data[ $var_name ] = $val !== null ? $val : '';
328 }
329 }
330
331 /**
332 * Find records based on given filters
333 *
334 * @param array $filter - array containing array items like search, where, orderby, order, page_no, items_per_page, etc.
335 * @param boolean $is_object - return data as array or object. Default object.
336 * @return mixed
337 */
338 public static function find( $filter = array(), $is_object = true ) {
339
340 global $wpdb;
341
342 $filter['items_per_page'] = isset( $filter['items_per_page'] ) ? $filter['items_per_page'] : 20;
343 $filter['page_no'] = isset( $filter['page_no'] ) ? $filter['page_no'] : 1;
344 $filter['orderby'] = isset( $filter['orderby'] ) ? $filter['orderby'] : 'name';
345 $filter['order'] = isset( $filter['order'] ) ? $filter['order'] : 'ASC';
346
347 $sql = 'SELECT c.* FROM ' . $wpdb->prefix . 'psmsc_customers c ';
348 $join = self::get_joins( $filter );
349 $where = self::get_where( $filter );
350
351 // Add table alice to orderby.
352 $filter['orderby'] = 'c.' . $filter['orderby'];
353
354 $order = WPSC_Functions::parse_order( $filter );
355 $limit = WPSC_Functions::parse_limit( $filter );
356
357 $group_by = 'GROUP BY c.id ';
358
359 $sql = $sql . $join . $where . $group_by . $order . $limit;
360 $results = $wpdb->get_results( $sql, ARRAY_A );
361
362 // total results.
363 $sql = 'SELECT count(DISTINCT c.id) FROM ' . $wpdb->prefix . 'psmsc_customers c ';
364 $total_items = $wpdb->get_var( $sql . $join . $where );
365
366 $response = WPSC_Functions::parse_response( $results, $total_items, $filter );
367
368 // Return array.
369 if ( ! $is_object ) {
370 return $response;
371 }
372
373 // create and return array of objects.
374 $temp_results = array();
375 foreach ( $response['results'] as $customer ) {
376
377 $ob = new WPSC_Customer();
378 $data = array();
379 foreach ( $customer as $key => $val ) {
380 $data[ $key ] = $val;
381 }
382 $ob->set_data( $data );
383 $temp_results[] = $ob;
384 }
385 $response['results'] = $temp_results;
386
387 return $response;
388 }
389
390 /**
391 * Apply joins to the query if search is needed
392 *
393 * @param array $filter - user filter.
394 * @return array
395 */
396 private static function get_joins( $filter ) {
397
398 global $wpdb;
399
400 $joins = array();
401 $search = WPSC_Functions::get_filter_search_str( $filter );
402 if ( $search ) {
403 $joins = array( 'LEFT JOIN ' . $wpdb->users . ' u ON c.user = u.ID' );
404 }
405
406 return $joins ? implode( ' ', $joins ) . ' ' : '';
407 }
408
409 /**
410 * Get where for find method
411 *
412 * @param array $filter - user filter.
413 * @return array
414 */
415 private static function get_where( $filter ) {
416
417 $where = array( '1=1' );
418
419 // Load meta filters.
420 $meta_query = isset( $filter['meta_query'] ) ? $filter['meta_query'] : array();
421 if ( $meta_query ) {
422 $where[] = self::parse_filters( $meta_query );
423 }
424
425 $search = WPSC_Functions::get_filter_search_str( $filter );
426 if ( $search ) {
427
428 $search_query = array();
429
430 // Customers table cols.
431 foreach ( self::$schema as $slug => $schema ) {
432
433 if ( ! $schema['has_ref'] && $slug != 'id' ) {
434 $search_query[] = 'CONVERT(c.' . $slug . ' USING utf8) LIKE \'%' . $search . '%\'';
435 }
436 }
437
438 // Users table cols.
439 $users_search = array(
440 'CONVERT(u.user_login USING utf8) LIKE \'%' . $search . '%\'',
441 'CONVERT(u.user_nicename USING utf8) LIKE \'%' . $search . '%\'',
442 'CONVERT(u.user_email USING utf8) LIKE \'%' . $search . '%\'',
443 'CONVERT(u.display_name USING utf8) LIKE \'%' . $search . '%\'',
444 );
445
446 $search_query = array_merge( $search_query, $users_search );
447 $where[] = implode( ' OR ', $search_query );
448 }
449
450 return 'WHERE (' . implode( ') AND (', $where ) . ') ';
451 }
452
453 /**
454 * Parse user filters for this model
455 *
456 * @param array $filters - user filters.
457 * @return string
458 */
459 private static function parse_filters( $filters ) {
460
461 // Invalid filter.
462 if ( ! isset( $filters['relation'] ) || count( $filters ) < 2 ) {
463 return '1=1';
464 }
465
466 $relation = $filters['relation'];
467 $filter_str = array();
468
469 foreach ( $filters as $key => $filter ) {
470
471 // Skip if current element is relation indicator.
472 if ( $key === 'relation' ) {
473 continue;
474 }
475
476 // Invalid filter if it is not an array.
477 if ( ! is_array( $filter ) ) {
478 return '1=1';
479 }
480
481 // Call recursively if there is multi-layer filter detected.
482 if ( isset( $filter['relation'] ) ) {
483 $filter_str[] = self::parse_user_filters( $filter );
484 continue;
485 }
486
487 // Invalid filter if it does not contain slug, compare and val indexes.
488 $slug = isset( $filter['slug'] ) ? WPSC_Functions::sanitize_sql_key( $filter['slug'] ) : false;
489 $compare = isset( $filter['compare'] ) ? $filter['compare'] : false;
490 $val = isset( $filter['val'] ) ? $filter['val'] : false;
491 if ( ! $slug || ! $compare || $val === false ) {
492 return '1=1';
493 }
494
495 // custom filter.
496 if ( $slug === 'custom_query' ) {
497
498 $filter_str[] = $val;
499
500 } else {
501
502 switch ( $compare ) {
503
504 case '<':
505 case '=':
506 case '>':
507 case '<=':
508 case '>=':
509 if ( self::$schema[ $slug ]['has_multiple_val'] ) {
510 $filter_str[] = '1=1';
511 break;
512 }
513 $filter_str[] = 'c.' . $slug . ' ' . $compare . ' \'' . esc_sql( $val ) . '\'';
514 break;
515
516 case 'BETWEEN':
517 $filter_str[] = 'c.' . $slug . ' BETWEEN \'' . esc_sql( $val[0] ) . '\' AND \'' . esc_sql( $val[1] ) . '\'';
518 break;
519
520 case 'IN':
521 if ( self::$schema[ $slug ]['has_multiple_val'] ) {
522
523 $filter_str[] = 'c.' . $slug . ' RLIKE \'(^|[|])(' . implode( '|', esc_sql( $val ) ) . ')($|[|])\'';
524
525 } else {
526
527 $filter_str[] = 'c.' . $slug . ' IN ( \'' . implode( '\', \'', esc_sql( $val ) ) . '\' )';
528 }
529 break;
530
531 case 'NOT IN':
532 if ( self::$schema[ $slug ]['has_multiple_val'] ) {
533
534 $filter_str[] = 'c.' . $slug . ' NOT RLIKE \'(^|[|])(' . implode( '|', esc_sql( $val ) ) . ')($|[|])\'';
535
536 } else {
537
538 $filter_str[] = 'c.' . $slug . ' NOT IN ( \'' . implode( '\', \'', esc_sql( $val ) ) . '\' )';
539 }
540 break;
541 }
542 }
543 }
544
545 return count( $filter_str ) > 1 ?
546 '( ' . implode( ' ' . $relation . ' ', $filter_str ) . ' )' :
547 $filter_str[0];
548 }
549
550 /**
551 * Load current class to reference classes
552 *
553 * @param array $classes - Associative array of class names indexed by its slug.
554 * @return array
555 */
556 public static function load_ref_class( $classes ) {
557
558 $classes['wpsc_customer'] = array(
559 'class' => __CLASS__,
560 'save-key' => 'id',
561 );
562 return $classes;
563 }
564
565 /**
566 * Customer autocomplete callback
567 *
568 * @param string $term - search term.
569 * @return array
570 */
571 public static function customer_autocomplete( $term ) {
572
573 return array_map(
574 fn( $customer )=>array(
575 'id' => $customer->id,
576 'title' => sprintf(
577 /* translators: %1$s: Name, %2$s: Email Address */
578 esc_attr__( '%1$s (%2$s)', 'supportcandy' ),
579 $customer->name,
580 $customer->email
581 ),
582 ),
583 self::customer_search( $term )
584 );
585 }
586
587 /**
588 * Search customer and return array of customer objects.
589 *
590 * @param string $term - search string.
591 * @return array
592 */
593 public static function customer_search( $term ) {
594
595 // search in wp user table.
596 $users = ( new WP_User_Query(
597 array(
598 'search' => '*' . esc_attr( $term ) . '*',
599 'search_columns' => array(
600 'user_login',
601 'user_nicename',
602 'user_email',
603 'display_name',
604 ),
605 'number' => 10,
606 )
607 ) )->get_results();
608
609 // import into customer table if not already exists.
610 foreach ( $users as $user ) {
611 self::get_by_user_id( $user->ID );
612 }
613
614 // finally search customer table.
615 $customers = self::find(
616 array(
617 'search' => $term,
618 'items_per_page' => 10,
619 )
620 )['results'];
621
622 // return results.
623 return array_filter(
624 $customers,
625 function ( $customer ) {
626 return $customer->email !== 'anonymous@anonymous.anonymous';
627 }
628 );
629 }
630
631 /**
632 * Return customer by an email address
633 *
634 * @param string $email - email address.
635 * @return WPSC_Customer
636 */
637 public static function get_by_email( $email ) {
638
639 if ( ! $email ) {
640 return new WPSC_Customer();
641 }
642
643 $response = self::find(
644 array(
645 'meta_query' => array(
646 'relation' => 'AND',
647 array(
648 'slug' => 'email',
649 'compare' => '=',
650 'val' => $email,
651 ),
652 ),
653 )
654 );
655
656 return $response['results'] ? $response['results'][0] : new WPSC_Customer();
657 }
658
659 /**
660 * Return customer object by wp user id
661 *
662 * @param int $id - wp user id.
663 * @return boolean
664 */
665 public static function get_by_user_id( $id ) {
666
667 if ( ! $id ) {
668 return new WPSC_Customer();
669 }
670
671 $response = self::find(
672 array(
673 'meta_query' => array(
674 'relation' => 'AND',
675 array(
676 'slug' => 'user',
677 'compare' => '=',
678 'val' => $id,
679 ),
680 ),
681 )
682 );
683
684 if ( $response['results'] ) {
685
686 return $response['results'][0];
687
688 } else {
689
690 $user = get_user_by( 'id', $id );
691 if ( ! $user ) {
692 return new WPSC_Customer();
693 }
694
695 return self::insert(
696 array(
697 'user' => $user->ID,
698 'name' => $user->display_name,
699 'email' => $user->user_email,
700 )
701 );
702 }
703 }
704
705 /**
706 * Update customer record such as name, email address, user type, etc. when wp user record is updated.
707 *
708 * @param int $user_id - wp user id.
709 * @param WP_User $old_user_data - user object before change.
710 * @return void
711 */
712 public static function customer_profile_update( $user_id, $old_user_data ) {
713
714 global $wpdb;
715
716 $user = get_userdata( $user_id );
717 $customer = self::get_by_user_id( $user_id );
718 if ( $customer ) {
719
720 $customer->name = $user->display_name;
721
722 // update user_type from "guest" to "registered" as well as customer id for tickets which was created as guest with email address that is now updated.
723 if ( $customer->email != $user->user_email ) {
724
725 // get the guest customer object of newly added email. Update tickets (if any).
726 $guest_customer = self::get_by_email( $user->user_email );
727 if ( $guest_customer->id ) {
728 $wpdb->update(
729 $wpdb->prefix . 'psmsc_tickets',
730 array(
731 'customer' => $customer->id,
732 'user_type' => 'registered',
733 ),
734 array(
735 'customer' => $guest_customer->id,
736 )
737 );
738
739 // destroy guest customer.
740 self::destroy( $guest_customer );
741 }
742
743 // update customer email.
744 $customer->email = $user->user_email;
745 }
746
747 $customer->save();
748 }
749 }
750
751 /**
752 * Add/update customer record after wp user registration
753 *
754 * @param int $user_id - wp user id.
755 * @param array $user_data - registered user data.
756 * @return void
757 */
758 public static function register_customer( $user_id, $user_data ) {
759
760 $user = get_user_by( 'ID', $user_id );
761 $name = isset( $user->display_name ) ? $user->display_name : $user_data['user_login'];
762
763 $customer = self::get_by_email( $user_data['user_email'] );
764
765 if ( ! $customer->id ) {
766
767 // Create customer record.
768 $new_customer = array(
769 'user' => $user_id,
770 'name' => $name,
771 'email' => $user_data['user_email'],
772 );
773 $customer = self::insert( $new_customer );
774
775 } else {
776
777 // update customer record.
778 $customer->user = $user_id;
779 $customer->name = $name;
780 $customer->save();
781 }
782 }
783
784 /**
785 * Get object for anonymous customer
786 *
787 * @return WPSC_Customer
788 */
789 public static function get_anonymous_customer() {
790
791 $data = array(
792 'id' => '0',
793 'name' => 'Anonymous',
794 'email' => 'anonymous@anonymous.anonymous',
795 );
796 $object = new WPSC_Customer();
797 $object->set_data( $data );
798 return $object;
799 }
800
801 /**
802 * Update total ticket count for the customer in database
803 *
804 * @return void
805 */
806 public function update_ticket_count() {
807
808 global $wpdb;
809 if ( ! is_numeric( $this->id ) || (int) $this->id <= 0 ) {
810 return;
811 }
812 $count = $wpdb->get_var( $wpdb->prepare( "SELECT count(id) FROM {$wpdb->prefix}psmsc_tickets WHERE is_active=1 AND customer=%d", $this->id ) );
813 $this->ticket_count = $count;
814 $this->save();
815 }
816
817 /**
818 * Check whether customer is an agent or not
819 *
820 * @return boolean
821 */
822 public function is_agent() {
823
824 $agent = WPSC_Agent::get_by_customer( $this );
825 return $agent->id ? true : false;
826 }
827 }
828 endif;
829
830 WPSC_Customer::init();
831