PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 1.10.2
Fluent Support – Helpdesk & Customer Support Ticket System v1.10.2
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 / Importer / FreshDeskTickets.php

FreshDeskTickets.php in Fluent Support – Helpdesk & Customer Support Ticket System 1.10.2, at app/Services/Tickets/Importer/FreshDeskTickets.php

362 lines 12.6 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\Importer;
4
5 use FluentSupport\App\Models\Meta;
6 use FluentSupport\App\Services\Helper;
7
8 class FreshDeskTickets extends BaseImporter
9 {
10 protected $handler = 'freshdesk';
11 public $accessToken;
12 public $mailbox_id;
13 private $domain;
14 protected $limit = 10;
15 private $hasMore;
16 private $currentPage;
17 private $totalTickets;
18 private $originId;
19 private $responseCount;
20 protected $ticketUpdatedSince;
21
22 public function stats()
23 {
24 $metadata = Meta::where('object_type', '_fs_freshdesk_migration_info')->first();
25 $previouslyImported = Helper::safeUnserialize($metadata->value ?? []);
26 $previouslyImported['domain'] = $metadata->key ?? '';
27 return [
28 'name' => esc_html('Freshdesk'),
29 'handler' => $this->handler,
30 'type' => 'sass',
31 'last_migrated' => get_option('_fs_migrate_freshdesk'),
32 'previously_imported' => $previouslyImported,
33 ];
34 }
35
36 public function doMigration($page, $handler)
37 {
38 $this->currentPage = $page;
39 $this->handler = $handler;
40 $tickets = $this->ticketsWithReply();
41
42 if (is_wp_error($tickets)) {
43 throw new \Exception(esc_html($tickets->get_error_message()));
44 }
45
46 $results = $this->migrateTickets($tickets);
47
48 $completedNow = isset($results['inserts']) ? count($results['inserts']) : 0;
49
50 $response = [
51 'handler' => $this->handler,
52 'insert_ids' => $results['inserts'],
53 'skips' => count($results['skips']),
54 'has_more' => $this->hasMore,
55 'completed' => $completedNow,
56 'imported_page' => $page,
57 'total_pages' => null,
58 'next_page' => $page + 1,
59 'total_tickets' => null,
60 'remaining' => 0,
61 ];
62
63 if ($this->hasMore) {
64 $previousValue = Meta::where('object_type', '_fs_freshdesk_migration_info')->first();
65 if ($previousValue) {
66 Meta::where('object_type', '_fs_freshdesk_migration_info')->update([
67 'key' => $this->domain,
68 'value' => maybe_serialize($response)
69 ]);
70 } else {
71 Meta::insert([
72 'object_type' => '_fs_freshdesk_migration_info',
73 'key' => $this->domain,
74 'value' => maybe_serialize($response)
75 ]);
76 }
77 return $response;
78 }
79
80 Meta::where('object_type', '_fs_freshdesk_migration_info')->delete();
81 $response['message'] = __('All tickets have been imported successfully', 'fluent-support');
82 update_option('_fs_migrate_freshdesk', current_time('mysql'), 'no');
83 return $response;
84 }
85
86 private function ticketsWithReply()
87 {
88 try {
89 $url = "{$this->domain}/api/v2/tickets?updated_since={$this->ticketUpdatedSince}&per_page={$this->limit}&page={$this->currentPage}&include=stats,requester,description";
90 $tickets = $this->makeRequest($url);
91
92 if (is_wp_error($tickets)) {
93 return $tickets;
94 }
95
96 $formattedTickets = [];
97 if (empty($tickets)) {
98 $this->hasMore = false;
99 return [];
100 }
101
102 $this->hasMore = true;
103
104 foreach ($tickets as $ticket) {
105 $singleTicketUrl = "{$this->domain}/api/v2/tickets/{$ticket->id}?include=conversations,requester,stats";
106 $singleTicket = $this->makeRequest($singleTicketUrl);
107
108 if (is_wp_error($singleTicket)) {
109 return $singleTicket;
110 }
111
112 if (!$singleTicket) {
113 continue;
114 }
115 $this->originId = $singleTicket->id;
116 $attachments = [];
117
118 if ($singleTicket->attachments) {
119 $attachments = $this->getAttachments($singleTicket->attachments);
120
121 if (is_wp_error($attachments)) {
122 return $attachments;
123 }
124 }
125
126 $lastCustomerResponse = $singleTicket->stats->requester_responded_at ?? $singleTicket->stats->status_updated_at;
127 $lastAgentResponse = $singleTicket->stats->agent_responded_at ? date('Y-m-d h:i:s', strtotime($singleTicket->stats->agent_responded_at)) : NULL;
128
129 $formattedTickets[] = [
130 'title' => sanitize_text_field($ticket->subject),
131 'content' => wp_kses_post($ticket->description),
132 'origin_id' => intval($ticket->id),
133 'source' => sanitize_text_field($this->handler),
134 'customer' => $this->fetchPerson($singleTicket->requester),
135 'replies' => $this->getReplies($singleTicket->conversations, $singleTicket->requester),
136 'response_count' => $this->responseCount,
137 'status' => $this->getStatus($ticket->status),
138 'client_priority' => $this->getPriority($ticket->priority),
139 'priority' => $this->getPriority($ticket->priority),
140 'created_at' => date('Y-m-d h:i:s', strtotime($ticket->created_at)),
141 'updated_at' => date('Y-m-d h:i:s', strtotime($ticket->updated_at)),
142 'last_customer_response' => date('Y-m-d h:i:s', strtotime($lastCustomerResponse)),
143 'last_agent_response' => $lastAgentResponse,
144 'attachments' => $attachments
145 ];
146 }
147
148 return $formattedTickets;
149
150 } catch (\Exception $e) {
151 return new \WP_Error('freshdesk_api_error', $e->getMessage());
152 }
153 }
154
155 private function getReplies($replies, $requester)
156 {
157 if (!$requester || !$replies) {
158 return [];
159 }
160
161 $formattedReplies = [];
162 $user = $this->fetchPerson($requester);
163
164 // Check if fetchPerson returned an error
165 if (is_wp_error($user)) {
166 return $user; // Return the WP_Error object
167 }
168
169 $this->setResponseCount(count($replies));
170 foreach ($replies as $reply) {
171 $ticketReply = [
172 'content' => wp_kses_post($reply->body),
173 'conversation_type' => ($reply->source == 2) ? 'note' : 'response',
174 'created_at' => date('Y-m-d h:i:s', strtotime($reply->created_at)),
175 'updated_at' => date('Y-m-d h:i:s', strtotime($reply->updated_at)),
176 'is_customer_reply' => ($requester->id === $reply->user_id),
177 ];
178
179 if ($requester->id == $reply->user_id) {
180 $ticketReply['user'] = $user;
181 } else {
182 $ticketReply['user'] = $this->fetchPerson($reply->user_id, 'agent', $reply->support_email);
183
184 if (is_wp_error($ticketReply['user'])) {
185 return $ticketReply['user'];
186 }
187 }
188
189 if (count($reply->attachments)) {
190 $ticketReply['attachments'] = $this->getAttachments($reply->attachments);
191
192 if (is_wp_error($ticketReply['attachments'])) {
193 return $ticketReply['attachments'];
194 }
195 }
196 $formattedReplies[] = $ticketReply;
197 }
198 return $formattedReplies;
199 }
200
201 private function makeRequest($url)
202 {
203 $token = base64_encode($this->accessToken . ':X');
204 $request = wp_remote_get($url, [
205 'headers' => [
206 'Authorization' => "Bearer {$token}",
207 'Content-Type' => 'application/json'
208 ],
209 'timeout' => 600
210 ]);
211
212 if (is_wp_error($request)) {
213 return new \WP_Error('api_request_error', $request->get_error_message());
214 }
215
216 $response_code = wp_remote_retrieve_response_code($request);
217
218 if ($response_code == 429) {
219 $retry_after = wp_remote_retrieve_header($request, 'retry-after');
220 if ($retry_after) {
221 $minutes = floor($retry_after / 60);
222 $seconds = $retry_after % 60;
223 $error_message = "Rate limit exceeded. Please retry after {$minutes} minutes and {$seconds} seconds.";
224 } else {
225 $error_message = "Rate limit exceeded. Please try again later.";
226 }
227 return new \WP_Error($response_code, $error_message);
228 }
229
230 if ($response_code >= 400) {
231 $body = wp_remote_retrieve_body($request);
232 $decoded_body = json_decode($body);
233 $error_message = isset($decoded_body->message) ? $decoded_body->message : "API request failed with status code: {$response_code}";
234 return new \WP_Error($response_code, $error_message);
235 }
236
237 $response = json_decode(wp_remote_retrieve_body($request));
238
239 if (json_last_error() !== JSON_ERROR_NONE) {
240 return new \WP_Error('json_parse_error', 'Failed to parse API response: ' . json_last_error_msg());
241 }
242
243 return $response;
244 }
245
246 private function fetchPerson($personData, $type = 'customer', $email = null)
247 {
248 if ('agent' == $type) {
249 try {
250 $url = "{$this->domain}/api/v2/agents/{$personData}";
251 $agent = $this->makeRequest($url);
252
253 if (is_wp_error($agent)) {
254 return $agent;
255 }
256
257 if (!isset($agent->contact)) {
258 $personArray = [
259 'first_name' => 'Freshdesk anonymous agent',
260 'last_name' => '',
261 'email' => $email,
262 'person_type' => 'agent',
263 ];
264 return Common::updateOrCreatePerson($personArray);
265 }
266 $personArray = Common::formatPersonData($agent->contact, $type);
267
268 return Common::updateOrCreatePerson($personArray);
269 } catch (\Exception $e) {
270 return new \WP_Error('person_fetch_error', $e->getMessage());
271 }
272 } else {
273 try {
274 $personArray = Common::formatPersonData($personData, $type);
275 return Common::updateOrCreatePerson($personArray);
276 } catch (\Exception $e) {
277 return new \WP_Error('person_format_error', $e->getMessage());
278 }
279 }
280 }
281
282 private function getAttachments($attachments)
283 {
284 try {
285 $wpUploadDir = wp_upload_dir();
286 $baseDir = $wpUploadDir['basedir'] . '/fluent-support/freshdesk-ticket-' . $this->originId . '/';
287
288 $formattedAttachments = [];
289 foreach ($attachments as $attachment) {
290 $filePath = Common::downloadFile($attachment->attachment_url, $baseDir, $attachment->name);
291
292 // Check if downloadFile returned an error
293 if (is_wp_error($filePath)) {
294 return $filePath; // Return the WP_Error object
295 }
296
297 $fileUrl = $wpUploadDir['baseurl'] . '/fluent-support/freshdesk-ticket-' . $this->originId . '/' . $attachment->name;
298 $formattedAttachments[] = [
299 'full_url' => $fileUrl,
300 'title' => $attachment->name,
301 'file_path' => $filePath,
302 'driver' => 'local',
303 'status' => 'active',
304 'file_type' => $attachment->content_type
305 ];
306 }
307
308 return $formattedAttachments;
309 } catch (\Exception $e) {
310 return new \WP_Error('attachment_error', $e->getMessage());
311 }
312 }
313
314 public function setAccessToken($accessToken)
315 {
316 $this->accessToken = $accessToken;
317 }
318
319 public function setDomain($domain)
320 {
321 $this->domain = $domain;
322 }
323
324 private function setResponseCount($count)
325 {
326 $this->responseCount = $count;
327 }
328
329 private function getStatus($statusCode)
330 {
331 switch ($statusCode) {
332 case 2:
333 return 'active';
334 case 3:
335 return 'pending';
336 case 4 || 5:
337 return 'closed';
338 default:
339 return 'new';
340 }
341 }
342
343 private function getPriority($priorityCode)
344 {
345 switch ($priorityCode) {
346 case 1:
347 return 'normal';
348 case 2:
349 return 'medium';
350 case 3 || 4:
351 return 'critical';
352 default:
353 return 'normal';
354 }
355 }
356
357 public function deleteTickets($page)
358 {
359 return;
360 }
361 }
362