PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 1.4.7
Fluent Support – Helpdesk & Customer Support Ticket System v1.4.7
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.4.7, 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 // Delete Attachments
324 Attachment::where('ticket_id', $this->id)->delete();
325 Ticket::where('id', $this->id)->delete();
326 }
327
328 public static function slugify($title)
329 {
330 $slug = sanitize_title($title, 'support-ticket-' . time(), 'display');
331 if (Ticket::where('slug', $slug)->first()) {
332 $slug .= '-' . time();
333 }
334 return $slug;
335 }
336
337 public function hasTag($tagId)
338 {
339 $tags = $this->tags;
340 foreach ($tags as $tag) {
341 if ($tag->id == $tagId) {
342 return true;
343 }
344 }
345
346 return false;
347 }
348
349 public function attachments()
350 {
351 $class = __NAMESPACE__ . '\Attachment';
352 return $this->hasMany($class, 'ticket_id', 'id')->where('conversation_id', NULL);
353 }
354
355 public function customData($scope = 'admin', $rendered = false)
356 {
357 if(!defined('FLUENTSUPPORTPRO')) {
358 return [];
359 }
360
361 $fields = \FluentSupportPro\App\Services\CustomFieldsService::getFieldLabels($scope);
362
363 if (!$fields) {
364 return [];
365 }
366
367 $keys = array_keys($fields);
368
369 $customRows = Meta::where('object_type', 'ticket_meta')->where('object_id', $this->id)
370 ->whereIn('key', $keys)
371 ->get();
372
373 if (!$customRows) {
374 return [];
375 }
376
377 $formattedData = [];
378
379 $customRenderers = \FluentSupportPro\App\Services\CustomFieldsService::getCustomerRenderers();
380
381 foreach ($customRows as $row) {
382 $dataKey = $row->key;
383
384 $value = $row->value;
385
386 $fieldType = $fields[$dataKey]['type'];
387
388 if($value) {
389 if(in_array($fieldType, $customRenderers) && $rendered) {
390 $value = apply_filters('fluent_support/custom_field_render_'.$fieldType, $value, $scope);
391 } else if ($fieldType == 'checkbox') {
392 $value = array_values(array_filter(explode('|', $value)));
393 }
394 }
395
396 $formattedData[$dataKey] = $value;
397 }
398
399 return $formattedData;
400 }
401
402 public function syncCustomFields($data)
403 {
404 if (!is_array($data)) {
405 return false;
406 }
407
408 $fields = apply_filters('fluent_support/ticket_custom_fields', []);
409
410 if (!$fields) {
411 return false;
412 }
413
414 $keys = array_keys($fields);
415
416 $validData = Arr::only($data, $keys);
417
418 foreach ($validData as $dataKey => $validDatum) {
419 if ($fields[$dataKey]['type'] == 'checkbox' || is_array($validDatum)) {
420 $validDatum = implode('|', $validDatum);
421 $validDatum = '|' . $validDatum . '|';
422 }
423
424 $exist = Meta::where('object_type', 'ticket_meta')->where('object_id', $this->id)
425 ->where('key', $dataKey)
426 ->first();
427
428 if ($exist) {
429 $exist->value = $validDatum;
430 $exist->save();
431 } else {
432 Meta::insert([
433 'object_type' => 'ticket_meta',
434 'object_id' => $this->id,
435 'key' => $dataKey,
436 'value' => $validDatum
437 ]);
438 }
439 }
440
441 // maybe delete data
442 $deletedSlugs = array_diff($keys, array_keys($validData));
443
444 if ($deletedSlugs) {
445 Meta::where('object_type', 'ticket_meta')->where('object_id', $this->id)
446 ->whereIn('key', $deletedSlugs)
447 ->delete();
448 }
449
450 return true;
451 }
452
453 public function getLastAgentResponse()
454 {
455 return \FluentSupport\App\App::db()->table('fs_conversations')
456 ->select(['fs_conversations.*'])
457 ->where('fs_conversations.conversation_type', 'response')
458 ->where('fs_conversations.ticket_id', $this->id)
459 ->join('fs_persons', 'fs_persons.person_type', '=', 'agent')
460 ->orderBy('fs_conversations.id', 'DESC')
461 ->first();
462 }
463
464 public function getLastResponse()
465 {
466 return \FluentSupport\App\App::db()->table('fs_conversations')
467 ->where('ticket_id', $this->id)
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