PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.4.0
Fluent Support – Helpdesk & Customer Support Ticket System v2.4.0
2.4.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 All 69 releases
← All changes | app/Models/Ticket.php +52 -1 2.2.1 → 2.4.0 View file →
@@ -15,8 +15,17 @@
15 15 protected $table = 'fs_tickets';
16 16
17 17 protected $dates = ['waiting_since'];
18 18
19 + /**
20 + * The ticket hash is a bearer credential for the signed public ticket view,
21 + * so it must never be serialized into an API response. PHP property access
22 + * is unaffected, which is what Helper::getTicketViewSignedUrl() relies on.
23 + *
24 + * @var array
25 + */
26 + protected $hidden = ['hash', 'content_hash'];
27 +
19 28 protected $appends = ['display_ticket_number'];
20 29
21 30 /**
22 31 * The attributes that are mass assignable.
@@ -70,8 +79,17 @@
70 79 $model->waiting_since = current_time('mysql');
71 80
72 81 });
73 82
83 + static::updating(function ($model) {
84 + // A hash handed out for one customer must stop working the moment
85 + // the ticket belongs to somebody else, otherwise every link already
86 + // emailed for it keeps authorizing read, reply, close and reopen.
87 + if ($model->isDirty('customer_id')) {
88 + $model->hash = bin2hex(random_bytes(16));
89 + }
90 + });
91 +
74 92 static::created(function ($model) {
75 93 if (empty($model->serial_number) || empty($model->ticket_number)) {
76 94 $model->assignTicketNumber();
77 95 }
@@ -219,8 +237,41 @@
219 237 return $query;
220 238 }
221 239
222 240 /**
241 + * Who replied last on this ticket, derived from the already-loaded
242 + * last_agent_response / last_customer_response timestamp columns.
243 + *
244 + * Returns 'agent', 'customer', or null. This is the per-ticket value behind
245 + * the `waiting_for_reply` filter and mirrors the timestamp comparison in
246 + * scopeWaitingOnly() (which lives in SQL, so it can't share this PHP code).
247 + *
248 + * Note: boot() seeds last_customer_response on creation, so a brand-new
249 + * ticket with no agent reply correctly resolves to 'customer' (awaiting an
250 + * agent). null is reserved for the rare case where neither timestamp is set.
251 + *
252 + * @return string|null
253 + */
254 + public function getLastReplyByAttribute()
255 + {
256 + $agentAt = $this->last_agent_response;
257 + $customerAt = $this->last_customer_response;
258 +
259 + if (!$agentAt && !$customerAt) {
260 + return null;
261 + }
262 + if (!$agentAt) {
263 + return 'customer';
264 + }
265 + if (!$customerAt) {
266 + return 'agent';
267 + }
268 +
269 + // Tie (same second) resolves to 'customer' — the waiting bias used by scopeWaitingOnly.
270 + return strtotime($customerAt) >= strtotime($agentAt) ? 'customer' : 'agent';
271 + }
272 +
273 + /**
223 274 * Local scope to filter tickets by not response by agent
224 275 * @param $query
225 276 * @return mixed
226 277 */
@@ -898,9 +949,9 @@
898 949
899 950 if ($value) {
900 951 if (in_array($fieldType, $customRenderers) && $rendered) {
901 952 $value = apply_filters('fluent_support/custom_field_render_' . $fieldType, $value, $scope);
902 - } else if ($fieldType == 'checkbox') {
953 + } else if (in_array($fieldType, ['checkbox', 'date-range'])) {
903 954 $value = array_values(array_filter(explode('|', $value)));
904 955 }
905 956
906 957 if (!is_array($value) && !is_object($value)) {