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