PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.15
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.15
6.2.15 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 All 197 releases
← All changes | app/Models/Submission.php +93 -4 6.2.2 → 6.2.15 View file →
@@ -6,8 +6,35 @@
6 6 use FluentForm\App\Modules\Payments\PaymentHelper;
7 7 use FluentForm\App\Services\Manager\FormManagerService;
8 8 use FluentForm\Framework\Support\Arr;
9 9
10 +/**
11 + * Column properties resolved at runtime via the ORM's magic __get; declared
12 + * here so static analysis can verify attribute access against the real schema
13 + * (database/Migrations/Submissions.php).
14 + *
15 + * @property int $id
16 + * @property int|null $form_id
17 + * @property int|null $serial_number
18 + * @property string|null $response
19 + * @property string|null $source_url
20 + * @property int|null $user_id
21 + * @property string|null $status
22 + * @property int $is_favourite
23 + * @property string|null $browser
24 + * @property string|null $device
25 + * @property string|null $ip
26 + * @property string|null $city
27 + * @property string|null $country
28 + * @property string|null $payment_status
29 + * @property string|null $payment_method
30 + * @property string|null $payment_type
31 + * @property string|null $currency
32 + * @property float|null $payment_total
33 + * @property float|null $total_paid
34 + * @property string|null $created_at
35 + * @property string|null $updated_at
36 + */
10 37 class Submission extends Model
11 38 {
12 39 /**
13 40 * The table associated with the model.
@@ -95,8 +122,22 @@
95 122 {
96 123 return $this->hasMany(OrderItem::class, 'submission_id', 'id');
97 124 }
98 125
126 + /**
127 + * Returns the column the entries listing should be sorted by.
128 + * Defaults to created_at so imported entries respect their original
129 + * submission date. Site owners can switch back to legacy id-based
130 + * ordering via the fluentform/entries_default_sort_column filter.
131 + */
132 + public static function getSortColumn()
133 + {
134 + $column = apply_filters('fluentform/entries_default_sort_column', 'created_at');
135 + $allowed = ['id', 'created_at'];
136 +
137 + return in_array($column, $allowed, true) ? $column : 'created_at';
138 + }
139 +
99 140 public function customQuery($attributes = [], $searchExtender = null)
100 141 {
101 142 $entryType = Arr::get($attributes, 'entry_type');
102 143 $dateRange = Arr::get($attributes, 'date_range');
@@ -121,9 +162,18 @@
121 162 if ($paymentStatuses && is_array($paymentStatuses)) {
122 163 $wheres[] = ['payment_status', $paymentStatuses];
123 164 }
124 165
125 - $query = $this->orderBy('fluentform_submissions.id', $sortBy)
166 + // Sort by submission date so imported entries (which get new auto-increment ids
167 + // but carry their original created_at) interleave correctly with native ones.
168 + // Tie-break on id to keep pagination stable for rows sharing a timestamp.
169 + // Filter lets site owners revert to legacy id-based ordering if needed.
170 + $sortColumn = self::getSortColumn();
171 + $query = $this->orderBy('fluentform_submissions.' . $sortColumn, $sortBy);
172 + if ('id' !== $sortColumn) {
173 + $query = $query->orderBy('fluentform_submissions.id', $sortBy);
174 + }
175 + $query = $query
126 176 ->when($formId, function ($q) use ($formId) {
127 177 return $q->where('fluentform_submissions.form_id', $formId);
128 178 })
129 179 ->when($isFavourite, function ($q) {
@@ -229,10 +279,31 @@
229 279 $columns = Arr::get($attributes, 'columns', 'id');
230 280
231 281 $query = $this->customQuery($attributes);
232 282
233 - $submission = $query->select($columns)->where('id', $operator, $entryId)->first();
283 + // Adjacency must match customQuery's sort order. Compare on the same
284 + // (sortColumn, id) row-tuple the listing orders by, so Next/Prev walk
285 + // in display order regardless of which sort column the filter selects.
286 + // When sortColumn is id the tuple degenerates to a simple id compare.
287 + $sortColumn = self::getSortColumn();
288 + // Re-assert whitelist; $sortColumn is interpolated into whereRaw below.
289 + if (!in_array($sortColumn, ['id', 'created_at'], true)) {
290 + $sortColumn = 'created_at';
291 + }
292 + $current = static::select(['id', $sortColumn])->find($entryId);
293 + if (!$current) {
294 + return apply_filters('fluentform/next_submission', null, $entryId, $attributes);
295 + }
234 296
297 + global $wpdb;
298 + $table = $wpdb->prefix . 'fluentform_submissions';
299 + $submission = $query->select($columns)
300 + ->whereRaw(
301 + "({$table}.{$sortColumn}, {$table}.id) {$operator} (?, ?)",
302 + [$current->{$sortColumn}, $entryId]
303 + )
304 + ->first();
305 +
235 306 return apply_filters('fluentform/next_submission', $submission, $entryId, $attributes);
236 307 }
237 308
238 309 public function countByGroup($formId)
@@ -273,10 +344,25 @@
273 344 {
274 345 $this->where('id', $id)->update($data);
275 346 }
276 347
277 - public static function remove($submissionIds)
348 + public static function remove($submissionIds, $formId = null)
278 349 {
350 + // Fail-closed scope guard: $formId scopes every delete to its owning form;
351 + // a missing scope throws rather than ever deleting unscoped.
352 + if (empty($formId)) {
353 + throw new \InvalidArgumentException('Submission::remove() requires a form id to scope the deletion.');
354 + }
355 +
356 + $submissionIds = static::where('form_id', $formId)
357 + ->whereIn('id', (array) $submissionIds)
358 + ->pluck('id')
359 + ->all();
360 +
361 + if (!$submissionIds) {
362 + return;
363 + }
364 +
279 365 static::whereIn('id', $submissionIds)->delete();
280 366
281 367 SubmissionMeta::whereIn('response_id', $submissionIds)->delete();
282 368
@@ -303,9 +389,12 @@
303 389 }
304 390 }
305 391
306 392 public function allSubmissions($attributes = []) {
307 - $searchExtender = function ($q, $escaped) {
393 + // Match by form title only in the cross-form view; once a form is
394 + // selected the search must hit submission data only, so the listing
395 + // stays identical to the (form-scoped) export and single-form list.
396 + $searchExtender = Arr::get($attributes, 'form_id') ? null : function ($q, $escaped) {
308 397 $q->orWhereHas('form', function ($q) use ($escaped) {
309 398 $q->where('title', 'LIKE', "%{$escaped}%");
310 399 });
311 400 };