PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.3.1
Fluent Support – Helpdesk & Customer Support Ticket System v2.3.1
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 / Services / Tickets / TicketService.php

TicketService.php in Fluent Support – Helpdesk & Customer Support Ticket System 2.3.1, at app/Services/Tickets/TicketService.php

274 lines 10.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\Services\Tickets;
4
5 use FluentSupport\App\Models\Attachment;
6 use FluentSupport\App\Models\Conversation;
7 use FluentSupport\App\Models\MailBox;
8 use FluentSupport\App\Models\Ticket;
9 use FluentSupport\App\Services\Helper;
10 use FluentSupport\App\Services\Includes\UploadService;
11 use FluentSupport\Framework\Support\Arr;
12
13 class TicketService
14 {
15 /**
16 * this function will set the ticket status as closed
17 * @param $ticket
18 * @param $person
19 * @param string $internalNote
20 * @param bool $silently
21 * @return mixed
22 */
23
24 public function close($ticket, $person, $internalNote = '', $silently = 'no')
25 {
26 if ($ticket->status != 'closed') {
27 $ticket->status = 'closed';
28 $ticket->resolved_at = current_time('mysql');
29 $ticket->closed_by = $person->id;
30 $ticket->total_close_time = current_time('timestamp') - strtotime($ticket->created_at);
31 $ticket->save();
32
33 if ('no' == $silently) {
34 do_action('fluent_support/ticket_closed', $ticket, $person);
35 do_action('fluent_support/ticket_closed_by_' . $person->person_type, $ticket, $person);
36 }
37
38 if (!$internalNote) {
39 $internalNote = __('Ticket has been closed', 'fluent-support');
40 }
41
42 //Keep track in conversation
43 Conversation::create([
44 'ticket_id' => $ticket->id,
45 'person_id' => $person->id,
46 'conversation_type' => 'internal_info',
47 'content' => $internalNote
48 ]);
49 }
50
51 return $ticket;
52 }
53
54 public function reopen($ticket, $person)
55 {
56 if ($ticket->status == 'closed') {
57 $ticket->status = 'active';
58 $ticket->waiting_since = current_time('mysql');
59 $ticket->resolved_at = null;
60 $ticket->save();
61
62 /*
63 * Action on ticket reopen
64 *
65 * @since v1.0.0
66 * @param object $ticket
67 * @param object $person
68 */
69 do_action('fluent_support/ticket_reopen', $ticket, $person);
70 do_action('fluent_support/ticket_reopen_by_' . $person->person_type, $ticket, $person);
71 Conversation::create([
72 'ticket_id' => $ticket->id,
73 'person_id' => $person->id,
74 'conversation_type' => 'internal_info',
75 'content' => __('Ticket has been reopened', 'fluent-support')
76 ]);
77 }
78
79 return $ticket;
80 }
81
82 public function onAgentChange($ticket, $person)
83 {
84 do_action('fluent_support/ticket_agent_change', $ticket, $person);
85 Conversation::create([
86 'ticket_id' => $ticket->id,
87 'person_id' => $person->id,
88 'conversation_type' => 'internal_info',
89 'content' => $ticket->agent->user_id !== $person->user_id ?
90 $person->full_name . __(' assigned ', 'fluent-support') . $ticket->agent->full_name . __(' in this ticket', 'fluent-support') :
91 $person->full_name .__( ' assign this ticket to self', 'fluent-support')
92 ]);
93
94 return $person;
95 }
96
97 /**
98 * This `addTicketAttachments` method is responsible for adding attachments to ticket
99 * @param array $data
100 * @param array $disabledFields
101 * @param \FluentSupport\App\Models\Ticket $ticket
102 * @param object $customer
103 * @return \FluentSupport\App\Models\Ticket $ticket
104 * @since 1.5.7
105 */
106 public static function addTicketAttachments($data, $disabledFields, $ticket, $customer)
107 {
108 Helper::tempImageMoveUploadDir($ticket->id, 'ticket-create', null, $customer->id);
109
110 if (($attachmentsHashes = Arr::get($data, 'attachments')) && !in_array('file_upload', $disabledFields)) {
111 $attachments = Attachment::whereIn('file_hash', $attachmentsHashes)
112 ->where('status', 'in-active')
113 ->get();
114
115 if ($attachments->isEmpty()) {
116 return $ticket;
117 }
118
119 $storageDriver = Helper::getUploadDriverKey();
120
121 if ($storageDriver != 'local') {
122 foreach ($attachments as $attachment) {
123 do_action_ref_array('fluent_support/finalize_file_upload_' . $storageDriver, [&$attachment, $ticket->id]);
124 }
125 }
126
127 foreach ($attachments as $attachment) {
128 if ($attachment->driver == 'local') {
129 $newFileInfo = UploadService::copyFileTicketFolder($attachment->file_path, $ticket->id);
130 if ($newFileInfo) {
131 $attachment->file_path = $newFileInfo['file_path'];
132 $attachment->full_url = $newFileInfo['url'];
133 }
134 }
135
136 $attachment->ticket_id = $ticket->id;
137 $attachment->person_id = $customer->id;
138 $attachment->status = 'active';
139 $attachment->save();
140 }
141
142 $ticket->load('attachments');
143 }
144
145 return $ticket;
146 }
147
148 /**
149 * Sanitize ticket data, create the ticket, handle attachments & custom fields, fire hooks.
150 * Shared by TicketController::createTicket and ProTicketService::handleSplitTicket.
151 *
152 * @param array $ticketData
153 * @param \FluentSupport\App\Models\Customer $customer
154 * @return \FluentSupport\App\Models\Ticket
155 */
156 public function storeTicket($ticketData, $customer)
157 {
158 if (empty($ticketData['mailbox_id'])) {
159 $mailbox = Helper::getDefaultMailBox();
160 if ($mailbox) {
161 $ticketData['mailbox_id'] = $mailbox->id;
162 }
163 } else {
164 $mailbox = MailBox::findOrFail($ticketData['mailbox_id']);
165 }
166
167 if (!empty($ticketData['product_id'])) {
168 $ticketData['product_source'] = 'local';
169 }
170
171 $ticketData['title'] = sanitize_text_field(wp_unslash($ticketData['title']));
172 $ticketData['content'] = wp_specialchars_decode(wp_unslash(wp_kses_post($ticketData['content'])));
173
174 if (!empty($ticketData['priority'])) {
175 $ticketData['priority'] = sanitize_text_field($ticketData['priority']);
176 }
177
178 if (!empty($ticketData['client_priority'])) {
179 $ticketData['client_priority'] = sanitize_text_field($ticketData['client_priority']);
180 }
181
182 $ticketData = apply_filters('fluent_support/create_ticket_data', $ticketData, $customer);
183
184 do_action('fluent_support/before_ticket_create', $ticketData, $customer);
185
186 $createdTicket = Ticket::create($ticketData);
187
188 $disabledFields = apply_filters('fluent_support/disabled_ticket_fields', []);
189 $disabledFields = array_diff($disabledFields, ['file_upload']);
190 self::addTicketAttachments($ticketData, $disabledFields, $createdTicket, $customer);
191
192 if (defined('FLUENTSUPPORTPRO') && !empty($ticketData['custom_fields'])) {
193 $createdTicket->syncCustomFields($ticketData['custom_fields']);
194 }
195
196 $agent = Helper::getAgentByUserId();
197 if ($agent) {
198 $isAgentInitiated = Arr::get($ticketData, 'agent_initiated') === 'yes';
199 $createdTicket->created_by = $agent->id;
200
201 if ($isAgentInitiated) {
202 // Skip all ticket creation emails for agent-initiated tickets
203 add_filter('fluent_support/should_send_notification', function ($shouldSend, $channel, $type) {
204 if ($channel === 'email' && in_array($type, ['ticket_created_email_to_customer', 'ticket_created_email_to_admin', 'ticket_created_by_agent_email_to_customer'])) {
205 return false;
206 }
207 return $shouldSend;
208 }, 10, 3);
209
210 $initializedMessage = $agent->full_name . __(' initialized this ticket', 'fluent-support');
211
212 $responseContent = apply_filters('fluent_support/parse_smartcode_data', $createdTicket->content, [
213 'customer' => $customer,
214 'agent' => $agent,
215 'ticket' => $createdTicket,
216 ]);
217
218 $responseContent = (new ResponseService())->maybeAppendSignature($responseContent, $agent);
219 $responseContent = wp_kses_post($responseContent);
220
221 // Agent response: actual ticket content — sends reply email to customer
222 $agentResponse = Conversation::create([
223 'ticket_id' => $createdTicket->id,
224 'person_id' => $agent->id,
225 'conversation_type' => 'response',
226 'content' => $responseContent
227 ]);
228
229 if ($attachmentHashes = Arr::get($ticketData, 'attachments', [])) {
230 Attachment::where('ticket_id', $createdTicket->id)
231 ->whereIn('file_hash', $attachmentHashes)
232 ->where('status', 'active')
233 ->update([
234 'person_id' => $agentResponse->person_id,
235 'conversation_id' => $agentResponse->id
236 ]);
237
238 $agentResponse->load('attachments');
239 }
240
241 do_action('fluent_support/agent_initiated_ticket_response', $agentResponse, $createdTicket, $agent);
242
243 $createdTicket->content = $initializedMessage;
244
245 }
246
247 $createdTicket->save();
248 }
249
250 do_action('fluent_support/ticket_created', $createdTicket, $customer);
251 do_action('fluent_support/ticket_created_behalf_of_customer', $createdTicket, $customer, $agent);
252
253 return $createdTicket;
254 }
255
256 // Callers own authorization — this also runs in system contexts (developer API, GDPR erasure, cron) with no current user.
257 public function deleteTicket($ticket, $agent = null)
258 {
259 if (!$agent) {
260 $agent = Helper::getAgentByUserId();
261 }
262
263 $ticketData = [
264 'id' => $ticket->id,
265 'title' => $ticket->title
266 ];
267
268 do_action('fluent_support/deleting_ticket', $ticket);
269 $ticket->delete();
270 do_action('fluent_support/ticket_deleted', $agent, $ticketData);
271 }
272
273 }
274