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 -28 1.4.6 → 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,48 +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 - $user = get_user_by('email', $customerData['email']);
41 - if($user) {
42 - 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) {
43 75 $customerData['first_name'] = $user->first_name;
44 76 }
45 - if($user->last_name) {
77 + if ($user->last_name) {
46 78 $customerData['last_name'] = $user->last_name;
47 79 }
48 - if(empty($customerData['first_name']) && empty($customerData['last_name'])) {
80 + if (empty($customerData['first_name']) && empty($customerData['last_name'])) {
49 81 $customerData['first_name'] = $user->display_name;
50 82 }
51 83 $customerData['user_id'] = $user->ID;
52 84 }
53 85
54 - if(!$customer) {
55 - if(!empty($customerData['last_ip_address'])) {
86 + if (!$customer) {
87 + if (!empty($customerData['last_ip_address'])) {
56 88 $customerData['ip_address'] = $customerData['last_ip_address'];
57 89 }
58 - // we have to create customer
90 +
91 + $customerData = self::explodeFullName($customerData);
92 +
59 93 $customer = self::create($customerData);
60 94
61 - 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 + }
62 104
63 - } else {
64 - if(!empty($customerData['user_id']) || !empty($customerData['remote_uid'])) {
65 - $customerData = array_filter($customerData);
66 - $customer->fill($customerData);
67 - $customer->save();
68 - }
105 + return $customer;
69 106 }
70 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 +
71 129 return $customer;
72 130 }
73 131
74 132
75 133 /**
76 - * One2Many: Customer has to many Click Tickets
77 134 * @return Model Collection
78 135 */
79 136 public function tickets()
80 137 {
@@ -86,35 +143,54 @@
86 143 $class, $foreign_key, 'id'
87 144 );
88 145 }
89 146
147 + /**
148 + * Look up by remote_uid, then user_id, then email.
149 + *
150 + * @param array $customerData
151 + * @return false|Customer
152 + */
90 153 public static function getCustomerFromData($customerData)
91 154 {
92 155 $remoteUid = Arr::get($customerData, 'remote_uid');
93 - $email = $customerData['email'];
156 + $email = Arr::get($customerData, 'email');
157 + $userId = (int) Arr::get($customerData, 'user_id');
94 158
95 - $customer = false;
96 - if($remoteUid) {
159 + if ($remoteUid) {
97 160 $customer = self::where('remote_uid', $remoteUid)->first();
161 +
162 + if ($customer) {
163 + return $customer;
164 + }
98 165 }
99 166
100 - if(!$customer) {
101 - if(!empty($customerData['user_id'])) {
102 - $customer = self::where('user_id', $customerData['user_id'])->first();
103 - }
104 - if(!$customer) {
105 - $customer = self::where('email', $email)->first();
106 - }
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;
107 173 }
108 174
109 - return $customer;
175 + if ($email) {
176 + return self::where('email', $email)->orderBy('id', 'ASC')->first() ?: false;
177 + }
178 +
179 + return false;
110 180 }
111 181
182 + /**
183 + * @return mixed
184 + */
112 185 public function getTicketCounts()
113 186 {
114 187 return Ticket::where('customer_id', $this->id)->count();
115 188 }
116 189
190 + /**
191 + * @return mixed
192 + */
117 193 public function getResponseCounts()
118 194 {
119 195 return Conversation::where('person_id', $this->id)->count();
120 196 }