PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 1.5.3
Fluent Support – Helpdesk & Customer Support Ticket System v1.5.3
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 1.5.5 All 68 releases
fluent-support / app / Models / Ticket.php

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

498 lines 13.3 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\App\Services\Helper;
6 use FluentSupport\Framework\Support\Arr;
7
8 class Ticket extends Model
9 {
10 protected $table = 'fs_tickets';
11
12 /**
13 * The attributes that are mass assignable.
14 *
15 * @var array
16 */
17 protected $fillable = [
18 'customer_id',
19 'agent_id',
20 'product_id',
21 'mailbox_id',
22 'product_source',
23 'privacy',
24 'priority',
25 'client_priority',
26 'status',
27 'title',
28 'slug',
29 'hash',
30 'source',
31 'message_id',
32 'content',
33 'last_agent_response',
34 'last_customer_response',
35 'waiting_since',
36 'response_count',
37 'first_response_time',
38 'total_close_time',
39 'resolved_at',
40 'closed_by'
41 ];
42
43 public static function boot()
44 {
45 static::creating(function ($model) {
46 $model->slug = static::slugify($model->title);
47 $model->hash = substr(md5(time() . wp_generate_uuid4()), 0, 8) . mt_rand(1, 99);
48 $model->last_customer_response = current_time('mysql');
49 $model->content_hash = md5($model->content);
50 $model->created_at = current_time('mysql');
51 $model->updated_at = current_time('mysql');
52 $model->waiting_since = current_time('mysql');
53 });
54 }
55
56 /**
57 * $searchable Columns in table to search
58 * @var array
59 */
60 protected $searchable = [
61 'title',
62 'slug',
63 'content',
64 'id'
65 ];
66
67 /**
68 * Local scope to filter subscribers by search/query string
69 * @param ModelQueryBuilder $query
70 * @param string $search
71 * @return ModelQueryBuilder
72 */
73 public function scopeSearchBy($query, $search)
74 {
75 if ($search) {
76
77 if (strpos($search, ':')) {
78 $array = explode(':', $search);
79 $column = $array[0];
80 $value = $array[1];
81 $columns = $this->fillable;
82 $columns[] = 'id';
83
84 if (in_array($column, $columns) && $value) {
85 if (is_numeric($value)) {
86 $query->where($column, $value);
87 } else {
88 $query->where($column, 'LIKE', "%$value%");
89 }
90 return $query;
91 }
92 }
93
94 $fields = $this->searchable;
95 $query->where(function ($query) use ($fields, $search) {
96 $query->where(array_shift($fields), 'LIKE', "%$search%");
97
98 foreach ($fields as $field) {
99 $query->orWhere($field, 'LIKE', "$search%");
100 }
101 });
102 }
103
104 return $query;
105 }
106
107 /**
108 * Local scope to filter subscribers by search/query string
109 * @param ModelQueryBuilder $query
110 * @param array $statuses
111 * @return ModelQueryBuilder
112 */
113 public function scopeFilterByStatues($query, $statuses)
114 {
115 if ($statuses) {
116 $query->whereIn('status', $statuses);
117 }
118
119 return $query;
120 }
121
122 public function scopeWaitingOnly($query)
123 {
124 global $wpdb;
125 $query->where(function ($q) use ($wpdb) {
126 $q->whereRaw($wpdb->prefix . 'fs_tickets.last_agent_response < ' . $wpdb->prefix . 'fs_tickets.last_customer_response')
127 ->orWhereNull('last_agent_response')
128 ->orWhere('status', 'new');
129 });
130 return $query;
131 }
132
133 public function scopeApplyFilters($query, $filters)
134 {
135 $supportedColumns = ['product_id', 'client_priority', 'priority', 'mailbox_id'];
136 foreach ($filters as $filterKey => $filterValue) {
137 if (!$filterValue && ($filterValue !== '0' || $filterValue !== 0)) {
138 continue;
139 }
140 if ($filterKey == 'status_type') {
141 $statusArray = Helper::getTkStatusesByGroupName($filterValue);
142 if ($statusArray) {
143 $query->whereIn('status', $statusArray);
144 }
145 } else if (in_array($filterKey, $supportedColumns)) {
146 $query->where($filterKey, $filterValue);
147 } else if ($filterKey == 'waiting_for_reply') {
148 if ($filterValue != 'yes') {
149 continue;
150 }
151 $query = $this->scopeWaitingOnly($query);
152 } else if ($filterKey == 'agent_id') {
153 if ($filterValue == 'unassigned') {
154 $query->whereNull($filterKey);
155 } else {
156 $query->where($filterKey, $filterValue);
157 }
158 } else if ($filterKey == 'ticket_tags') {
159 if (!$filterValue) {
160 continue;
161 }
162 $query->whereHas('tags', function ($q) use ($filterValue) {
163 $q->whereIn('tag_id', $filterValue);
164 });
165 }
166 }
167
168 return $query;
169 }
170
171 /**
172 * Local scope to filter subscribers by search/query string
173 * @param ModelQueryBuilder $query
174 * @param array $statuses
175 * @return ModelQueryBuilder
176 */
177 public function scopeFilterByAgentId($query, $agentId)
178 {
179 if ($agentId) {
180 $query->where('agent_id', $agentId);
181 }
182
183 return $query;
184 }
185
186 /**
187 * Local scope to filter subscribers by search/query string
188 * @param ModelQueryBuilder $query
189 * @param int $customerId
190 * @return ModelQueryBuilder
191 */
192 public function scopeFilterByCustomerId($query, $customerId)
193 {
194 $query->where('customer_id', $customerId);
195
196 return $query;
197 }
198
199 /**
200 * Local scope to filter subscribers by search/query string
201 * @param ModelQueryBuilder $query
202 * @param int $productId
203 * @return ModelQueryBuilder
204 */
205 public function scopeFilterByProductId($query, $productId)
206 {
207 if ($productId) {
208 $query->where('product_id', $productId);
209 }
210
211 return $query;
212 }
213
214 /**
215 * Local scope to filter subscribers by search/query string
216 * @param ModelQueryBuilder $query
217 * @param array $priorities
218 * @return ModelQueryBuilder
219 */
220 public function scopeFilterByPriorities($query, $priorities)
221 {
222 if ($priorities) {
223 $query->whereIn('priority', $priorities);
224 }
225
226 return $query;
227 }
228
229
230 /**
231 * One2Many: Customer has to many Click Tickets
232 * @return Model Collection
233 */
234 public function responses()
235 {
236 $class = __NAMESPACE__ . '\Conversation';
237
238 return $this->hasMany(
239 $class, 'ticket_id', 'id'
240 );
241 }
242
243 public function preview_response()
244 {
245 $class = __NAMESPACE__ . '\Conversation';
246
247 return $this->hasOne(
248 $class, 'ticket_id', 'id'
249 );
250 }
251
252 public function tags()
253 {
254 $class = __NAMESPACE__ . '\TicketTag';
255
256 return $this->belongsToMany(
257 $class, 'fs_tag_pivot', 'source_id', 'tag_id'
258 )->wherePivot('source_type', 'ticket_tag');
259 }
260
261
262 /**
263 * One2one: Customer has to many Click Tickets
264 * @return Model Collection
265 */
266 public function customer()
267 {
268 $class = __NAMESPACE__ . '\Customer';
269
270 return $this->belongsTo(
271 $class, 'customer_id', 'id'
272 );
273 }
274
275 /**
276 * One2one: Customer has to many Click Tickets
277 * @return Model Collection
278 */
279 public function agent()
280 {
281 $class = __NAMESPACE__ . '\Agent';
282
283 return $this->belongsTo(
284 $class, 'agent_id', 'id'
285 );
286 }
287
288 public function closed_by_person()
289 {
290 $class = __NAMESPACE__ . '\Person';
291
292 return $this->belongsTo(
293 $class, 'closed_by', 'id'
294 );
295 }
296
297 public function product()
298 {
299 $class = __NAMESPACE__ . '\Product';
300
301 return $this->belongsTo(
302 $class, 'product_id', 'id'
303 );
304 }
305
306 public function mailbox()
307 {
308 $class = __NAMESPACE__ . '\MailBox';
309
310 return $this->belongsTo(
311 $class, 'mailbox_id', 'id'
312 );
313 }
314
315
316 public function deleteTicket()
317 {
318 do_action('fluent_support/deleting_ticket', $this);
319 // delete the responses first
320 Conversation::where('ticket_id', $this->id)->delete();
321 // Delete the ticket meta
322 Meta::where('object_type', 'ticket_meta')->where('object_id', $this->id)->delete();
323
324 Ticket::where('id', $this->id)->delete();
325 }
326
327 public static function slugify($title)
328 {
329 $slug = sanitize_title($title, 'support-ticket-' . time(), 'display');
330 if (Ticket::where('slug', $slug)->first()) {
331 $slug .= '-' . time();
332 }
333 return $slug;
334 }
335
336 public function hasTag($tagId)
337 {
338 $tags = $this->tags;
339 foreach ($tags as $tag) {
340 if ($tag->id == $tagId) {
341 return true;
342 }
343 }
344
345 return false;
346 }
347
348 public function attachments()
349 {
350 $class = __NAMESPACE__ . '\Attachment';
351 return $this->hasMany($class, 'ticket_id', 'id')->where('conversation_id', NULL);
352 }
353
354 public function customData($scope = 'admin', $rendered = false)
355 {
356 if (!defined('FLUENTSUPPORTPRO')) {
357 return [];
358 }
359
360 $fields = \FluentSupportPro\App\Services\CustomFieldsService::getFieldLabels($scope);
361
362 if (!$fields) {
363 return [];
364 }
365
366 $keys = array_keys($fields);
367
368 $customRows = Meta::where('object_type', 'ticket_meta')->where('object_id', $this->id)
369 ->whereIn('key', $keys)
370 ->get();
371
372 if (!$customRows) {
373 return [];
374 }
375
376 $formattedData = [];
377
378 $customRenderers = \FluentSupportPro\App\Services\CustomFieldsService::getCustomerRenderers();
379
380 foreach ($customRows as $row) {
381 $dataKey = $row->key;
382
383 $value = $row->value;
384
385 $fieldType = $fields[$dataKey]['type'];
386
387 if ($value) {
388 if (in_array($fieldType, $customRenderers) && $rendered) {
389 $value = apply_filters('fluent_support/custom_field_render_' . $fieldType, $value, $scope);
390 } else if ($fieldType == 'checkbox') {
391 $value = array_values(array_filter(explode('|', $value)));
392 }
393 }
394
395 $formattedData[$dataKey] = $value;
396 }
397
398 return $formattedData;
399 }
400
401 public function syncCustomFields($data)
402 {
403 if (!is_array($data)) {
404 return false;
405 }
406
407 $fields = apply_filters('fluent_support/ticket_custom_fields', []);
408
409 if (!$fields) {
410 return false;
411 }
412
413 $keys = array_keys($fields);
414
415 $validData = Arr::only($data, $keys);
416
417 foreach ($validData as $dataKey => $validDatum) {
418 if ($fields[$dataKey]['type'] == 'checkbox' || is_array($validDatum)) {
419 $validDatum = implode('|', $validDatum);
420 $validDatum = '|' . $validDatum . '|';
421 }
422
423 $exist = Meta::where('object_type', 'ticket_meta')->where('object_id', $this->id)
424 ->where('key', $dataKey)
425 ->first();
426
427 if ($exist) {
428 $exist->value = $validDatum;
429 $exist->save();
430 } else {
431 Meta::insert([
432 'object_type' => 'ticket_meta',
433 'object_id' => $this->id,
434 'key' => $dataKey,
435 'value' => $validDatum
436 ]);
437 }
438 }
439
440 // maybe delete data
441 $deletedSlugs = array_diff($keys, array_keys($validData));
442
443 if ($deletedSlugs) {
444 Meta::where('object_type', 'ticket_meta')->where('object_id', $this->id)
445 ->whereIn('key', $deletedSlugs)
446 ->delete();
447 }
448
449 return true;
450 }
451
452 public function getLastAgentResponse()
453 {
454 return \FluentSupport\App\App::db()->table('fs_conversations')
455 ->select(['fs_conversations.*'])
456 ->where('fs_conversations.conversation_type', 'response')
457 ->where('fs_conversations.ticket_id', $this->id)
458 ->join('fs_persons', 'fs_persons.person_type', '=', 'agent')
459 ->orderBy('fs_conversations.id', 'DESC')
460 ->first();
461 }
462
463 public function getLastResponse()
464 {
465 return \FluentSupport\App\App::db()->table('fs_conversations')
466 ->where('ticket_id', $this->id)
467 ->where('conversation_type', 'response')
468 ->orderBy('id', 'DESC')
469 ->first();
470 }
471
472 public function applyTags($tagIds)
473 {
474 $result = false;
475 foreach ($tagIds as $tagId) {
476 if (!$this->hasTag($tagId)) {
477 $this->tags()->attach($tagId, ['source_type' => 'ticket_tag']);
478 $result = true;
479 do_action('fluent_support/ticket_tag_added', $tagId, $this);
480 }
481 }
482 return $result;
483 }
484
485 public function detachTags($tagIds)
486 {
487 $result = false;
488 foreach ($tagIds as $tagId) {
489 if ($this->hasTag($tagId)) {
490 $this->tags()->detach($tagId);
491 do_action('fluent_support/ticket_tag_removed', $tagId, $this);
492 $result = true;
493 }
494 }
495 return $result;
496 }
497 }
498