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

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

855 lines 26.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\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 'content',
62 'title',
63 'slug',
64 'id'
65 ];
66
67 /**
68 * Local scope to filter tickets 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 (strpos($search, ':')) {
76 $array = explode(':', $search);
77 $column = $array[0];
78 $value = $array[1];
79 $columns = $this->fillable;
80 $columns[] = 'id';
81
82 if (in_array($column, $columns) && $value) {
83 if (is_numeric($value)) {
84 $query->where($column, $value);
85 } else {
86 $query->where($column, 'LIKE', "%$value%");
87 }
88 return $query;
89 }
90 }
91
92 $fields = $this->searchable;
93 $query->where(function ($query) use ($fields, $search) {
94 $query->where(array_shift($fields), 'LIKE', "%$search%");
95 foreach ($fields as $field) {
96 $query->orWhere($field, 'LIKE', "%$search%");
97 }
98 });
99
100 return $query;
101 }
102
103 /**
104 * Local scope to filter tickets by different filtering condition
105 * @param ModelQueryBuilder $query
106 * @param mixed $search
107 * @return ModelQueryBuilder
108 */
109
110 public function doSearchForAdvancedFilter($query, $search)
111 {
112 foreach ($search as $s) {
113 $operator = $s['operator'];
114 //If selected item for ticket either title or content
115 if(in_array($s['property'], ['title','content' ])){
116 //If the selected condition is contains, query operator id LIKE
117 if ($operator == 'contains') {
118 $query = $query->where(function ($query) use ($s) {
119 $query->where($s['property'], 'LIKE', "%".$s['value']."%");
120 });
121 } elseif ($operator == 'not_contains') {
122 //If the selected condition is not_contains, query operator id NOT LIKE
123 $query = $query->where(function ($query) use ($s){
124 $query->where($s['property'], 'NOT LIKE', '%'.$s['value'].'%');
125 });
126 }
127 }
128
129 //If selected item is Ticket Conversation Content
130 if($s['property'] == 'conversation_content'){
131 $operator = $s['operator'];
132 if($operator == 'contains') {
133 $query = $query->whereHas('responses', function ($q) use ($s) {
134 $q->where('content', 'LIKE', "%".$s['value']."%");
135 });
136
137 } else if ($operator == 'not_contains') {
138 $query = $query->whereHas('responses', function ($q) use ($s) {
139 $q->where('content', 'NOT LIKE', "%".$s['value']."%");
140 });
141 }
142 }
143
144 //If selected item is Ticket created or Last Response or Customer Waiting For, or Last Agent Response or Last Customer Response
145 if(in_array($s['property'], ['created_at', 'updated_at', 'waiting_since', 'last_agent_response', 'last_customer_response'])){
146 $query = (new \FluentSupport\App\Models\Ticket())->buildDateBaseFilterQuery($query, $s);
147 }
148
149 //If selected item is Ticket Status or Client Priority or Agent Priority or Tags or Product or Waiting For Reply
150 if(in_array($s['property'], ['status', 'client_priority', 'priority', 'tags', 'product', 'waiting_for_reply', 'agent_id', 'mailbox_id'])){
151 $query = (new \FluentSupport\App\Models\Ticket())->buildPropertiesFilterQuery($query, $s);
152 }
153 }
154 return $query;
155 }
156
157 /**
158 * Local scope to filter subscribers by search/query string
159 * @param ModelQueryBuilder $query
160 * @param array $statuses
161 * @return ModelQueryBuilder
162 */
163 public function scopeFilterByStatues($query, $statuses)
164 {
165 if ($statuses) {
166 $query->whereIn('status', $statuses);
167 }
168
169 return $query;
170 }
171
172 /**
173 * Local scope to filter tickets by not response by agent
174 * @param $query
175 * @return mixed
176 */
177 public function scopeWaitingOnly($query)
178 {
179 $query->where(function ($q){
180 $q->whereColumn('last_agent_response', '<' ,'last_customer_response')
181 ->orWhereNull('last_agent_response')
182 ->orWhere('status', 'new');
183 });
184 return $query;
185 }
186
187 /**
188 * scopeApplyFilters method will filet ticket based on the selected filters
189 * This method will get filter option as parameter, loop through and apply conditions in query
190 * @param $query
191 * @param $filters
192 * @return ModelQueryBuilder
193 */
194 public function scopeApplyFilters($query, $filters)
195 {
196 $supportedColumns = ['product_id', 'client_priority', 'priority', 'mailbox_id'];
197 foreach ($filters as $filterKey => $filterValue) {
198 if (!$filterValue && ($filterValue !== '0' || $filterValue !== 0)) {
199 continue;
200 }
201 //If filer using status
202 if ($filterKey == 'status_type') {
203 //Get list of ticket status
204 $statusArray = Helper::getTkStatusesByGroupName($filterValue);
205 if ($statusArray) {
206 //Apply filet where status in
207 $query->whereIn('status', $statusArray);
208 }
209 } else if (in_array($filterKey, $supportedColumns)) {
210 $query->where($filterKey, $filterValue);
211 } else if ($filterKey == 'waiting_for_reply') {
212 if ($filterValue != 'yes') {
213 continue;
214 }
215 //Apply filter where no response by agent
216 $query = $this->scopeWaitingOnly($query);
217 } else if ($filterKey == 'agent_id') {
218 //Apply filter where ticket is not assigned
219 if ($filterValue == 'unassigned') {
220 $query->whereNull($filterKey);
221 } else {
222 //Apply filter, get only assigned ticket
223 $query->where($filterKey, $filterValue);
224 }
225 } else if ($filterKey == 'ticket_tags') {
226 if (!$filterValue) {
227 continue;
228 }
229 //Apply filter where ticket only has this tag id
230 $query->whereHas('tags', function ($q) use ($filterValue) {
231 $q->whereIn('tag_id', $filterValue);
232 });
233 }
234 }
235
236 return $query;
237 }
238
239 /**
240 * Local scope to filter tickets by agent id
241 * @param ModelQueryBuilder $query
242 * @param int $agentId
243 * @return ModelQueryBuilder
244 */
245 public function scopeFilterByAgentId($query, $agentId)
246 {
247 if ($agentId) {
248 $query->where('agent_id', $agentId);
249 }
250
251 return $query;
252 }
253
254 /**
255 * Local scope to filter subscribers by search/query string
256 * @param ModelQueryBuilder $query
257 * @param int $customerId
258 * @return ModelQueryBuilder
259 */
260 public function scopeFilterByCustomerId($query, $customerId)
261 {
262 $query->where('customer_id', $customerId);
263
264 return $query;
265 }
266
267 /**
268 * Local scope to filter subscribers by search/query string
269 * @param ModelQueryBuilder $query
270 * @param int $productId
271 * @return ModelQueryBuilder
272 */
273 public function scopeFilterByProductId($query, $productId)
274 {
275 if ($productId) {
276 $query->where('product_id', $productId);
277 }
278
279 return $query;
280 }
281
282 /**
283 * Local scope to filter subscribers by search/query string
284 * @param ModelQueryBuilder $query
285 * @param array $priorities
286 * @return ModelQueryBuilder
287 */
288 public function scopeFilterByPriorities($query, $priorities)
289 {
290 if ($priorities) {
291 $query->whereIn('priority', $priorities);
292 }
293
294 return $query;
295 }
296
297 /**
298 * @param $filter
299 * @return string[]
300 */
301
302 public static function parseRelationalFilterQueryMethods($filter)
303 {
304 // default operator = in
305 $method = 'whereHas';
306 $subMethod = 'whereIn';
307
308 switch ($filter['operator']) {
309 case 'not_in':
310 $method = 'whereDoesntHave';
311 $subMethod = 'whereIn';
312
313 break;
314 case 'in_all':
315 $method = 'whereHas';
316 $subMethod = 'where';
317
318 break;
319 case 'not_in_all':
320 $method = 'whereDoesntHave';
321 $subMethod = 'where';
322
323 break;
324 }
325
326 return [$method, $subMethod];
327 }
328
329 /**
330 * Parse filter to set proper operator and value for the filter query.
331 *
332 * @param array $filter
333 * @return array
334 */
335 public static function filterParser($filter)
336 {
337 switch ($filter['operator']) {
338 case 'before':
339 $filter['operator'] = '<';
340 $filter['value'] = $filter['value'] . ' 23:59:59';
341 break;
342
343 case 'after':
344 $filter['operator'] = '>';
345 $filter['value'] = $filter['value'] . ' 23:59:59';
346 break;
347
348 case 'date_equal':
349 $filter['operator'] = 'LIKE';
350 $filter['value'] = '%' . $filter['value'] . '%';
351 break;
352
353 case 'days_before':
354 $filter['operator'] = '<';
355 $filter['value'] = date('Y-m-d', current_time('timestamp') - $filter['value'] * 24 * 60 * 60);
356 break;
357
358 case 'days_within':
359 $filter['operator'] = 'BETWEEN';
360 $filter['value'] = [
361 date('Y-m-d', current_time('timestamp') - $filter['value'] * 24 * 60 * 60),
362 date('Y-m-d') . ' 23:59:59'
363 ];
364 break;
365 case 'date_range':
366 $filter['operator'] = 'BETWEEN';
367 if(isset($filter['value'][0]))
368 $filter['value'][0] .= ' 00:00:00';
369 if(isset($filter['value'][1]))
370 $filter['value'][1] .= ' 23:59:59';
371 break;
372 }
373
374 return $filter;
375 }
376
377 /**
378 * @param \FluentSupport\Framework\Database\Orm\Builder|\FluentSupport\Framework\Database\Query\Builder $query
379 * @param array $filters
380 * @return ModelQueryBuilder
381 */
382 public function buildDateBaseFilterQuery($query, $filters)
383 {
384 $filter = static::filterParser($filters);
385 $query->where(function ($dateQuery) use ($filter) {
386
387 if ($filter['operator'] == 'BETWEEN') {
388 $dateQuery->whereBetween($filter['property'], $filter['value']);
389 } else {
390 $dateQuery->where($filter['property'], $filter['operator'], $filter['value']);
391 }
392 });
393
394 return $query;
395 }
396
397 /**
398 * Relation builder
399 * @param $relation
400 * @param $query
401 * @param $method
402 * @param $subMethod
403 * @param $subField
404 * @param $filter
405 * @param false $provider
406 * @return ModelQueryBuilder
407 */
408
409 public static function buildRelationFilterQuery($relation, $query, $method, $subMethod, $subField, $filter, $provider = false)
410 {
411 if (in_array($filter['operator'], ['in_all', 'not_in_all']) && $filter['value']) {
412 foreach ($filter['value'] as $item) {
413 $query = static::buildRelationFilterQuery($relation, $query, $method, $subMethod, $subField, ['value' => $item, 'operator' => ''], $provider);
414 }
415 } else {
416 $query = $query->{$method}($relation, function ($relationQuery) use ($subMethod, $subField, $filter, $provider) {
417 $relationQuery = $relationQuery->{$subMethod}($subField, $filter['value']);
418
419 if ($provider) {
420 $relationQuery = $relationQuery->where('provider', $provider);
421 }
422
423 return $relationQuery;
424 });
425 }
426
427 return $query;
428 }
429
430 /**
431 * get tickets by advanced filter segment data
432 * @param $query
433 * @param $filter
434 * @return ModelQueryBuilder
435 */
436
437 public function buildPropertiesFilterQuery($query, $filter)
438 {
439 if (in_array($filter['property'], ['tags', 'product'])) {
440 $subField = $filter['property'] == 'tags' ? 'tag_id' : 'product_id';
441 list($method, $subMethod) = static::parseRelationalFilterQueryMethods($filter);
442 $query = static::buildRelationFilterQuery($filter['property'], $query, $method, $subMethod, $subField, $filter);
443 }
444
445 elseif ($filter['property'] == 'waiting_for_reply'){
446 if (($filter['value'] == 'yes' && $filter['operator'] == '=') || ($filter['value'] == 'no' && $filter['operator'] == '!=')){
447 $query = $query->where(function ($q){
448 $q->whereColumn('last_agent_response', '<' ,'last_customer_response')
449 ->orWhereNull('last_agent_response')
450 ->orWhere('status', 'new');
451 });
452 } else {
453 $query = $query->where(function ($q) {
454 $q->whereColumn('last_customer_response', '<' ,'last_agent_response');
455 });
456 }
457 }
458
459 else {
460 $method = $filter['operator'] == 'in' ? 'whereIn' : 'whereNotIn';
461 $query = $query->{$method}($filter['property'], (array) $filter['value']);
462 }
463 return $query;
464 }
465
466 /**
467 * method to search by properties
468 * @param $provider
469 * @param $query
470 * @param $search
471 * @param string $operator
472 * @return ModelQueryBuilder
473 */
474 public function buildSearchableQuery($provider, $query, $search, $operator = 'LIKE')
475 {
476 $fields = $this->searchable;
477
478 $query->whereHas($provider, function ($query) use ($fields, $search, $operator) {
479 $query->where(array_shift($fields), $operator, $search);
480
481 $nameArray = explode(' ', $search);
482
483 if (count($nameArray) >= 2) {
484 $query->orWhere(function ($q) use ($nameArray, $operator) {
485 $firstName = array_shift($nameArray);
486 $lastName = implode(' ', $nameArray);
487
488 $q->where('first_name', $operator, $firstName);
489 $q->where('last_name', $operator, $lastName);
490 });
491 }
492
493 foreach ($fields as $field) {
494 $query->orWhere($field, $operator, $search);
495 }
496 });
497
498 return $query;
499 }
500
501 /**
502 * Filter by ticket general properties like customer name, agent name etc
503 * @param $provider
504 * @param $query
505 * @param $filters
506 * @return ModelQueryBuilder
507 */
508 public function filterTicketByUser($provider, $query, $filters)
509 {
510 foreach ($filters as $index=>$filter) {
511 if ($filter['operator']=='in' || $filter['operator']=='not_in') {
512 $method = $filter['operator'] == 'in' ? 'whereIn' : 'whereNotIn';
513 $query = $query->whereHas($provider, function ($q) use ($method, $filter) {
514 $q->{$method}($filter['property'], $filter['value']);
515 });
516 }
517 elseif ($filter['operator'] == 'contains') {
518 $operator = 'LIKE';
519 $searchTerm = '%' . $filter['value'] . '%';
520 $query = $this->buildSearchableQuery($provider, $query, $searchTerm, $operator);
521 } elseif ($filter['operator'] == 'not_contains') {
522 $operator = 'NOT LIKE';
523 $searchTerm = '%' . $filter['value'] . '%';
524 $query = $this->buildSearchableQuery($provider, $query, $searchTerm, $operator);
525 }
526
527 if ($filter['operator'] == '=') {
528 $query->whereHas($provider, function ($q) use ($index, $filter) {
529 if ($index == 0){
530 $q->where($filter['property'], '=', $filter['value']);
531 }else{
532 $q->orWhere($filter['property'], '=', $filter['value']);
533 }
534 });
535 } elseif ($filter['operator'] == '!=') {
536 $query->whereHas($provider, function ($q) use ($index, $filter) {
537 if ($index == 0){
538 $q->where($filter['property'], '!=', $filter['value']);
539 }else{
540 $q->orWhere($filter['property'], '!=', $filter['value']);
541 }
542 });
543
544 }
545 return $query;
546 }
547 }
548 /**
549 * One2Many: Customer has to many Click Tickets
550 * @return Model Collection
551 */
552 public function responses()
553 {
554 $class = __NAMESPACE__ . '\Conversation';
555
556 return $this->hasMany(
557 $class, 'ticket_id', 'id'
558 );
559 }
560
561 public function preview_response()
562 {
563 $class = __NAMESPACE__ . '\Conversation';
564
565 return $this->hasOne(
566 $class, 'ticket_id', 'id'
567 );
568 }
569
570 public function tags()
571 {
572 $class = __NAMESPACE__ . '\TicketTag';
573
574 return $this->belongsToMany(
575 $class, 'fs_tag_pivot', 'source_id', 'tag_id'
576 )->wherePivot('source_type', 'ticket_tag');
577 }
578
579
580 /**
581 * One2one: Customer has to many Click Tickets
582 * @return Model Collection
583 */
584 public function customer()
585 {
586 $class = __NAMESPACE__ . '\Customer';
587
588 return $this->belongsTo(
589 $class, 'customer_id', 'id'
590 );
591 }
592
593 /**
594 * One2one: Customer has to many Click Tickets
595 * @return Model Collection
596 */
597 public function agent()
598 {
599 $class = __NAMESPACE__ . '\Agent';
600
601 return $this->belongsTo(
602 $class, 'agent_id', 'id'
603 );
604 }
605
606 public function closed_by_person()
607 {
608 $class = __NAMESPACE__ . '\Person';
609
610 return $this->belongsTo(
611 $class, 'closed_by', 'id'
612 );
613 }
614
615 public function product()
616 {
617 $class = __NAMESPACE__ . '\Product';
618
619 return $this->belongsTo(
620 $class, 'product_id', 'id'
621 );
622 }
623
624 public function mailbox()
625 {
626 $class = __NAMESPACE__ . '\MailBox';
627
628 return $this->belongsTo(
629 $class, 'mailbox_id', 'id'
630 );
631 }
632
633
634 public function deleteTicket()
635 {
636 /*
637 * Action on ticket deleting
638 *
639 * @since v1.0.0
640 * @param object $ticket
641 */
642 do_action('fluent_support/deleting_ticket', $this);
643 // delete the responses first
644 Conversation::where('ticket_id', $this->id)->delete();
645 // Delete the ticket meta
646 Meta::where('object_type', 'ticket_meta')->where('object_id', $this->id)->delete();
647 // Delete attachments related to this ticket
648 Attachment::where('ticket_id', $this->id)->delete();
649 // Delete the ticket
650 Ticket::where('id', $this->id)->delete();
651 }
652
653 public static function slugify($title)
654 {
655 $slug = sanitize_title($title, 'support-ticket-' . time(), 'display');
656 if (Ticket::where('slug', $slug)->first()) {
657 $slug .= '-' . time();
658 }
659 return $slug;
660 }
661
662 public function hasTag($tagId)
663 {
664 $tags = $this->tags;
665 foreach ($tags as $tag) {
666 if ($tag->id == $tagId) {
667 return true;
668 }
669 }
670
671 return false;
672 }
673
674 public function attachments()
675 {
676 $class = __NAMESPACE__ . '\Attachment';
677 return $this->hasMany($class, 'ticket_id', 'id')->where('conversation_id', NULL);
678 }
679
680 public function customData($scope = 'admin', $rendered = false)
681 {
682 if (!defined('FLUENTSUPPORTPRO')) {
683 return [];
684 }
685
686 $fields = \FluentSupportPro\App\Services\CustomFieldsService::getFieldLabels($scope);
687
688 if (!$fields) {
689 return [];
690 }
691
692 $keys = array_keys($fields);
693
694 $customRows = Meta::where('object_type', 'ticket_meta')->where('object_id', $this->id)
695 ->whereIn('key', $keys)
696 ->get();
697
698 if (!$customRows) {
699 return [];
700 }
701
702 $formattedData = [];
703
704 $customRenderers = \FluentSupportPro\App\Services\CustomFieldsService::getCustomerRenderers();
705
706 foreach ($customRows as $row) {
707 $dataKey = $row->key;
708
709 $value = $row->value;
710
711 $fieldType = $fields[$dataKey]['type'];
712
713 if ($value) {
714 if (in_array($fieldType, $customRenderers) && $rendered) {
715 $value = apply_filters('fluent_support/custom_field_render_' . $fieldType, $value, $scope);
716 } else if ($fieldType == 'checkbox') {
717 $value = array_values(array_filter(explode('|', $value)));
718 }
719 }
720
721 $formattedData[$dataKey] = $value;
722 }
723
724 return $formattedData;
725 }
726
727 /**
728 * @param $data This is the data that will be saved to the ticket_meta for custom fields
729 * @return bool
730 */
731 public function syncCustomFields($data)
732 {
733 if (!is_array($data)) {
734 return false;
735 }
736
737 $fields = apply_filters('fluent_support/ticket_custom_fields', []);
738
739 if (!$fields) {
740 return false;
741 }
742
743 $keys = array_keys($fields);
744
745 $validData = Arr::only($data, $keys);
746
747 foreach ($validData as $dataKey => $validDatum) {
748 if ($fields[$dataKey]['type'] == 'checkbox' || is_array($validDatum)) {
749 $validDatum = implode('|', $validDatum);
750 $validDatum = '|' . $validDatum . '|';
751 }
752
753 $exist = Meta::where('object_type', 'ticket_meta')->where('object_id', $this->id)
754 ->where('key', $dataKey)
755 ->first();
756
757 if ($exist) {
758 $exist->value = $validDatum;
759 $exist->save();
760 } else {
761 Meta::insert([
762 'object_type' => 'ticket_meta',
763 'object_id' => $this->id,
764 'key' => $dataKey,
765 'value' => $validDatum
766 ]);
767 }
768 }
769
770 // maybe delete data
771 $deletedSlugs = array_diff($keys, array_keys($validData));
772
773 if ($deletedSlugs) {
774 Meta::where('object_type', 'ticket_meta')->where('object_id', $this->id)
775 ->whereIn('key', $deletedSlugs)
776 ->delete();
777 }
778
779 return true;
780 }
781
782 public function getLastAgentResponse()
783 {
784 $query = \FluentSupport\App\App::db()->table('fs_conversations')
785 ->select(['fs_conversations.*'])
786 ->where('fs_conversations.conversation_type', 'response')
787 ->where('fs_conversations.ticket_id', $this->id)
788 ->where('fs_persons.person_type', '=', 'agent')
789 ->join('fs_persons', 'fs_persons.id', '=', 'fs_conversations.person_id')
790 ->orderBy('fs_conversations.id', 'DESC');
791
792 return $query->first();
793 }
794
795 public function getLastResponse()
796 {
797 return \FluentSupport\App\App::db()->table('fs_conversations')
798 ->where('ticket_id', $this->id)
799 ->where('conversation_type', 'response')
800 ->orderBy('id', 'DESC')
801 ->first();
802 }
803
804 /**
805 * This method will assign tags to the ticket
806 * @param $tagIds This is the array of tag ids that will be assigned to the ticket
807 * @return \FluentSupport\App\Models\Ticket
808 */
809 public function applyTags($tagIds)
810 {
811 $result = false;
812 foreach ($tagIds as $tagId) {
813 if (!$this->hasTag($tagId)) {
814 $this->tags()->attach($tagId, ['source_type' => 'ticket_tag']);
815 $result = true;
816
817 /*
818 * Action while tag added to ticket
819 *
820 * @since v1.0.0
821 * @param integer $tagId
822 * @param object $ticket
823 */
824 do_action('fluent_support/ticket_tag_added', $tagId, $this);
825 }
826 }
827 return $result;
828 }
829 /**
830 * This method will remove tags from ticket
831 * @param $tagIds This is the array of tag ids that will be removed from the ticket
832 * @return \FluentSupport\App\Models\Ticket
833 */
834 public function detachTags($tagIds)
835 {
836 $result = false;
837 foreach ($tagIds as $tagId) {
838 if ($this->hasTag($tagId)) {
839 $this->tags()->detach($tagId);
840
841 /*
842 * Action while tag removed from ticket
843 *
844 * @since v1.0.0
845 * @param integer $tagId
846 * @param object $ticket
847 */
848 do_action('fluent_support/ticket_tag_removed', $tagId, $this);
849 $result = true;
850 }
851 }
852 return $result;
853 }
854 }
855