PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.4.5
Fluent Support – Helpdesk & Customer Support Ticket System v2.4.5
2.4.5 2.4.0 2.3.2 2.3.1 2.3.0 2.2.1 2.2.0 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.4.0 1.4.1 1.4.2 1.4.5 1.4.6 1.4.7 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 All 69 releases
← All changes | app/Models/Customer.php +104 -30 1.4.1 → 2.4.5 View file →
@@ -3,11 +3,14 @@
3 3 namespace FluentSupport\App\Models;
4 4
5 5 use FluentSupport\Framework\Database\Orm\Builder;
6 6 use FluentSupport\Framework\Support\Arr;
7 +use FluentSupport\App\Models\Traits\CustomerTrait;
7 8
8 9 class Customer extends Person
9 10 {
11 + use CustomerTrait;
12 +
10 13 protected static $type = 'customer';
11 14
12 15 protected $searchable = [
13 16 'id',
@@ -18,8 +21,16 @@
18 21 'address_line_2',
19 22 'country'
20 23 ];
21 24
25 + /**
26 + * @return array
27 + */
28 + public function getSearchableFields()
29 + {
30 + return $this->searchable;
31 + }
32 +
22 33 public static function boot()
23 34 {
24 35 parent::boot();
25 36
@@ -24,9 +35,9 @@
24 35 parent::boot();
25 36
26 37 static::creating(function ($model) {
27 38 $model->person_type = static::$type;
28 - $model->hash = md5(time().wp_generate_uuid4());
39 + $model->hash = md5(time() . wp_generate_uuid4());
29 40 });
30 41
31 42 static::addGlobalScope(function (Builder $builder) {
32 43 $builder->where('person_type', 'customer');
@@ -33,50 +44,94 @@
33 44 });
34 45
35 46 }
36 47
48 + public static function mappables()
49 + {
50 + return [
51 + 'title' => __('Customer Title', 'fluent-support'),
52 + 'address_line_1' => __('Address Line 1', 'fluent-support'),
53 + 'address_line_2' => __('Address Line 2', 'fluent-support'),
54 + 'city' => __('City', 'fluent-support'),
55 + 'state' => __('State', 'fluent-support'),
56 + 'zip' => __('Zip Code', 'fluent-support'),
57 + 'country' => __('Country', 'fluent-support'),
58 + ];
59 + }
60 +
61 + /**
62 + * Find the customer for this data or create one. An existing record is only
63 + * updated when the data carries a user_id or remote_uid.
64 + *
65 + * @param array $customerData
66 + * @return false|Customer
67 + */
37 68 public static function maybeCreateCustomer($customerData)
38 69 {
39 70 $customer = self::getCustomerFromData($customerData);
40 -
41 - $user = get_user_by('email', $customerData['email']);
42 - if($user) {
43 - if($user->first_name) {
71 + $email = Arr::get($customerData, 'email');
72 + $user = $email ? get_user_by('email', $email) : false;
73 + if ($user) {
74 + if ($user->first_name) {
44 75 $customerData['first_name'] = $user->first_name;
45 76 }
46 - if($user->last_name) {
77 + if ($user->last_name) {
47 78 $customerData['last_name'] = $user->last_name;
48 79 }
49 - if(empty($customerData['first_name']) && empty($customerData['last_name'])) {
80 + if (empty($customerData['first_name']) && empty($customerData['last_name'])) {
50 81 $customerData['first_name'] = $user->display_name;
51 82 }
52 83 $customerData['user_id'] = $user->ID;
53 -
54 84 }
55 85
56 - if(!$customer) {
57 - if(!empty($customerData['last_ip_address'])) {
86 + if (!$customer) {
87 + if (!empty($customerData['last_ip_address'])) {
58 88 $customerData['ip_address'] = $customerData['last_ip_address'];
59 89 }
60 - // we have to create customer
90 +
91 + $customerData = self::explodeFullName($customerData);
92 +
61 93 $customer = self::create($customerData);
62 94
63 - do_action('fluent_support/customer_created', $customer);
95 + if ($customer && $customer->exists) {
96 + /*
97 + * Action on customer create
98 + *
99 + * @since v1.0.0
100 + * @param object $customer
101 + */
102 + do_action('fluent_support/customer_created', $customer);
103 + }
64 104
65 - } else {
66 - if(!empty($customerData['user_id']) || !empty($customerData['remote_uid'])) {
67 - $customerData = array_filter($customerData);
68 - $customer->fill($customerData);
69 - $customer->save();
70 - }
105 + return $customer;
71 106 }
72 107
108 + // Never link an existing row to a different WP account by email match.
109 + // Linking happens on create, on registration, or by an agent.
110 + $incomingUserId = (int) Arr::get($customerData, 'user_id');
111 +
112 + if ($incomingUserId && $incomingUserId !== (int) $customer->user_id) {
113 + unset($customerData['user_id']);
114 + }
115 +
116 + // The incoming email is unverified (WP lets users change theirs without
117 + // confirmation), so it must not overwrite a linked customer's address.
118 + // That only changes via EmailClaimService or an agent edit.
119 + if ((int) $customer->user_id) {
120 + unset($customerData['email']);
121 + }
122 +
123 + if (!empty($customerData['user_id']) || !empty($customerData['remote_uid'])) {
124 + $customerData = array_filter($customerData);
125 + $customer->fill($customerData);
126 + $customer->save();
127 + }
128 +
73 129 return $customer;
74 130 }
75 131
76 132
77 133 /**
78 - * One2Many: Customer has to many Click Tickets
79 134 * @return Model Collection
80 135 */
81 136 public function tickets()
82 137 {
@@ -88,35 +143,54 @@
88 143 $class, $foreign_key, 'id'
89 144 );
90 145 }
91 146
147 + /**
148 + * Look up by remote_uid, then user_id, then email.
149 + *
150 + * @param array $customerData
151 + * @return false|Customer
152 + */
92 153 public static function getCustomerFromData($customerData)
93 154 {
94 155 $remoteUid = Arr::get($customerData, 'remote_uid');
95 - $email = $customerData['email'];
156 + $email = Arr::get($customerData, 'email');
157 + $userId = (int) Arr::get($customerData, 'user_id');
96 158
97 - $customer = false;
98 - if($remoteUid) {
159 + if ($remoteUid) {
99 160 $customer = self::where('remote_uid', $remoteUid)->first();
161 +
162 + if ($customer) {
163 + return $customer;
164 + }
100 165 }
101 166
102 - if(!$customer) {
103 - if(!empty($customerData['user_id'])) {
104 - $customer = self::where('user_id', $customerData['user_id'])->first();
105 - }
106 - if(!$customer) {
107 - $customer = self::where('email', $email)->first();
108 - }
167 + // When a user id is given, don't fall back to email: a WP account's email
168 + // is unverified (the REST users endpoint skips confirmation), so it must
169 + // not reach another person's row. Email isn't unique, so take the oldest
170 + // match to keep replies threading onto the original record.
171 + if ($userId) {
172 + return self::where('user_id', $userId)->orderBy('id', 'ASC')->first() ?: false;
109 173 }
110 174
111 - return $customer;
175 + if ($email) {
176 + return self::where('email', $email)->orderBy('id', 'ASC')->first() ?: false;
177 + }
178 +
179 + return false;
112 180 }
113 181
182 + /**
183 + * @return mixed
184 + */
114 185 public function getTicketCounts()
115 186 {
116 187 return Ticket::where('customer_id', $this->id)->count();
117 188 }
118 189
190 + /**
191 + * @return mixed
192 + */
119 193 public function getResponseCounts()
120 194 {
121 195 return Conversation::where('person_id', $this->id)->count();
122 196 }