PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 1.10.4
Fluent Support – Helpdesk & Customer Support Ticket System v1.10.4
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.10.4, at app/Models/Ticket.php

1,665 lines 52.9 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 Exception;
6 use FluentSupport\App\Http\Controllers\AuthController;
7 use FluentSupport\App\Modules\PermissionManager;
8 use FluentSupport\App\Services\Helper;
9 use FluentSupport\App\Services\ProfileInfoService;
10 use FluentSupport\App\Services\TicketHelper;
11 use FluentSupport\App\Services\TicketQueryService;
12 use FluentSupport\App\Services\Tickets\ResponseService;
13 use FluentSupport\App\Services\Tickets\TicketService;
14 use FluentSupport\Framework\Support\Arr;
15
16 class Ticket extends Model
17 {
18 protected $table = 'fs_tickets';
19
20 protected $dates = ['waiting_since'];
21
22 /**
23 * The attributes that are mass assignable.
24 *
25 * @var array
26 */
27 protected $fillable = [
28 'customer_id',
29 'agent_id',
30 'product_id',
31 'mailbox_id',
32 'product_source',
33 'privacy',
34 'priority',
35 'client_priority',
36 'status',
37 'title',
38 'slug',
39 'hash',
40 'source',
41 'message_id',
42 'content',
43 'last_agent_response',
44 'last_customer_response',
45 'waiting_since',
46 'response_count',
47 'first_response_time',
48 'total_close_time',
49 'resolved_at',
50 'closed_by'
51 ];
52
53 public static function boot()
54 {
55 parent::boot();
56
57 static::creating(function ($model) {
58 if (empty($model->slug)) {
59 $model->slug = static::slugify($model->title);
60 }
61
62 $model->hash = substr(md5(time() . wp_generate_uuid4()), 0, 8) . wp_rand(1, 99);
63 $model->content_hash = md5($model->content);
64
65 $model->last_customer_response = current_time('mysql');
66 $model->created_at = current_time('mysql');
67 $model->updated_at = current_time('mysql');
68 $model->waiting_since = current_time('mysql');
69
70 });
71
72 static::deleting(function ($model) {
73 //Delete the ticket meta
74 Meta::where('object_type', 'ticket_meta')->where('object_id', $model->id)->delete();
75 //Delete all cc info for the ticket
76 Meta::where('object_type', 'ticket')->where('object_id', $model->id)->delete();
77 //Delete draft info
78 Meta::where('object_type', '_fs_auto_draft')->where('object_id', $model->id)->delete();
79 //delete the responses first
80 Conversation::deleteAll($model->id);
81 });
82 }
83
84 /**
85 * $searchable Columns in table to search
86 * @var array
87 */
88 protected $searchable = [
89 'content',
90 'title',
91 'slug',
92 'id'
93 ];
94
95 /**
96 * Local scope to filter tickets by search/query string
97 * @param ModelQueryBuilder $query
98 * @param string $search
99 * @return ModelQueryBuilder
100 */
101 public function scopeSearchBy($query, $search)
102 {
103 if (strpos($search, ':')) {
104 $array = explode(':', $search);
105 $column = $array[0];
106 $value = $array[1];
107 $columns = $this->fillable;
108 $columns[] = 'id';
109
110 if (in_array($column, $columns) && $value) {
111 if (is_numeric($value)) {
112 $query->where($column, $value);
113 } else {
114 $query->where($column, 'LIKE', "%$value%");
115 }
116 return $query;
117 }
118 }
119
120 $fields = $this->searchable;
121 $query->where(function ($query) use ($fields, $search) {
122 $query->where(array_shift($fields), 'LIKE', "%$search%");
123 foreach ($fields as $field) {
124 $query->orWhere($field, 'LIKE', "%$search%");
125 }
126 });
127
128 return $query;
129 }
130
131 /**
132 * Local scope to filter tickets by different filtering condition
133 * @param ModelQueryBuilder $query
134 * @param mixed $search
135 * @return ModelQueryBuilder
136 */
137
138 public function doSearchForAdvancedFilter($query, $search)
139 {
140 foreach ($search as $s) {
141 $operator = $s['operator'];
142 //If selected item for ticket either title or content
143 if (in_array($s['property'], ['title', 'content'])) {
144 //If the selected condition is contains, query operator id LIKE
145 if ($operator == 'contains') {
146 $query = $query->where(function ($query) use ($s) {
147 $query->where($s['property'], 'LIKE', "%" . $s['value'] . "%");
148 });
149 } elseif ($operator == 'not_contains') {
150 //If the selected condition is not_contains, query operator id NOT LIKE
151 $query = $query->where(function ($query) use ($s) {
152 $query->where($s['property'], 'NOT LIKE', '%' . $s['value'] . '%');
153 });
154 }
155 }
156
157 //If selected item is Ticket Conversation Content
158 if ($s['property'] == 'conversation_content') {
159 $operator = $s['operator'];
160 if ($operator == 'contains') {
161 $query = $query->whereHas('responses', function ($q) use ($s) {
162 $q->where('content', 'LIKE', "%" . $s['value'] . "%");
163 });
164
165 } else if ($operator == 'not_contains') {
166 $query = $query->whereHas('responses', function ($q) use ($s) {
167 $q->where('content', 'NOT LIKE', "%" . $s['value'] . "%");
168 });
169 }
170 }
171
172 //If selected item is Ticket created or Last Response or Customer Waiting For, or Last Agent Response or Last Customer Response
173 if (in_array($s['property'], ['created_at', 'updated_at', 'waiting_since', 'last_agent_response', 'last_customer_response'])) {
174 $query = (new \FluentSupport\App\Models\Ticket())->buildDateBaseFilterQuery($query, $s);
175 }
176
177 //If selected item is Ticket Status or Client Priority or Agent Priority or Tags or Product or Waiting For Reply
178 if (in_array($s['property'], ['status', 'client_priority', 'priority', 'tags', 'product', 'waiting_for_reply', 'agent_id', 'mailbox_id'])) {
179 $query = (new \FluentSupport\App\Models\Ticket())->buildPropertiesFilterQuery($query, $s);
180 }
181 }
182 return $query;
183 }
184
185 /**
186 * Local scope to filter subscribers by search/query string
187 * @param ModelQueryBuilder $query
188 * @param array $statuses
189 * @return ModelQueryBuilder
190 */
191 public function scopeFilterByStatues($query, $statuses)
192 {
193 if ($statuses) {
194 $query->whereIn('status', $statuses);
195 }
196
197 return $query;
198 }
199
200 /**
201 * Local scope to filter tickets by not response by agent
202 * @param $query
203 * @return mixed
204 */
205 public function scopeWaitingOnly($query)
206 {
207 $query->where(function ($q) {
208 $q->whereColumn('last_agent_response', '<', 'last_customer_response')
209 ->orWhereNull('last_agent_response')
210 ->orWhere('status', 'new');
211 });
212 return $query;
213 }
214
215 /**
216 * scopeApplyFilters method will filet ticket based on the selected filters
217 * This method will get filter option as parameter, loop through and apply conditions in query
218 * @param $query
219 * @param $filters
220 * @return ModelQueryBuilder
221 */
222 public function scopeApplyFilters($query, $filters)
223 {
224 $supportedColumns = ['product_id', 'client_priority', 'priority', 'mailbox_id'];
225 foreach ($filters as $filterKey => $filterValue) {
226 if (!$filterValue && ($filterValue !== '0' || $filterValue !== 0)) {
227 continue;
228 }
229 //If filer using status
230 if ($filterKey == 'status_type') {
231 //Get list of ticket status
232 $statusArray = Helper::getTkStatusesByGroupName($filterValue);
233 if ($statusArray) {
234 //Apply filet where status in
235 $query->whereIn('status', $statusArray);
236 }
237 } else if (in_array($filterKey, $supportedColumns)) {
238 // Use whereIn for product_id (always expects array) and where for other columns
239 if ($filterKey === 'product_id') {
240 $query->whereIn($filterKey, $filterValue);
241 } else {
242 $query->where($filterKey, $filterValue);
243 }
244 } else if ($filterKey == 'waiting_for_reply') {
245 if ($filterValue != 'yes') {
246 continue;
247 }
248 //Apply filter where no response by agent
249 $query = $this->scopeWaitingOnly($query);
250 } else if ($filterKey == 'agent_id') {
251 //Apply filter where ticket is not assigned
252 if ($filterValue == 'unassigned') {
253 $query->whereNull($filterKey);
254 } else {
255 if (defined('FLUENTSUPPORTPRO')) {
256 if (isset($filters['watcher']) && $filters['watcher'] == 'watcher') {
257 $watcherTickets = TicketHelper::getWatcherTicketIds($filterValue);
258 $query->whereIn('id', $watcherTickets);
259 } else {
260 //Apply filter, get only assigned ticket
261 $query->where($filterKey, $filterValue);
262 }
263 }
264 }
265 } else if ($filterKey == 'ticket_tags') {
266 if (!$filterValue) {
267 continue;
268 }
269 //Apply filter where ticket only has this tag id
270 $query->whereHas('tags', function ($q) use ($filterValue) {
271 $q->whereIn('tag_id', $filterValue);
272 });
273 }
274 }
275
276 return $query;
277 }
278
279 /**
280 * Local scope to filter tickets by agent id
281 * @param ModelQueryBuilder $query
282 * @param int $agentId
283 * @return ModelQueryBuilder
284 */
285 public function scopeFilterByAgentId($query, $agentId)
286 {
287 if ($agentId) {
288 $query->where('agent_id', $agentId);
289 }
290
291 return $query;
292 }
293
294 /**
295 * Local scope to filter subscribers by search/query string
296 * @param ModelQueryBuilder $query
297 * @param int $customerId
298 * @return ModelQueryBuilder
299 */
300 public function scopeFilterByCustomerId($query, $customerId)
301 {
302 $query->where('customer_id', $customerId);
303
304 return $query;
305 }
306
307 /**
308 * Local scope to filter subscribers by search/query string
309 * @param ModelQueryBuilder $query
310 * @param int $productId
311 * @return ModelQueryBuilder
312 */
313 public function scopeFilterByProductId($query, $productId)
314 {
315 if ($productId) {
316 $query->where('product_id', $productId);
317 }
318
319 return $query;
320 }
321
322 /**
323 * Local scope to filter subscribers by search/query string
324 * @param ModelQueryBuilder $query
325 * @param array $priorities
326 * @return ModelQueryBuilder
327 */
328 public function scopeFilterByPriorities($query, $priorities)
329 {
330 if ($priorities) {
331 $query->whereIn('priority', $priorities);
332 }
333
334 return $query;
335 }
336
337 /**
338 * @param $filter
339 * @return string[]
340 */
341
342 public static function parseRelationalFilterQueryMethods($filter)
343 {
344 // default operator = in
345 $method = 'whereHas';
346 $subMethod = 'whereIn';
347
348 switch ($filter['operator']) {
349 case 'not_in':
350 $method = 'whereDoesntHave';
351 $subMethod = 'whereIn';
352
353 break;
354 case 'in_all':
355 $method = 'whereHas';
356 $subMethod = 'where';
357
358 break;
359 case 'not_in_all':
360 $method = 'whereDoesntHave';
361 $subMethod = 'where';
362
363 break;
364 }
365
366 return [$method, $subMethod];
367 }
368
369 /**
370 * Parse filter to set proper operator and value for the filter query.
371 *
372 * @param array $filter
373 * @return array
374 */
375 public static function filterParser($filter)
376 {
377 switch ($filter['operator']) {
378 case 'before':
379 $filter['operator'] = '<';
380 $filter['value'] = $filter['value'] . ' 23:59:59';
381 break;
382
383 case 'after':
384 $filter['operator'] = '>';
385 $filter['value'] = $filter['value'] . ' 23:59:59';
386 break;
387
388 case 'date_equal':
389 $filter['operator'] = 'LIKE';
390 $filter['value'] = '%' . $filter['value'] . '%';
391 break;
392
393 case 'days_before':
394 $filter['operator'] = '<';
395 $filter['value'] = date('Y-m-d', current_time('timestamp') - $filter['value'] * 24 * 60 * 60);
396 break;
397
398 case 'days_within':
399 $filter['operator'] = 'BETWEEN';
400 $filter['value'] = [
401 date('Y-m-d', current_time('timestamp') - $filter['value'] * 24 * 60 * 60),
402 date('Y-m-d') . ' 23:59:59'
403 ];
404 break;
405 case 'date_range':
406 $filter['operator'] = 'BETWEEN';
407 if (isset($filter['value'][0]))
408 $filter['value'][0] .= ' 00:00:00';
409 if (isset($filter['value'][1]))
410 $filter['value'][1] .= ' 23:59:59';
411 break;
412 }
413
414 return $filter;
415 }
416
417 /**
418 * @param \FluentSupport\Framework\Database\Orm\Builder|\FluentSupport\Framework\Database\Query\Builder $query
419 * @param array $filters
420 * @return ModelQueryBuilder
421 */
422 public function buildDateBaseFilterQuery($query, $filters)
423 {
424 $filter = static::filterParser($filters);
425 $query->where(function ($dateQuery) use ($filter) {
426
427 if ($filter['operator'] == 'BETWEEN') {
428 $dateQuery->whereBetween($filter['property'], $filter['value']);
429 } else {
430 $dateQuery->where($filter['property'], $filter['operator'], $filter['value']);
431 }
432 });
433
434 return $query;
435 }
436
437 /**
438 * Relation builder
439 * @param $relation
440 * @param $query
441 * @param $method
442 * @param $subMethod
443 * @param $subField
444 * @param $filter
445 * @param false $provider
446 * @return ModelQueryBuilder
447 */
448
449 public static function buildRelationFilterQuery($relation, $query, $method, $subMethod, $subField, $filter, $provider = false)
450 {
451 if (in_array($filter['operator'], ['in_all', 'not_in_all']) && $filter['value']) {
452 foreach ($filter['value'] as $item) {
453 $query = static::buildRelationFilterQuery($relation, $query, $method, $subMethod, $subField, ['value' => $item, 'operator' => ''], $provider);
454 }
455 } else {
456 $query = $query->{$method}($relation, function ($relationQuery) use ($subMethod, $subField, $filter, $provider) {
457 $relationQuery = $relationQuery->{$subMethod}($subField, $filter['value']);
458
459 if ($provider) {
460 $relationQuery = $relationQuery->where('provider', $provider);
461 }
462
463 return $relationQuery;
464 });
465 }
466
467 return $query;
468 }
469
470 /**
471 * get tickets by advanced filter segment data
472 * @param $query
473 * @param $filter
474 * @return ModelQueryBuilder
475 */
476
477 public function buildPropertiesFilterQuery($query, $filter)
478 {
479 if (in_array($filter['property'], ['tags', 'product'])) {
480 $subField = $filter['property'] == 'tags' ? 'tag_id' : 'product_id';
481 list($method, $subMethod) = static::parseRelationalFilterQueryMethods($filter);
482 $query = static::buildRelationFilterQuery($filter['property'], $query, $method, $subMethod, $subField, $filter);
483 } elseif ($filter['property'] == 'waiting_for_reply') {
484 if (($filter['value'] == 'yes' && $filter['operator'] == 'in') || ($filter['value'] == 'no' && $filter['operator'] == 'not_in')) {
485 $query = $query->where(function ($q) {
486 $q->whereColumn('last_agent_response', '<', 'last_customer_response')
487 ->orWhereNull('last_agent_response')
488 ->orWhere('status', 'new');
489 });
490 } else {
491 $query = $query->where(function ($q) {
492 $q->whereColumn('last_customer_response', '<', 'last_agent_response');
493 });
494 }
495 } else {
496 $method = $filter['operator'] == 'in' ? 'whereIn' : 'whereNotIn';
497 $query = $query->{$method}($filter['property'], (array)$filter['value']);
498 }
499 return $query;
500 }
501
502 /**
503 * method to search by properties
504 * @param $provider
505 * @param $query
506 * @param $search
507 * @param string $operator
508 * @return ModelQueryBuilder
509 */
510 public function buildSearchableQuery($provider, $query, $search, $operator = 'LIKE')
511 {
512 switch ($provider) {
513 case 'customer':
514 $fields = (new Customer())->getSearchableFields();
515 break;
516 case 'agent':
517 $fields = (new Agent())->getSearchableFields();
518 break;
519 default:
520 $fields = $this->searchable;
521 break;
522 }
523
524 $query->whereHas($provider, function ($query) use ($fields, $search, $operator) {
525 $query->where(array_shift($fields), $operator, $search);
526
527 $nameArray = explode(' ', $search);
528
529 if (count($nameArray) >= 2) {
530 $query->orWhere(function ($q) use ($nameArray, $operator) {
531 $firstName = array_shift($nameArray);
532 $lastName = implode(' ', $nameArray);
533
534 $q->where('first_name', $operator, $firstName);
535 $q->where('last_name', $operator, $lastName);
536 });
537 }
538
539 foreach ($fields as $field) {
540 $query->orWhere($field, $operator, $search);
541 }
542 });
543
544 return $query;
545 }
546
547 /**
548 * Filter by ticket general properties like customer name, agent name etc
549 * @param $provider
550 * @param $query
551 * @param $filters
552 * @return ModelQueryBuilder
553 */
554 public function filterTicketByUser($provider, $query, $filters)
555 {
556 foreach ($filters as $filter) {
557 if ($filter['operator'] == 'in' || $filter['operator'] == 'not_in') {
558 $method = $filter['operator'] == 'in' ? 'whereIn' : 'whereNotIn';
559 $query = $query->whereHas($provider, function ($q) use ($method, $filter) {
560 $q->{$method}($filter['property'], $filter['value']);
561 });
562 }
563
564 if ($filter['operator'] == 'contains' || $filter['operator'] == 'not_contains') {
565 $operator = $filter['operator'] == 'contains' ? 'LIKE' : 'NOT LIKE';
566 $query->whereHas($provider, function ($q) use ($operator, $filter) {
567 $q->where($filter['property'], $operator, '%' . $filter['value'] . '%');
568 });
569 }
570
571 if ($filter['operator'] == '=' || $filter['operator'] == '!=') {
572 $operator = $filter['operator'];
573 $query->whereHas($provider, function ($q) use ($operator, $filter) {
574 $q->where($filter['property'], $operator, $filter['value']);
575 });
576 }
577 }
578 return $query;
579 }
580
581 /**
582 * One2Many: Customer has to many Click Tickets
583 * @return Model Collection
584 */
585 public function responses()
586 {
587 $class = __NAMESPACE__ . '\Conversation';
588
589 return $this->hasMany(
590 $class, 'ticket_id', 'id'
591 )->with('person', 'attachments', 'ccinfo')
592 ->latest('id');
593 }
594
595 public function preview_response()
596 {
597 $class = __NAMESPACE__ . '\Conversation';
598
599 return $this->hasOne(
600 $class, 'ticket_id', 'id'
601 );
602 }
603
604 public function tags()
605 {
606 $class = __NAMESPACE__ . '\TicketTag';
607
608 return $this->belongsToMany(
609 $class, 'fs_tag_pivot', 'source_id', 'tag_id'
610 )->wherePivot('source_type', 'ticket_tag');
611 }
612
613 public function watchers()
614 {
615 $class = __NAMESPACE__ . '\TagPivot';
616
617 return $this->hasMany($class, 'source_id', 'id')
618 ->where('source_type', 'ticket_watcher')
619 ->select(['tag_id']);
620 }
621
622 /**
623 * One2one: Customer has to many Click Tickets
624 * @return Model Collection
625 */
626 public function customer()
627 {
628 $class = __NAMESPACE__ . '\Customer';
629
630 return $this->belongsTo(
631 $class, 'customer_id', 'id'
632 );
633 }
634
635 /**
636 * One2one: Customer has to many Click Tickets
637 * @return Model Collection
638 */
639 public function agent()
640 {
641 $class = __NAMESPACE__ . '\Agent';
642
643 return $this->belongsTo(
644 $class, 'agent_id', 'id'
645 );
646 }
647
648 public function closed_by_person()
649 {
650 $class = __NAMESPACE__ . '\Person';
651
652 return $this->belongsTo(
653 $class, 'closed_by', 'id'
654 );
655 }
656
657 public function product()
658 {
659 $class = __NAMESPACE__ . '\Product';
660
661 return $this->belongsTo(
662 $class, 'product_id', 'id'
663 );
664 }
665
666 public function mailbox()
667 {
668 $class = __NAMESPACE__ . '\MailBox';
669
670 return $this->belongsTo(
671 $class, 'mailbox_id', 'id'
672 );
673 }
674
675
676 public function deleteTicket()
677 {
678 /*
679 * Action on ticket deleting
680 *
681 * @since v1.0.0
682 * @param object $ticket
683 */
684 do_action('fluent_support/deleting_ticket', $this);
685 // Delete the ticket
686 $this->delete();
687 }
688
689 public static function slugify($title)
690 {
691 $slug = sanitize_title($title, 'support-ticket-' . time(), 'display');
692 if (Ticket::where('slug', $slug)->first()) {
693 $slug .= '-' . time();
694 }
695 return $slug;
696 }
697
698 public function hasTag($tagId)
699 {
700 $tags = $this->tags;
701 foreach ($tags as $tag) {
702 if ($tag->id == $tagId) {
703 return true;
704 }
705 }
706
707 return false;
708 }
709
710 public function attachments()
711 {
712 $class = __NAMESPACE__ . '\Attachment';
713 return $this->hasMany($class, 'ticket_id', 'id')->where('conversation_id', NULL);
714 }
715
716 public function customData($scope = 'admin', $rendered = false)
717 {
718 if (!defined('FLUENTSUPPORTPRO')) {
719 return [];
720 }
721
722 $fields = \FluentSupportPro\App\Services\CustomFieldsService::getFieldLabels($scope);
723
724 if (!$fields) {
725 return [];
726 }
727
728 $keys = array_keys($fields);
729
730 $customRows = Meta::where('object_type', 'ticket_meta')->where('object_id', $this->id)
731 ->whereIn('key', $keys)
732 ->get();
733
734 if (!$customRows) {
735 return [];
736 }
737
738 $formattedData = [];
739
740 $customRenderers = \FluentSupportPro\App\Services\CustomFieldsService::getCustomerRenderers();
741
742 foreach ($customRows as $row) {
743 $dataKey = $row->key;
744
745 $value = $row->value;
746
747 $fieldType = $fields[$dataKey]['type'];
748
749 if ($value) {
750 if (in_array($fieldType, $customRenderers) && $rendered) {
751 $value = apply_filters('fluent_support/custom_field_render_' . $fieldType, $value, $scope);
752 } else if ($fieldType == 'checkbox') {
753 $value = array_values(array_filter(explode('|', $value)));
754 }
755
756 if (!is_array($value) && !is_object($value)) {
757 $formattedData[$dataKey] = links_add_target(make_clickable($value));
758 } else {
759 $formattedData[$dataKey] = $value;
760 }
761 }
762 }
763
764 return $formattedData;
765 }
766
767 /**
768 * @param $data This is the data that will be saved to the ticket_meta for custom fields
769 * @return bool
770 */
771 public function syncCustomFields($data)
772 {
773 if (!is_array($data)) {
774 return false;
775 }
776
777 $fields = apply_filters('fluent_support/ticket_custom_fields', []);
778
779 if (!$fields) {
780 return false;
781 }
782
783 $keys = array_keys($fields);
784
785 $validData = Arr::only($data, $keys);
786
787 foreach ($validData as $dataKey => $validDatum) {
788 if (empty($validDatum)) {
789 Meta::where('object_type', 'ticket_meta')
790 ->where('object_id', $this->id)
791 ->where('key', $dataKey)
792 ->delete();
793 continue;
794 }
795
796 if ($fields[$dataKey]['type'] == 'checkbox' || is_array($validDatum)) {
797 $validDatum = implode('|', $validDatum);
798 $validDatum = '|' . $validDatum . '|';
799 }
800
801 $exist = Meta::where('object_type', 'ticket_meta')
802 ->where('object_id', $this->id)
803 ->where('key', $dataKey)
804 ->first();
805
806 if ($exist) {
807 $exist->value = $validDatum;
808 $exist->save();
809 } else {
810 Meta::insert([
811 'object_type' => 'ticket_meta',
812 'object_id' => $this->id,
813 'key' => $dataKey,
814 'value' => $validDatum
815 ]);
816 }
817 }
818
819 return true;
820 }
821
822 public function getLastAgentResponse()
823 {
824 $query = \FluentSupport\App\App::db()->table('fs_conversations')
825 ->select(['fs_conversations.*'])
826 ->where('fs_conversations.conversation_type', 'response')
827 ->where('fs_conversations.ticket_id', $this->id)
828 ->where('fs_persons.person_type', '=', 'agent')
829 ->join('fs_persons', 'fs_persons.id', '=', 'fs_conversations.person_id')
830 ->orderBy('fs_conversations.id', 'DESC');
831
832 return $query->first();
833 }
834
835 public function getLastResponse()
836 {
837 return \FluentSupport\App\App::db()->table('fs_conversations')
838 ->where('ticket_id', $this->id)
839 ->where('conversation_type', 'response')
840 ->latest('id')
841 ->first();
842 }
843
844 /**
845 * This method will assign tags to the ticket
846 * @param $tagIds This is the array of tag ids that will be assigned to the ticket
847 * @return \FluentSupport\App\Models\Ticket
848 */
849 public function applyTags($tagIds)
850 {
851 $result = false;
852
853 if (!is_array($tagIds)) {
854 $tagIds = array($tagIds);
855 }
856
857 foreach ($tagIds as $tagId) {
858 if (!$this->hasTag($tagId)) {
859 $this->tags()->attach($tagId, ['source_type' => 'ticket_tag']);
860 $result = true;
861
862 /*
863 * Action while tag added to ticket
864 *
865 * @since v1.0.0
866 * @param integer $tagId
867 * @param object $ticket
868 */
869 do_action('fluent_support/ticket_tag_added', $tagId, $this);
870 }
871 }
872 return $result;
873 }
874
875 /**
876 * This method will remove tags from ticket
877 * @param $tagIds This is the array of tag ids that will be removed from the ticket
878 * @return \FluentSupport\App\Models\Ticket
879 */
880 public function detachTags($tagIds)
881 {
882 $result = false;
883
884 if (!is_array($tagIds)) {
885 $tagIds = array($tagIds);
886 }
887
888 foreach ($tagIds as $tagId) {
889 if ($this->hasTag($tagId)) {
890 $this->tags()->detach($tagId);
891
892 /*
893 * Action while tag removed from ticket
894 *
895 * @since v1.0.0
896 * @param integer $tagId
897 * @param object $ticket
898 */
899 do_action('fluent_support/ticket_tag_removed', $tagId, $this);
900 $result = true;
901 }
902 }
903 return $result;
904 }
905
906 /**
907 * This `createTicket` method will create a new ticket and it will also create a new customer or
908 * a customer with WP profile if given.
909 * @param array $ticketData
910 * @param array $maybeNewCustomer
911 * @return $this | \WP_Error
912 */
913
914 public function createTicket($ticketData, $maybeNewCustomer = false)
915 {
916 $ticketData = $this->maybeCreateNewCustomer($ticketData, $maybeNewCustomer);
917
918 if (!$ticketData) {
919 return new \WP_Error('error', 'Ticket could not be created');
920 }
921
922 $customer = Customer::findOrFail($ticketData['customer_id']);
923 $ticketData = $this->buildTicketData($ticketData, $customer);
924 $disabledFields = apply_filters('fluent_support/disabled_ticket_fields', []);
925
926 return $this->storeTicket($ticketData, $customer, $disabledFields);
927 }
928
929 // This is a supporting method for createTicket method
930 // it will create ticket data array for ticket creation
931 private function buildTicketData($ticketData, $customer)
932 {
933 if (empty($ticketData['mailbox_id'])) {
934 $mailbox = Helper::getDefaultMailBox();
935 $ticketData['mailbox_id'] = $mailbox->id;
936 } else {
937 $mailbox = MailBox::findOrFail($ticketData['mailbox_id']); // just for validation
938 }
939
940 if (!empty($ticketData['product_id'])) {
941 $ticketData['product_source'] = 'local';
942 }
943
944 $ticketData['title'] = sanitize_text_field(wp_unslash($ticketData['title']));
945
946 $ticketData['content'] = wp_specialchars_decode(wp_unslash(wp_kses_post($ticketData['content'])));
947
948 if (!empty($ticketData['priority'])) {
949 $ticketData['priority'] = sanitize_text_field($ticketData['priority']);
950 }
951
952 if (!empty($ticketData['client_priority'])) {
953 $ticketData['client_priority'] = sanitize_text_field($ticketData['client_priority']);
954 }
955
956 /*
957 * Filter ticket data
958 *
959 * @since v1.0.0
960 * @param array $ticketData
961 * @param object $customer
962 */
963 $ticketData = apply_filters('fluent_support/create_ticket_data', $ticketData, $customer);
964
965 return $ticketData;
966 }
967
968 // This is a supporting method for createTicket method
969 // it will store the ticket and return the ticket object
970 private function storeTicket($ticketData, $customer, $disabledFields)
971 {
972 /*
973 * Action before ticket create
974 *
975 * @since v1.0.0
976 * @param array $ticketData
977 * @param object $customer
978 */
979 do_action('fluent_support/before_ticket_create', $ticketData, $customer);
980
981 $createdTicket = Ticket::create($ticketData);
982
983 TicketService::addTicketAttachments($ticketData, $disabledFields, $createdTicket, $customer);
984
985 if (defined('FLUENTSUPPORTPRO') && !empty($ticketData['custom_fields'])) {
986 $createdTicket->syncCustomFields($ticketData['custom_fields']);
987 }
988
989 /*
990 * Action on ticket create
991 *
992 * @since v1.0.0
993 * @param object $createdTicket
994 * @param object $customer
995 */
996 do_action('fluent_support/ticket_created', $createdTicket, $customer);
997 do_action('fluent_support/ticket_created_behalf_of_customer', $createdTicket, $customer, Helper::getAgentByUserId());
998 return $createdTicket;
999 }
1000
1001 // This is a supporting method for createTicket method
1002 // it will create a new customer or a customer with WP profile if given
1003 // and after creating a new user it will store customer_id
1004 // inside $ticketData array as we only need customer_id to create ticket
1005 private function maybeCreateNewCustomer($ticketData, $maybeNewCustomer)
1006 {
1007 if (!empty($ticketData['customer_id'])) {
1008 return $ticketData;
1009 }
1010
1011 $createdUserId = false;
1012
1013 if (Arr::get($ticketData, 'create_wp_user') == 'yes' && !empty($maybeNewCustomer['username'])) {
1014 // Check if username already in use, if not create a wp new user
1015 if (!username_exists($maybeNewCustomer['username'])) {
1016 $authController = new AuthController();
1017 $createdUserId = $authController->createUser($maybeNewCustomer);
1018 $authController->maybeUpdateUser($createdUserId, $maybeNewCustomer);
1019 }
1020 }
1021
1022 $email = Arr::get($maybeNewCustomer, 'email');
1023 if (!$email || !is_email($email)) {
1024 return false;
1025 }
1026
1027 $existingCustomer = Customer::where('email', $email)->first();
1028
1029 if ($existingCustomer) {
1030 $ticketData['customer_id'] = $existingCustomer->id;
1031 return $ticketData;
1032 }
1033
1034 // create the customer now
1035 $customerData = Arr::only($maybeNewCustomer, (new Customer())->getFillable());
1036 $customerData['user_id'] = $createdUserId;
1037
1038 $customerData = array_filter($customerData);
1039
1040 $createCustomer = Customer::create($customerData);
1041
1042 do_action('fluent_support/customer_created', $createCustomer);
1043
1044 if (!$createCustomer) {
1045 return false;
1046 }
1047
1048 $ticketData['customer_id'] = $createCustomer->id;
1049 return $ticketData;
1050 }
1051
1052
1053 /**
1054 * This `getTicket` will load a ticket with all its data
1055 * @param array $ticketWith
1056 * @param bool $withCrmData
1057 * @param int $ticketId
1058 * @return array
1059 * @throws Exception
1060 */
1061 public function getTicket($ticketWith, $withCrmData, $ticketId)
1062 {
1063 $agent = Helper::getAgentByUserId();
1064 $restrictedBusinessBoxes = PermissionManager::currentUserRestrictedBusinessBoxes();
1065
1066 $ticket = self::with($ticketWith)->findOrFail($ticketId);
1067
1068 if (in_array($ticket->mailbox_id, $restrictedBusinessBoxes)) {
1069 throw new \Exception('Ticket cannot be fetched due to restricted mailbox');
1070 }
1071
1072 $customFieldsKey = apply_filters('fluent_support/custom_registration_form_fields_key', Helper::getBusinessSettings('custom_registration_form_field'));
1073 $ticket->customer->custom_field_keys = $customFieldsKey;
1074
1075 if ($ticket->customer->user_id) {
1076 $customFieldKeysUsingHook = apply_filters('fluent_support/custom_registration_form_fields_key', []);
1077
1078 foreach ($customFieldKeysUsingHook as $key) {
1079 $userMeta = get_user_meta($ticket->customer->user_id,$key, true);
1080 if($userMeta) {
1081 $ticket->customer->$key = $userMeta;
1082 }
1083 }
1084 }
1085
1086 if (defined('FLUENTSUPPORTPRO_PLUGIN_VERSION') && Helper::isAgentFeedbackEnabled()) {
1087 foreach ($ticket->responses as $response) {
1088 $agentFeedback = Meta::where('object_id', $response->id)
1089 ->where('object_type', 'conversation_meta')
1090 ->where('key', 'agent_feedback_ratings')
1091 ->first();
1092
1093 if ($agentFeedback) {
1094 $response->agent_feedback = $agentFeedback->value;
1095 }
1096 }
1097 }
1098
1099 $ticket->customer->profile_edit_url = $this->getCustomerProfileUrl($ticket->customer); //Get and set customer profile url
1100 $this->checkAgentPermission($ticket); // Check Agent Permission
1101 $this->checkIfClosedTicket($ticket); // Check if ticket is closed, if closed then load ticket with closed data
1102
1103 return $this->getTicketAdditionalData($agent, $ticket->responses, $ticket, $withCrmData);
1104 }
1105
1106 // This checkIfClosedTicket method will validate if ticket is closed, if closed then load ticket with closed data
1107 private function checkIfClosedTicket($ticket)
1108 {
1109 if ($ticket->status == 'closed') {
1110 $ticket->load('closed_by_person');
1111 }
1112 }
1113
1114 // This getCustomerProfileUrl method will return customer profile url
1115 private function getCustomerProfileUrl($customer)
1116 {
1117 return $customer->getUserProfileEditUrl();
1118 }
1119
1120 // This getTicketAdditionalData method will load ticket with additional data
1121 private function getTicketAdditionalData($agent, $responses, $ticket, $isCrmProfileRequested = false)
1122 {
1123 foreach ($responses as $response) {
1124 $response->content = links_add_target(make_clickable(wpautop($response->content, false)));
1125 if (!empty($response->ccinfo)) {
1126 $val = Helper::safeUnserialize($response->ccinfo->value);
1127 if(isset($val['cc_email']) && !empty($val['cc_email'])){
1128 $response->cc_info = $val['cc_email'];
1129 } else {
1130 $response->cc_info = '';
1131 }
1132 } else {
1133 $response->cc_info = '';
1134 }
1135 }
1136
1137 $ticket->content = links_add_target(make_clickable(wpautop($ticket->content, false)));
1138
1139 //Get last activity by agent
1140 $ticket->live_activity = TicketHelper::getActivity($ticket->id, $agent->id);
1141 //Get all carbon copy customer
1142 $ccInfo = $ticket->getSettingsValue('cc_email', []);
1143
1144 $ticket->carbon_copy = !empty($ccInfo) ? implode(', ', $ccInfo) : '';
1145
1146 if (defined('FLUENTSUPPORTPRO')) {
1147 $ticket->custom_fields = $ticket->customData('admin', true);
1148 }
1149
1150 $data = [
1151 'ticket' => $ticket,
1152 'responses' => $responses,
1153 'agent_id' => $agent->id
1154 ];
1155
1156 if (defined('FLUENTSUPPORTPRO') && $ticket->watchers) {
1157 $data['watchers'] = TicketHelper::getWatchers($ticket->watchers);
1158 }
1159
1160 if (defined('FLUENTCRM') && $isCrmProfileRequested) {
1161 $data['fluentcrm_profile'] = Helper::getFluentCrmContactData($ticket->customer);
1162 }
1163
1164 return $data;
1165
1166 }
1167
1168
1169 /**
1170 * This `createResponse` will create a response for a ticket
1171 * @param array $data
1172 * @param int $ticketId
1173 * @return array
1174 * @throws Exception
1175 */
1176 public function createResponse($data, $ticketId)
1177 {
1178 $agent = Helper::getAgentByUserId(get_current_user_id());
1179 $this->checkIfValidAgent($agent);
1180
1181 $ticket = static::findOrFail($ticketId);
1182 $this->checkAgentPermission($ticket);
1183
1184 $responseData = (new ResponseService())->createResponse($data, $agent, $ticket);
1185
1186 $responseData['response']->content = wp_specialchars_decode(wpautop($responseData['response']->content, false));
1187
1188 return [
1189 'message' => __('Response has been added', 'fluent-support'),
1190 'response' => $responseData['response'],
1191 'ticket' => $responseData['ticket'],
1192 'update_data' => $responseData['update_data']
1193 ];
1194 }
1195
1196 public function addOrUpdatDraft($data, $ticketId)
1197 {
1198 $agent = Helper::getAgentByUserId(get_current_user_id());
1199 $this->checkIfValidAgent($agent);
1200
1201 $ticket = static::findOrFail($ticketId);
1202 $this->checkAgentPermission($ticket);
1203 $key = 'ticket_no_' . $ticketId . '_agent_id_' . $agent->id . '_response_draft';
1204
1205 $previousDraft = Meta::where('key', $key)->first();
1206
1207 if ($data['draftID'] || $previousDraft) {
1208 return $this->updateDraft($key, $data['draftID'], $data);
1209 }
1210
1211 $draftID = Meta::insertGetId([
1212 'object_type' => '_fs_auto_draft',
1213 'object_id' => $ticketId,
1214 'key' => $key,
1215 'value' => maybe_serialize($data)
1216 ]);
1217
1218 return [
1219 'message' => __('Draft has been added', 'fluent-support'),
1220 'draftID' => $draftID
1221 ];
1222
1223 }
1224
1225 public function updateDraft($key, $draftID, $data)
1226 {
1227 Meta::where('key', $key)->update([
1228 'value' => maybe_serialize($data)
1229 ]);
1230 return [
1231 'message' => __('Draft has been updated', 'fluent-support'),
1232 'draftID' => $draftID
1233 ];
1234 }
1235
1236 public function fetchDraft($ticketId)
1237 {
1238 $agent = Helper::getAgentByUserId(get_current_user_id());
1239 $this->checkIfValidAgent($agent);
1240
1241 $ticket = static::findOrFail($ticketId);
1242 $this->checkAgentPermission($ticket);
1243 $key = 'ticket_no_' . $ticketId . '_agent_id_' . $agent->id . '_response_draft';
1244
1245 $draft = Meta::where([
1246 'object_type' => '_fs_auto_draft',
1247 'key' => $key,
1248 ])->first();
1249
1250 if ($draft) {
1251 $draft->value = Helper::safeUnserialize($draft->value);
1252 }
1253
1254 return [
1255 'draft' => $draft
1256 ];
1257 }
1258
1259 public function removeDraft($draftID)
1260 {
1261 Meta::where('id', $draftID)->delete();
1262
1263 return [
1264 'message' => __('Discard draft successfully', 'fluent-support'),
1265 ];
1266 }
1267
1268 // This checkIfValidAgent method will check if agent is valid or not
1269 private function checkIfValidAgent($agent)
1270 {
1271 if (!$agent) {
1272 throw new \Exception('Sorry, You do not have permission. Please add yourself as support agent first');
1273 } else {
1274 return true;
1275 }
1276 }
1277
1278 /**
1279 * This `closeTicket` will close a ticket by ticket id
1280 * @param int $ticketId
1281 * @return array
1282 * @throws Exception
1283 */
1284 public function closeTicket($ticketId, $closeSilently = false)
1285 {
1286 $agent = Helper::getAgentByUserId(get_current_user_id());
1287
1288 $ticket = static::findOrFail($ticketId);
1289 $this->checkAgentPermission($ticket);
1290
1291 return [
1292 'message' => __('Ticket has been closed', 'fluent-support'),
1293 'ticket' => (new TicketService())->close($ticket, $agent, '', $closeSilently)
1294 ];
1295 }
1296
1297 /**
1298 * This `reopenTicket` will reopen a ticket by ticket id
1299 * @param int $ticketId
1300 * @return array
1301 * @throws Exception
1302 */
1303 public function reOpenTicket($ticketId)
1304 {
1305 $agent = Helper::getAgentByUserId(get_current_user_id());
1306
1307 $ticket = static::findOrFail($ticketId);
1308 $this->checkAgentPermission($ticket);
1309
1310
1311 return [
1312 'message' => __('Ticket has been opened again', 'fluent-support'),
1313 'ticket' => (new TicketService())->reopen($ticket, $agent)
1314 ];
1315 }
1316
1317
1318 /**
1319 * This `getTicketWidgets` will load all ticket widgets
1320 * @param int $ticketId
1321 * @return array
1322 * @throws Exception
1323 */
1324 public function getTicketWidgets($ticketId)
1325 {
1326 $ticket = static::with('customer')->findOrFail($ticketId);
1327 $this->checkAgentPermission($ticket);
1328
1329 //Get last 10 tickets of this customer except this
1330 /*
1331 * Filter ticket limit to show ticket in view ticket page sidebar
1332 * @since 1.5.6
1333 * @param int $limit
1334 */
1335 $limit = apply_filters('fluent_support/previous_ticket_widgets_limit', 10);
1336
1337 $otherTickets = static::where('id', '!=', $ticketId)
1338 ->select(['id', 'title', 'status', 'created_at'])
1339 ->where('customer_id', $ticket->customer_id)
1340 ->latest('id')
1341 ->limit($limit)
1342 ->get();
1343
1344 return [
1345 'other_tickets' => $otherTickets,
1346 'extra_widgets' => ProfileInfoService::getProfileExtraWidgets($ticket->customer)
1347 ];
1348 }
1349
1350 /**
1351 * This `updateTicketProperty` will update tickets properties
1352 * @param string $propName
1353 * @param mixed $propValue
1354 * @param int $ticketId
1355 * @return array
1356 * @throws Exception
1357 */
1358 public function updateTicketProperty($propName, $propValue, $ticketId)
1359 {
1360 $assigner = Helper::getAgentByUserId(get_current_user_id());
1361 $ticket = static::findOrFail($ticketId);
1362
1363 $this->checkAgentPermission($ticket);
1364 $propName = sanitize_text_field($propName);
1365 $message = sprintf(
1366 /* translators: %s: The name of the property that was updated */
1367 __('%s has been updated', 'fluent-support'),
1368 esc_html(str_replace('_', ' ', ucwords($propName)))
1369 );
1370 return [
1371 'message' => $message,
1372 'update_data' => $this->handlePropertyUpdate($propName, $propValue, $ticket, $assigner)
1373 ];
1374 }
1375
1376 // This will handle ticket property update
1377 private function handlePropertyUpdate($propName, $propValue, $ticket, $assigner)
1378 {
1379 $prevValue = $ticket->{$propName};
1380
1381 if ($propName === 'agent_id') {
1382 if (!PermissionManager::currentUserCan('fst_assign_agents')) {
1383 throw new \Exception('Permission denied to assign agent', 403);
1384 }
1385
1386 $agent = Agent::findOrFail($propValue);
1387 $restrictions = $agent->getMeta('agent_restrictions', []);
1388
1389 if (!empty($restrictions['restrictedBusinessBoxes'])) {
1390 $mailboxId = (int)$ticket->mailbox_id;
1391
1392 if (in_array($mailboxId, $restrictions['restrictedBusinessBoxes'], true)) {
1393 throw new \Exception('Agent is restricted for this mailbox ticket', 403);
1394 }
1395 }
1396 }
1397
1398 if ($propName && $propValue && $prevValue != $propValue) {
1399 $ticket->{$propName} = $propValue;
1400 $ticket->save();
1401 }
1402
1403 $updateData = [];
1404
1405 if ($propName == 'product_id') {
1406 $ticket->load('product');
1407 $updateData['product'] = $ticket->product;
1408 } else if ($propName == 'agent_id') {
1409 $ticket->load('agent');
1410 $updateData['agent'] = $ticket->agent;
1411 $updateData['assigner'] = (new TicketService())->onAgentChange($ticket, $assigner);
1412 if ($prevValue != $ticket->{$propName}) {
1413 do_action('fluent_support/agent_assigned_to_ticket', $ticket->agent, $ticket, $assigner);
1414 }
1415 }
1416
1417 return $updateData;
1418 }
1419
1420 /**
1421 * This `handleBulkActions` will handle bulk actions
1422 * @param string $action
1423 * @param array $ticketIds
1424 * @return array
1425 * @throws Exception
1426 */
1427 public function handleBulkActions($action, $ticketIds)
1428 {
1429 $hasAllPermission = PermissionManager::currentUserCan('fst_manage_other_tickets');
1430 $agent = Helper::getAgentByUserId();
1431 $query = Ticket::whereIn('id', $ticketIds);
1432
1433 if (!$hasAllPermission) {
1434 //Filter ticket by agent_id
1435 $query->where('agent_id', $agent->id);
1436 }
1437
1438 return $this->handleAction($action, $query, $agent);
1439 }
1440
1441 // This will handle ticket bulk action
1442 private function handleAction($action, $query, $agent)
1443 {
1444 if ($action == 'close_tickets') {
1445 return $this->bulkCloseTickets($query->get(), $agent);
1446 } else if ($action == 'delete_tickets') {
1447 return (new TicketService)->deleteTickets($query->get());
1448 } else if ($action == 'assign_agent') {
1449 return $this->bulkAssignAgent($query);
1450 } else if ($action == 'assign_tags') {
1451 return $this->bulkAssignTag($query->get());
1452 } else {
1453 throw new \Exception('Sorry no action found as available');
1454 }
1455 }
1456
1457 /**
1458 * This `bulkCloseTickets` will close all given or selected tickets
1459 * @param object $tickets
1460 * @param object $agent
1461 * @return array
1462 */
1463 public function bulkCloseTickets($tickets, $agent)
1464 {
1465 $tickets->each(function ($ticket) use ($agent) {
1466 (new TicketService())->close($ticket, $agent);
1467 });
1468
1469 // translators: %d represents the number of tickets that have been closed.
1470 return [
1471 'message' => sprintf(
1472 /* translators: %d represents the number of closed tickets. */
1473 __('%d tickets have been closed.', 'fluent-support'),
1474 count($tickets)
1475 )
1476 ];
1477 }
1478
1479 /**
1480 * This `bulkAssignAgent` will assign all given or selected tickets to given agent
1481 * @param object $tickets
1482 * @return array
1483 */
1484 public function bulkAssignAgent($query)
1485 {
1486 $request = \FluentSupport\App\App::getInstance('request');
1487
1488 if (!$request->has('agent_id')) {
1489 throw new \Exception('agent_id param is required');
1490 }
1491
1492 $agent = Agent::findOrFail($request->get('agent_id'));
1493
1494 $query->where(function ($q) use ($agent) {
1495 $q->where('agent_id', '!=', $agent->id)
1496 ->orWhereNull('agent_id');
1497 });
1498
1499 $tickets = $query->get();
1500
1501 $assignedCount = 0;
1502 $skippedCount = 0;
1503
1504 $tickets->each(function ($ticket) use ($agent, &$assignedCount, &$skippedCount) {
1505 $assigner = Helper::getCurrentAgent();
1506 $restrictions = $agent->getMeta('agent_restrictions', []);
1507
1508 // Skip ticket if mailbox is restricted for the agent
1509 if (!empty($restrictions) && in_array($ticket->mailbox_id, $restrictions['restrictedBusinessBoxes'])) {
1510 $skippedCount++;
1511 return;
1512 }
1513
1514 $ticket->agent_id = $agent->id;
1515 $ticket->save();
1516 $assignedCount++;
1517
1518 do_action('fluent_support/agent_assigned_to_ticket', $agent, $ticket, $assigner);
1519 });
1520
1521 // translators: %1$d is the number of assigned tickets, %2$s is the agent's full name.
1522 $assignedMessage = sprintf(
1523 /* translators: %1$d is the number of tickets assigned, %2$s is the agent's name. */
1524 __('%1$d tickets have been assigned to %2$s.', 'fluent-support'),
1525 $assignedCount,
1526 $agent->full_name
1527 );
1528
1529 // translators: %1$d is the number of skipped tickets.
1530 $skippedMessage = $skippedCount > 0
1531 ? sprintf(
1532 /* translators: %1$d is the number of skipped tickets due to mailbox restrictions. */
1533 __('%1$d tickets were skipped due to mailbox restrictions or already being assigned.', 'fluent-support'),
1534 $skippedCount
1535 )
1536 : "";
1537
1538 return [
1539 'message' => trim($assignedMessage . ' ' . $skippedMessage)
1540 ];
1541 }
1542
1543
1544
1545 /**
1546 * This `bulkAssignTag` will assign all given or selected tickets to given tag
1547 * @param object $tickets
1548 * @return array
1549 */
1550 public function bulkAssignTag($query)
1551 {
1552 $request = \FluentSupport\App\App::getInstance('request');
1553
1554 if (!$request->has('tag_ids')) {
1555 throw new \Exception('tag_ids param is required');
1556 }
1557
1558 $tags = array_filter(array_map('absint', $request->get('tag_ids', [])));
1559
1560 $query->each(function ($ticket) use ($tags) {
1561 $ticket->applyTags($tags);
1562 });
1563
1564 return [
1565 'message' => __('Selected tags has been added to tickets', 'fluent-support')
1566 ];
1567 }
1568
1569 // This checkAgentPermission method will validate if agent has permission to view ticket
1570 private function checkAgentPermission($ticket)
1571 {
1572 if (!PermissionManager::hasTicketPermission($ticket)) {
1573 throw new \Exception('Sorry, You do not have permission to this ticket');
1574 }
1575 }
1576
1577 public static function countTicketByMailBoxId($mailbox_id)
1578 {
1579 return self::where('mailbox_id', $mailbox_id)->count();
1580 }
1581
1582 public static function syncMailBoxId($mailbox_id, $fallback_id)
1583 {
1584 return self::where('mailbox_id', $mailbox_id)
1585 ->update([
1586 'mailbox_id' => $fallback_id
1587 ]);
1588 }
1589
1590 public static function getTicketsQuery()
1591 {
1592 return self::with([
1593 'customer' => function ($query) {
1594 $query->select(['first_name', 'last_name', 'email', 'id', 'avatar']);
1595 }, 'agent' => function ($query) {
1596 $query->select(['first_name', 'last_name', 'id']);
1597 },
1598 'product',
1599 'tags',
1600 'preview_response' => function ($query) {
1601 $query->latest('id');
1602 }
1603 ]);
1604 }
1605
1606 public function getSettingsValue($valueKey = false, $default = false)
1607 {
1608 $exist = Meta::where('object_type', 'ticket')
1609 ->where('key', 'settings')
1610 ->where('object_id', $this->id)
1611 ->first();
1612
1613 if ($exist) {
1614 $value = Helper::safeUnserialize($exist->value);
1615 if ($valueKey) {
1616 if (!is_array($value)) {
1617 return $default;
1618 }
1619 return Arr::get($value, $valueKey, $default);
1620 }
1621 return $value;
1622 }
1623
1624 return $default;
1625 }
1626
1627 public function updateSettingsValue($valueKey, $value)
1628 {
1629 $exist = Meta::where('object_type', 'ticket')
1630 ->where('key', 'settings')
1631 ->where('object_id', $this->id)
1632 ->first();
1633
1634 if ($exist) {
1635 $existingValue = Helper::safeUnserialize($exist->value);
1636
1637 if (!is_array($existingValue)) {
1638 $existingValue = [];
1639 }
1640
1641 $existingValue[$valueKey] = $value;
1642
1643 $exist->value = maybe_serialize($existingValue);
1644 $exist->save();
1645 return $this;
1646 }
1647
1648 $settings = [
1649 'object_type' => 'ticket',
1650 'key' => 'settings',
1651 'object_id' => $this->id,
1652 'value' => maybe_serialize([
1653 $valueKey => $value
1654 ])
1655 ];
1656
1657 Meta::create($settings);
1658
1659 return $this;
1660
1661 }
1662
1663 }
1664
1665