PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.11
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.11
6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 3.6.65 All 196 releases
fluentform / app / Models / Submission.php

Submission.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.11, at app/Models/Submission.php

482 lines 16.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentForm\App\Models;
4
5 use Exception;
6 use FluentForm\App\Modules\Payments\PaymentHelper;
7 use FluentForm\App\Services\Manager\FormManagerService;
8 use FluentForm\Framework\Support\Arr;
9
10 class Submission extends Model
11 {
12 /**
13 * The table associated with the model.
14 *
15 * @var string
16 */
17 protected $table = 'fluentform_submissions';
18
19 /**
20 * A submission is owned by a User.
21 *
22 * @return \FluentForm\Framework\Database\Orm\Relations\BelongsTo
23 */
24 public function user()
25 {
26 return $this->belongsTo(User::class, 'user_id', 'ID');
27 }
28
29 /**
30 * A submission is owned by a form.
31 *
32 * @return \FluentForm\Framework\Database\Orm\Relations\BelongsTo
33 */
34 public function form()
35 {
36 return $this->belongsTo(Form::class, 'form_id', 'id');
37 }
38
39 /**
40 * A submission has many meta.
41 *
42 * @return \FluentForm\Framework\Database\Orm\Relations\HasMany
43 */
44 public function submissionMeta()
45 {
46 return $this->hasMany(SubmissionMeta::class, 'response_id', 'id');
47 }
48
49 /**
50 * A submission has many logs.
51 *
52 * @return \FluentForm\Framework\Database\Orm\Relations\HasMany
53 */
54 public function logs()
55 {
56 return $this->hasMany(Log::class, 'source_id', 'id');
57 }
58
59 /**
60 * A submission has many entry details.
61 *
62 * @return \FluentForm\Framework\Database\Orm\Relations\HasMany
63 */
64 public function entryDetails()
65 {
66 return $this->hasMany(EntryDetails::class, 'submission_id', 'id');
67 }
68
69 /**
70 * A submission has many transactions.
71 *
72 * @return \FluentForm\Framework\Database\Orm\Relations\HasMany
73 */
74 public function transactions()
75 {
76 return $this->hasMany(Transaction::class, 'submission_id', 'id');
77 }
78
79 /**
80 * A submission has many subscriptions.
81 *
82 * @return \FluentForm\Framework\Database\Orm\Relations\HasMany
83 */
84 public function subscriptions()
85 {
86 return $this->hasMany(Subscription::class, 'submission_id', 'id');
87 }
88
89 /**
90 * A submission has many order items.
91 *
92 * @return \FluentForm\Framework\Database\Orm\Relations\HasMany
93 */
94 public function orderItems()
95 {
96 return $this->hasMany(OrderItem::class, 'submission_id', 'id');
97 }
98
99 /**
100 * Returns the column the entries listing should be sorted by.
101 * Defaults to created_at so imported entries respect their original
102 * submission date. Site owners can switch back to legacy id-based
103 * ordering via the fluentform/entries_default_sort_column filter.
104 */
105 public static function getSortColumn()
106 {
107 $column = apply_filters('fluentform/entries_default_sort_column', 'created_at');
108 $allowed = ['id', 'created_at'];
109
110 return in_array($column, $allowed, true) ? $column : 'created_at';
111 }
112
113 public function customQuery($attributes = [], $searchExtender = null)
114 {
115 $entryType = Arr::get($attributes, 'entry_type');
116 $dateRange = Arr::get($attributes, 'date_range');
117 $isFavourite = false;
118 $status = $entryType;
119
120 // We have to handle favorites separately because status and favorites are different
121 if ('favorites' === $entryType) {
122 $isFavourite = true;
123 $status = false;
124 }
125
126 $formId = Arr::get($attributes, 'form_id');
127 $startDate = Arr::get($dateRange, 0);
128 $endDate = Arr::get($dateRange, 1);
129 $search = Arr::get($attributes, 'search');
130 $sortBy = \FluentForm\App\Helpers\Helper::sanitizeOrderValue(Arr::get($attributes, 'sort_by', 'DESC'));
131
132 $wheres = [];
133 $paymentStatuses = Arr::get($attributes, 'payment_statuses');
134
135 if ($paymentStatuses && is_array($paymentStatuses)) {
136 $wheres[] = ['payment_status', $paymentStatuses];
137 }
138
139 // Sort by submission date so imported entries (which get new auto-increment ids
140 // but carry their original created_at) interleave correctly with native ones.
141 // Tie-break on id to keep pagination stable for rows sharing a timestamp.
142 // Filter lets site owners revert to legacy id-based ordering if needed.
143 $sortColumn = self::getSortColumn();
144 $query = $this->orderBy('fluentform_submissions.' . $sortColumn, $sortBy);
145 if ('id' !== $sortColumn) {
146 $query = $query->orderBy('fluentform_submissions.id', $sortBy);
147 }
148 $query = $query
149 ->when($formId, function ($q) use ($formId) {
150 return $q->where('fluentform_submissions.form_id', $formId);
151 })
152 ->when($isFavourite, function ($q) {
153 return $q->where('is_favourite', true);
154 })
155 ->where(function ($q) use ($status) {
156 $operator = '=';
157
158 if (!$status) {
159 $operator = '!=';
160 $status = 'trashed';
161 }
162
163 return $q->where('fluentform_submissions.status', $operator, $status);
164 })
165 ->when($startDate && $endDate, function ($q) use ($startDate, $endDate) {
166 $endDate .= ' 23:59:59';
167
168 return $q->where('fluentform_submissions.created_at', '>=', $startDate)
169 ->where('fluentform_submissions.created_at', '<=', $endDate);
170 })
171 ->when($search, function ($q) use ($search, $searchExtender) {
172 global $wpdb;
173 $escaped = $wpdb->esc_like($search);
174 return $q->where(function ($q) use ($escaped, $searchExtender) {
175 $q->where('fluentform_submissions.id', 'LIKE', "%{$escaped}%")
176 ->orWhere('response', 'LIKE', "%{$escaped}%")
177 ->orWhere('fluentform_submissions.status', 'LIKE', "%{$escaped}%")
178 ->orWhere('fluentform_submissions.created_at', 'LIKE', "%{$escaped}%");
179 if ($searchExtender) {
180 $searchExtender($q, $escaped);
181 }
182 });
183 })
184 ->when($wheres, function ($q) use ($wheres) {
185 foreach ($wheres as $where) {
186 if (is_array($where) && count($where) > 1) {
187 if (count($where) > 2) {
188 $column = $where[0];
189 $operator = $where[1];
190 $value = $where[2];
191 } else {
192 $column = $where[0];
193 $operator = '=';
194 $value = $where[1];
195 }
196
197 if (is_array($value)) {
198 return $q->whereIn($column, $value);
199 } else {
200 return $q->where($column, $operator, $value);
201 }
202 }
203 }
204 });
205
206 return $query;
207 }
208
209 public function paginateEntries($attributes = [])
210 {
211 $formId = Arr::get($attributes, 'form_id');
212 $allowFormIds = FormManagerService::getUserAllowedFormsScope();
213 $query = $this->customQuery($attributes);
214 $query = $query->when(false !== $allowFormIds, function ($q) use ($allowFormIds) {
215 return $q->whereIn('fluentform_submissions.form_id', $allowFormIds ?: [0]);
216 });
217 if (Arr::get($attributes, 'advanced_filter')) {
218 $query = apply_filters('fluentform/apply_entries_advance_filter', $query, $attributes);
219 }
220 $response = $query->paginate();
221 $response = apply_filters_deprecated(
222 'fluentform_get_raw_responses',
223 [
224 $response,
225 $formId
226 ],
227 FLUENTFORM_FRAMEWORK_UPGRADE,
228 'fluentform/get_raw_responses',
229 'Use fluentform/get_raw_responses instead of fluentform_get_raw_responses.'
230 );
231
232 return apply_filters('fluentform/get_raw_responses', $response, $formId);
233 }
234
235 public function findAdjacentSubmission($attributes = [])
236 {
237 $sortBy = Arr::get($attributes, 'sort_by', 'DESC');
238
239 $direction = Arr::get($attributes, 'direction', 'next');
240
241 $operator = 'ASC' === $sortBy && 'previous' === $direction ? '>' : '<';
242
243 if ('previous' === $direction) {
244 $operator = 'ASC' === $sortBy ? '>' : '<';
245 } else {
246 $operator = 'ASC' === $sortBy ? '<' : '>';
247 $attributes['sort_by'] = 'ASC' === $sortBy ? 'DESC' : 'ASC';
248 }
249
250 $entryId = Arr::get($attributes, 'entry_id');
251
252 $columns = Arr::get($attributes, 'columns', 'id');
253
254 $query = $this->customQuery($attributes);
255
256 // Adjacency must match customQuery's sort order. Compare on the same
257 // (sortColumn, id) row-tuple the listing orders by, so Next/Prev walk
258 // in display order regardless of which sort column the filter selects.
259 // When sortColumn is id the tuple degenerates to a simple id compare.
260 $sortColumn = self::getSortColumn();
261 // Re-assert whitelist; $sortColumn is interpolated into whereRaw below.
262 if (!in_array($sortColumn, ['id', 'created_at'], true)) {
263 $sortColumn = 'created_at';
264 }
265 $current = static::select(['id', $sortColumn])->find($entryId);
266 if (!$current) {
267 return apply_filters('fluentform/next_submission', null, $entryId, $attributes);
268 }
269
270 global $wpdb;
271 $table = $wpdb->prefix . 'fluentform_submissions';
272 $submission = $query->select($columns)
273 ->whereRaw(
274 "({$table}.{$sortColumn}, {$table}.id) {$operator} (?, ?)",
275 [$current->{$sortColumn}, $entryId]
276 )
277 ->first();
278
279 return apply_filters('fluentform/next_submission', $submission, $entryId, $attributes);
280 }
281
282 public function countByGroup($formId)
283 {
284 $statuses = $this->selectRaw('status, COUNT(*) as count')
285 ->where('form_id', $formId)
286 ->groupBy('status')
287 ->get();
288
289 $counts = [];
290
291 foreach ($statuses as $status) {
292 $counts[$status->status] = (int) $status->count;
293 }
294
295 $counts['all'] = array_sum($counts);
296
297 if (isset($counts['trashed'])) {
298 $counts['all'] -= $counts['trashed'];
299 }
300
301 $favorites = $this->where('form_id', $formId)
302 ->where('is_favourite', 1)
303 ->where('status', '!=', 'trashed')
304 ->count();
305
306 $counts['favorites'] = $favorites;
307
308 return array_merge([
309 'unread' => 0,
310 'read' => 0,
311 'spam' => 0,
312 'trashed' => 0,
313 ], $counts);
314 }
315
316 public function amend($id, $data = [])
317 {
318 $this->where('id', $id)->update($data);
319 }
320
321 public static function remove($submissionIds, $formId = null)
322 {
323 // Fail-closed scope guard: $formId scopes every delete to its owning form;
324 // a missing scope throws rather than ever deleting unscoped.
325 if (empty($formId)) {
326 throw new \InvalidArgumentException('Submission::remove() requires a form id to scope the deletion.');
327 }
328
329 $submissionIds = static::where('form_id', $formId)
330 ->whereIn('id', (array) $submissionIds)
331 ->pluck('id')
332 ->all();
333
334 if (!$submissionIds) {
335 return;
336 }
337
338 static::whereIn('id', $submissionIds)->delete();
339
340 SubmissionMeta::whereIn('response_id', $submissionIds)->delete();
341
342 Log::whereIn('source_id', $submissionIds)
343 ->where('source_type', 'submission_item')
344 ->delete();
345
346 EntryDetails::whereIn('submission_id', $submissionIds)->delete();
347
348 try {
349 if (PaymentHelper::hasPaymentSettings()) {
350 OrderItem::whereIn('submission_id', $submissionIds)->delete();
351 Transaction::whereIn('submission_id', $submissionIds)->delete();
352 Subscription::whereIn('submission_id', $submissionIds)->delete();
353 }
354
355 wpFluent()->table('ff_scheduled_actions')
356 ->whereIn('origin_id', $submissionIds)
357 ->where('type', 'submission_action')
358 ->delete();
359
360 } catch (Exception $exception) {
361 // ...
362 }
363 }
364
365 public function allSubmissions($attributes = []) {
366 $searchExtender = function ($q, $escaped) {
367 $q->orWhereHas('form', function ($q) use ($escaped) {
368 $q->where('title', 'LIKE', "%{$escaped}%");
369 });
370 };
371 $customQuery = $this->customQuery($attributes, $searchExtender);
372 $allowFormIds = FormManagerService::getUserAllowedFormsScope();
373
374 $result = $customQuery
375 ->with([
376 'form' => function ($q) {
377 $q->select(['id', 'title']);
378 }
379 ])
380 ->select(['id', 'form_id', 'status', 'created_at', 'browser', 'currency', 'total_paid'])
381 ->when(false !== $allowFormIds, function ($q) use ($allowFormIds){
382 return $q->whereIn('form_id', $allowFormIds ?: [0]);
383 })
384 ->paginate()
385 ->toArray();
386
387 $useHumanDate = apply_filters('fluentform/entries_human_date', false);
388
389 foreach ($result['data'] as &$entry) {
390 $entry['entry_url'] = admin_url('admin.php?page=fluent_forms&route=entries&form_id=' . $entry['form_id'] . '#/entries/' . $entry['id']);
391
392 if ($useHumanDate) {
393 $entry['human_date'] = human_time_diff(strtotime($entry['created_at']), strtotime(current_time('mysql')));
394 }
395 }
396
397 $result['available_forms'] = $this->availableForms();
398
399 return $result;
400 }
401
402 public function availableForms()
403 {
404 $form = new Form();
405 if (false !== ($allowForms = FormManagerService::getUserAllowedFormsScope())) {
406 return $form->select('id', 'title')->whereIn('id', $allowForms ?: [0])->get();
407 }
408 return $form->select('id', 'title')->get();
409 }
410
411 public static function report($attributes)
412 {
413 $from = date('Y-m-d H:i:s', strtotime('-30 days'));
414 $to = date('Y-m-d H:i:s', strtotime('+1 days'));
415 $formId = Arr::get($attributes, 'form_id');
416 $allowFormIds = FormManagerService::getUserAllowedFormsScope();
417 $status = Arr::get($attributes, 'entry_status');
418 $start = Arr::get($attributes, 'date_range.0', '');
419 $end = Arr::get($attributes, 'date_range.1', '');
420 $dateRange = Arr::get($attributes, 'date_range');
421
422 if ('all' === $dateRange) {
423 $firstItem = self::orderBy('created_at', 'ASC')
424 ->when(false !== $allowFormIds, function ($q) use ($allowFormIds) {
425 return $q->whereIn('form_id', $allowFormIds ?: [0]);
426 })
427 ->when($formId, function ($q) use ($formId) {
428 return $q->where('form_id', $formId);
429 })
430 ->when($status, function ($q2) use ($status) {
431 return $q2->where('status', $status);
432 })
433 ->first();
434
435 if ($firstItem && $firstItem->created_at) {
436 $from = date('Y-m-d H:i:s', strtotime($firstItem->created_at));
437 $to = date('Y-m-d H:i:s');
438 }
439 }
440
441
442 if ($start && $startTime = strtotime($start)) {
443 $from = date('Y-m-d 00:00:00', $startTime);
444 }
445
446 if ($end && $endTime = strtotime($end)) {
447 $to = date('Y-m-d 23:59:59', $endTime);
448 }
449
450 $period = new \DatePeriod(new \DateTime($from), new \DateInterval('P1D'), new \DateTime($to));
451
452 $range = [];
453
454 foreach ($period as $date) {
455 $range[$date->format('Y-m-d')] = 0;
456 }
457
458 $items = self::selectRaw('DATE(created_at) AS date')
459 ->selectRaw('COUNT(id) AS count')
460 ->whereBetween('created_at', [$from, $to])
461 ->groupBy('date')
462 ->orderBy('date', 'ASC')
463 ->when(false !== $allowFormIds, function ($q) use ($allowFormIds) {
464 return $q->whereIn('form_id', $allowFormIds ?: [0]);
465 })
466 ->when($formId, function ($q) use ($formId) {
467 return $q->where('form_id', $formId);
468 })
469 ->when($status, function ($q2) use ($status) {
470 return $q2->where('status', $status);
471 })
472 ->get();
473
474
475 foreach ($items as $item) {
476 $range[$item->date] = $item->count;
477 }
478
479 return $range;
480 }
481 }
482