PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / trunk
Fluent Support – Helpdesk & Customer Support Ticket System vtrunk
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 1.5.5 1.5.6 All 67 releases
fluent-support / app / Models / Customer.php

Customer.php in Fluent Support – Helpdesk & Customer Support Ticket System trunk, at app/Models/Customer.php

221 lines 7.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentSupport\App\Models;
4
5 use FluentSupport\Framework\Database\Orm\Builder;
6 use FluentSupport\Framework\Support\Arr;
7 use FluentSupport\App\Models\Traits\CustomerTrait;
8
9 class Customer extends Person
10 {
11 use CustomerTrait;
12
13 protected static $type = 'customer';
14
15 protected $searchable = [
16 'id',
17 'first_name',
18 'last_name',
19 'email',
20 'address_line_1',
21 'address_line_2',
22 'country'
23 ];
24
25 /**
26 * @return array
27 */
28 public function getSearchableFields()
29 {
30 return $this->searchable;
31 }
32
33 public static function boot()
34 {
35 parent::boot();
36
37 static::creating(function ($model) {
38 $model->person_type = static::$type;
39 $model->hash = md5(time() . wp_generate_uuid4());
40 });
41
42 static::addGlobalScope(function (Builder $builder) {
43 $builder->where('person_type', 'customer');
44 });
45
46 }
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 * maybeCreateCustomer method will update existing customer or create new
63 * This method will get request to create customer, this will check existence, if exist it will update otherwise it will create new.
64 * @param $customerData
65 * @return false|Customer
66 */
67 public static function maybeCreateCustomer($customerData)
68 {
69 $customer = self::getCustomerFromData($customerData);
70 $email = Arr::get($customerData, 'email');
71 $user = $email ? get_user_by('email', $email) : false;
72 if ($user) {
73 if ($user->first_name) {
74 $customerData['first_name'] = $user->first_name;
75 }
76 if ($user->last_name) {
77 $customerData['last_name'] = $user->last_name;
78 }
79 if (empty($customerData['first_name']) && empty($customerData['last_name'])) {
80 $customerData['first_name'] = $user->display_name;
81 }
82 $customerData['user_id'] = $user->ID;
83 }
84
85 if (!$customer) {
86 if (!empty($customerData['last_ip_address'])) {
87 $customerData['ip_address'] = $customerData['last_ip_address'];
88 }
89
90 $customerData = self::explodeFullName($customerData);
91
92 // we have to create customer
93 $customer = self::create($customerData);
94
95 /*
96 * Action on customer create
97 *
98 * @since v1.0.0
99 * @param object $customer
100 */
101 do_action('fluent_support/customer_created', $customer);
102
103 return $customer;
104 }
105
106 // An existing row may never be claimed for a WordPress account it is not
107 // already linked to. Binding happens when the row is first created, when
108 // the account registers (ProfileInfoService::onWPUserRegister), or when
109 // an agent sets it explicitly from the customer profile. Allowing an
110 // email match to write user_id here is what made a portal takeover
111 // persistent.
112 $incomingUserId = (int) Arr::get($customerData, 'user_id');
113
114 if ($incomingUserId && $incomingUserId !== (int) $customer->user_id) {
115 unset($customerData['user_id']);
116 }
117
118 // Intake may not move the contact address of a record that belongs to a
119 // WordPress account. Every caller here builds its payload from an
120 // address it has not verified -- most of them from the signed-in
121 // account's own email, which WordPress lets that account change with no
122 // confirmation at all.
123 //
124 // Without this, ProfileInfoService::onWPProfileUpdate holding the
125 // address is worth nothing: it refuses the unverified change, and then
126 // the next POST to /customer-portal/tickets resolves the same record
127 // through here and writes the new address anyway. It also produced
128 // duplicate rows on an address belonging to somebody else, without the
129 // collision check the profile_update path applies.
130 //
131 // The address moves through the two paths that prove something instead:
132 // EmailClaimService, where the customer opens a link sent to the address
133 // being claimed, and an agent editing the record directly.
134 if ((int) $customer->user_id) {
135 unset($customerData['email']);
136 }
137
138 if (!empty($customerData['user_id']) || !empty($customerData['remote_uid'])) {
139 $customerData = array_filter($customerData);
140 $customer->fill($customerData);
141 $customer->save();
142 }
143
144 return $customer;
145 }
146
147
148 /**
149 * One2Many: Customer has to many Click Tickets
150 * @return Model Collection
151 */
152 public function tickets()
153 {
154 $foreign_key = self::$type . '_id';
155
156 $class = __NAMESPACE__ . '\Ticket';
157
158 return $this->hasMany(
159 $class, $foreign_key, 'id'
160 );
161 }
162
163 /**
164 * getCustomerFromData method will return customer information by user id or email address
165 * @param $customerData
166 * @return false
167 */
168 public static function getCustomerFromData($customerData)
169 {
170 $remoteUid = Arr::get($customerData, 'remote_uid');
171 $email = Arr::get($customerData, 'email');
172 $userId = (int) Arr::get($customerData, 'user_id');
173
174 if ($remoteUid) {
175 $customer = self::where('remote_uid', $remoteUid)->first();
176
177 if ($customer) {
178 return $customer;
179 }
180 }
181
182 // A WordPress user id is the only identity that can be trusted here. An
183 // account email is editable by its own holder with no verification: the
184 // REST users endpoint calls wp_update_user() directly and skips the
185 // confirmation flow that profile.php runs. An email must therefore never
186 // be able to reach a customer row that belongs to somebody else.
187 // Both lookups order by id so the row that wins is always the oldest
188 // match rather than whatever the storage engine returns first. The email
189 // column carries a plain index, not a unique one, so duplicates are
190 // possible and an inbound reply must keep threading onto the original
191 // record. Helper::getCurrentPerson() orders for the same reason.
192 if ($userId) {
193 return self::where('user_id', $userId)->orderBy('id', 'ASC')->first() ?: false;
194 }
195
196 if ($email) {
197 return self::where('email', $email)->orderBy('id', 'ASC')->first() ?: false;
198 }
199
200 return false;
201 }
202
203 /**
204 * getTicketCounts method will return the number of tickets by a customer
205 * @return mixed
206 */
207 public function getTicketCounts()
208 {
209 return Ticket::where('customer_id', $this->id)->count();
210 }
211
212 /**
213 * getResponseCounts will return the number of responses by a customer
214 * @return mixed
215 */
216 public function getResponseCounts()
217 {
218 return Conversation::where('person_id', $this->id)->count();
219 }
220 }
221