PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.14
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.14
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 / Services / Logger / Logger.php

Logger.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.14, at app/Services/Logger/Logger.php

485 lines 17.4 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\Services\Logger;
4
5 use FluentForm\App\Models\Log;
6 use FluentForm\App\Models\Form;
7 use FluentForm\App\Helpers\Helper;
8 use FluentForm\App\Models\Scheduler;
9 use FluentForm\App\Services\Manager\FormManagerService;
10 use FluentForm\Framework\Support\Arr;
11 use FluentForm\Framework\Support\Collection;
12 use FluentForm\Framework\Validator\ValidationException;
13
14 class Logger
15 {
16 public function get($attributes = [])
17 {
18 $statuses = Arr::get($attributes, 'status');
19 $formIds = $this->normalizeFormScope(Arr::get($attributes, 'form_id'));
20 $formIds = $this->resolveVisibleFormScope($formIds);
21 $components = Arr::get($attributes, 'component');
22 $sortBy = \FluentForm\App\Helpers\Helper::sanitizeOrderValue(Arr::get($attributes, 'sort_by', 'DESC'));
23 $type = Arr::get($attributes, 'type', 'log');
24 $dateRange = Arr::get($attributes, 'date_range', []);
25 $startDate = Arr::get($dateRange, 0);
26 $endDate = Arr::get($dateRange, 1);
27 [$table, $model, $columns, $join, $componentColumn, $dateColumn] = $this->getBases($type);
28
29 $logsQuery = $model->select($columns)
30 ->leftJoin('fluentform_forms', 'fluentform_forms.id', '=', $join)
31 ->orderBy($table . '.id', $sortBy)
32 ->when(false !== $formIds && [] !== $formIds, function ($q) use ($formIds) {
33 return $q->whereIn('fluentform_forms.id', array_map('intval', $formIds));
34 })
35 ->when([] === $formIds, function ($q) {
36 return $q->whereIn('fluentform_forms.id', [0]);
37 })
38 ->when($statuses, function ($q) use ($statuses, $table) {
39 return $q->whereIn($table . '.status', array_map('sanitize_text_field', $statuses));
40 })
41 ->when($components, function ($q) use ($components, $componentColumn) {
42 return $q->whereIn($componentColumn, array_map('sanitize_text_field', $components));
43 })
44 ->when($startDate && $endDate, function ($q) use ($startDate, $endDate, $dateColumn) {
45 // Concatenate time if not time included on start/end date string
46 if (date('Y-m-d H:i:s', strtotime($startDate)) != $startDate) { // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- comparing a local-time string to its own roundtrip; UTC would be wrong here
47 $startDate .= ' 00:00:01';
48 }
49 if (date('Y-m-d H:i:s', strtotime($endDate)) != $endDate) { // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- comparing a local-time string to its own roundtrip; UTC would be wrong here
50 $endDate .= ' 23:59:59';
51 }
52 return $q->where($dateColumn, '>=', $startDate)
53 ->where($dateColumn, '<=', $endDate);
54 });
55
56 $logs = $logsQuery->paginate();
57
58 $logItems = $logs->items();
59
60 foreach ($logItems as $log) {
61 $hasUrl = ('api' === $type) || (
62 'submission_item' == $log->source_type && $log->submission_id
63 );
64
65 if ($hasUrl) {
66 $log->submission_url = admin_url(
67 'admin.php?page=fluent_forms&route=entries&form_id=' . $log->form_id . '#/entries/' . $log->submission_id
68 );
69 }
70
71 $log->component = Helper::getLogInitiator($log->component, $type);
72 $log->integration_enabled = false;
73
74 $notificationKeys = apply_filters('fluentform/global_notification_active_types', [], $log->form_id);
75
76 unset($notificationKeys['user_registration_feeds']);
77 unset($notificationKeys['notifications']);
78
79 $notificationKeys = array_flip($notificationKeys);
80
81 $actionName = $log->getOriginal('component');
82 if ($actionName) {
83 $actionName = str_replace(['fluentform_integration_notify_', 'fluentform/integration_notify_'], '', $actionName);
84
85 if (in_array($actionName, $notificationKeys)) {
86 $log->integration_enabled = true;
87 }
88 }
89 }
90
91 $logItems = apply_filters_deprecated(
92 'fluentform_all_logs',
93 [
94 $logItems,
95 ],
96 FLUENTFORM_FRAMEWORK_UPGRADE,
97 'fluentform/get_logs',
98 'Use fluentform/get_logs instead of fluentform_all_logs'
99 );
100
101 $logs->setCollection(Collection::make($logItems));
102
103 $logs = apply_filters('fluentform/get_logs', $logs);
104
105 foreach ($logs->items() as $log) {
106 if ('api' === $type && isset($log->note)) {
107 $log->note = static::sanitizeLogHtml($log->note);
108 } elseif (isset($log->description)) {
109 $log->description = static::sanitizeLogHtml($log->description);
110 }
111 }
112
113 return $logs;
114 }
115
116 protected function normalizeFormScope($formIds)
117 {
118 if (null === $formIds || false === $formIds || '' === $formIds || [] === $formIds) {
119 return false;
120 }
121
122 $normalized = array_values(array_filter(array_map('intval', (array) $formIds)));
123
124 return $normalized ?: [];
125 }
126
127 protected function resolveVisibleFormScope($requestedFormIds)
128 {
129 $allowedForms = FormManagerService::getUserAllowedFormsScope();
130
131 if (false === $allowedForms) {
132 return $requestedFormIds;
133 }
134
135 if (false === $requestedFormIds) {
136 return $allowedForms;
137 }
138
139 return array_values(array_intersect($requestedFormIds, $allowedForms));
140 }
141
142 protected function getBases($type)
143 {
144 if ('log' === $type) {
145 $table = 'fluentform_logs';
146 $model = Log::query();
147 $columns = [
148 'fluentform_logs.*',
149 'fluentform_forms.title as form_title',
150 'fluentform_logs.source_id as submission_id',
151 'fluentform_logs.parent_source_id as form_id',
152 ];
153 $join = 'fluentform_logs.parent_source_id';
154 $componentColumn = 'fluentform_logs.component';
155 $dateColumn = 'fluentform_logs.created_at';
156 } else {
157 $table = 'ff_scheduled_actions';
158 $model = Scheduler::query();
159 $columns = [
160 'ff_scheduled_actions.id',
161 'ff_scheduled_actions.action as component',
162 'ff_scheduled_actions.form_id',
163 'ff_scheduled_actions.origin_id as submission_id',
164 'ff_scheduled_actions.status',
165 'ff_scheduled_actions.note',
166 'ff_scheduled_actions.updated_at',
167 'ff_scheduled_actions.feed_id',
168 'fluentform_forms.title as form_title',
169 ];
170 $join = 'ff_scheduled_actions.form_id';
171 $componentColumn = 'ff_scheduled_actions.action';
172 $dateColumn = 'ff_scheduled_actions.updated_at';
173 }
174
175 return [$table, $model, $columns, $join, $componentColumn, $dateColumn];
176 }
177
178 public function getFilters($attributes = [])
179 {
180 $type = Arr::get($attributes, 'type', 'log');
181 $allowedForms = FormManagerService::getUserAllowedFormsScope();
182
183 if ('log' === $type) {
184 $statusQuery = Log::select('status')->distinct();
185 $componentQuery = Log::select('component')->distinct();
186 $formIdQuery = Log::select('parent_source_id as form_id')->distinct();
187 $scopeColumn = 'parent_source_id';
188 } else {
189 $statusQuery = Scheduler::select('status')->distinct();
190 $componentQuery = Scheduler::select('action as component')->distinct();
191 $formIdQuery = Scheduler::select('form_id')->distinct();
192 $scopeColumn = 'form_id';
193 }
194
195 $statusRows = $this->scopeFilterQuery($statusQuery, $scopeColumn, $allowedForms)->get();
196 $componentRows = $this->scopeFilterQuery($componentQuery, $scopeColumn, $allowedForms)->get();
197 $formIdRows = $this->scopeFilterQuery($formIdQuery, $scopeColumn, $allowedForms)->get();
198
199 $statuses = $statusRows->pluck('status')->filter()->map(function ($item) {
200 return [
201 'label' => ucwords($item),
202 'value' => $item,
203 ];
204 })->values();
205
206 $components = $componentRows->pluck('component')->filter()->map(function ($item) use ($type) {
207 return [
208 'label' => Helper::getLogInitiator($item, $type),
209 'value' => $item,
210 ];
211 })->values();
212
213 $formIds = $formIdRows->pluck('form_id')->filter()->toArray();
214 if (false !== ($allowForms = FormManagerService::getUserAllowedFormsScope())) {
215 $formIds = array_filter($formIds, function ($value) use ($allowForms) {
216 return in_array($value, $allowForms);
217 });
218 }
219
220 $forms = Form::select('id', 'title')->whereIn('id', $formIds ?: [0])->get();
221
222 return apply_filters('fluentform/get_log_filters', [
223 'statuses' => $statuses,
224 'components' => $components,
225 'forms' => $forms,
226 ]);
227 }
228
229 protected function scopeFilterQuery($query, $formColumn, $allowedForms)
230 {
231 if (false !== $allowedForms) {
232 // phpcs:ignore Universal.Operators.DisallowShortTernary.Found -- `?: [0]` is the delegated-scope regression contract (detect_resource_authorization)
233 $query->whereIn($formColumn, $allowedForms ?: [0]);
234 }
235
236 return $query;
237 }
238
239 public function getSubmissionLogs($submissionId, $attributes = [])
240 {
241 $logType = Arr::get($attributes, 'log_type', 'logs');
242
243 $sourceType = Arr::get($attributes, 'source_type', 'submission_item');
244
245 if ('logs' === $logType) {
246 $logs = Log::where('source_id', $submissionId)
247 ->where('source_type', $sourceType)
248 ->orderBy('id', 'DESC')
249 ->get();
250
251 $logs = apply_filters_deprecated(
252 'fluentform_entry_logs',
253 [
254 $logs,
255 $submissionId,
256 ],
257 FLUENTFORM_FRAMEWORK_UPGRADE,
258 'fluentform/submission_logs',
259 'Use fluentform/submission_logs instead of fluentform_entry_logs.'
260 );
261
262 $logs = apply_filters('fluentform/submission_logs', $logs, $submissionId);
263
264 $entryLogs = [];
265
266 foreach ($logs as $log) {
267 if (isset($log->component) && $log->component === 'slack') {
268 continue;
269 }
270 $entryLogs[] = [
271 'id' => $log->id,
272 'status' => esc_attr($log->status),
273 'title' => esc_html($log->component . ' (' . $log->title . ')'),
274 'description' => $log->description,
275 'created_at' => (string) $log->created_at,
276 ];
277 }
278 } else {
279 $columns = [
280 'id',
281 'action',
282 'status',
283 'note',
284 'created_at',
285 'form_id',
286 'feed_id',
287 'origin_id',
288 ];
289
290 $logs = Scheduler::select($columns)
291 ->where('origin_id', $submissionId)
292 ->orderBy('id', 'DESC')
293 ->get();
294
295 $logs = apply_filters_deprecated(
296 'fluentform_entry_api_logs',
297 [
298 $logs,
299 $submissionId,
300 ],
301 FLUENTFORM_FRAMEWORK_UPGRADE,
302 'fluentform/submission_api_logs',
303 'Use fluentform/submission_api_logs instead of fluentform_entry_api_logs.'
304 );
305 $logs = apply_filters('fluentform/submission_api_logs', $logs, $submissionId);
306
307 $entryLogs = [];
308
309 foreach ($logs as $log) {
310 $entryLog = [
311 'id' => $log->id,
312 'status' => esc_attr($log->status),
313 'title' => 'n/a',
314 'description' => $log->note,
315 'created_at' => (string) $log->created_at,
316 'form_id' => $log->form_id,
317 'feed_id' => $log->feed_id,
318 'submission_id' => $log->origin_id,
319 'integration_enabled' => false,
320 ];
321
322 $notificationKeys = apply_filters('fluentform/global_notification_active_types', [], $log->form_id);
323
324 unset($notificationKeys['user_registration_feeds']);
325 unset($notificationKeys['notifications']);
326
327 $notificationKeys = array_flip($notificationKeys);
328
329 $actionName = Helper::getLogInitiator($log->action);
330 if ($actionName) {
331 $actionName = str_replace(['Fluentform_integration_notify_', 'Fluentform/integration_notify_'], '', $actionName);
332
333 if (in_array($actionName, $notificationKeys)) {
334 $entryLog['integration_enabled'] = true;
335 }
336 }
337
338 if ($log->action) {
339 $entryLog['title'] = esc_html(Helper::getLogInitiator($log->action, $logType));
340 }
341
342 $entryLogs[] = $entryLog;
343 }
344 }
345
346 $entryLogs = apply_filters('fluentform/submission_logs', $entryLogs, $submissionId);
347
348 foreach ($entryLogs as &$entryLog) {
349 if (isset($entryLog['description'])) {
350 $entryLog['description'] = static::sanitizeLogHtml($entryLog['description']);
351 }
352 }
353 unset($entryLog);
354
355 return $entryLogs;
356 }
357
358 public static function sanitizeLogHtml($value)
359 {
360 if (!is_scalar($value)) {
361 return '';
362 }
363
364 return wp_kses((string) $value, [
365 'br' => [],
366 'b' => [],
367 'strong' => [],
368 'i' => [],
369 'em' => [],
370 'code' => [],
371 'p' => [],
372 'a' => [
373 'href' => [],
374 'title' => [],
375 'rel' => [],
376 ],
377 ]);
378 }
379
380 public function remove($attributes = [])
381 {
382 $ids = $this->normalizeLogIds($attributes);
383
384 if (!$ids) {
385 throw new ValidationException(
386 // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception message, not output
387 __('No selections found', 'fluentform')
388 );
389 }
390
391 $logType = Arr::get($attributes, 'type', Arr::get($attributes, 'log_type', 'logs'));
392 $entryId = intval(Arr::get($attributes, 'entry_id'));
393 $targetLogs = $this->getLogsForDeletion($ids, $logType);
394
395 if (!$targetLogs->count()) {
396 throw new ValidationException(
397 // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception message, not output
398 __('No selections found', 'fluentform')
399 );
400 }
401
402 $this->assertDeletePermission($targetLogs, $entryId);
403 $this->getDeleteQuery($logType)
404 ->whereIn('id', $targetLogs->pluck('id')->all())
405 ->delete();
406
407 return [
408 'message' => __('Selected log(s) successfully deleted', 'fluentform'),
409 ];
410 }
411
412 protected function normalizeLogIds($attributes)
413 {
414 $ids = Arr::get($attributes, 'log_ids', []);
415
416 if (!is_array($ids)) {
417 $ids = [];
418 }
419
420 $singleLogId = intval(Arr::get($attributes, 'log_id'));
421 if ($singleLogId) {
422 $ids[] = $singleLogId;
423 }
424
425 $ids = array_map('intval', $ids);
426 $ids = array_filter($ids);
427
428 return array_values(array_unique($ids));
429 }
430
431 protected function getLogsForDeletion($ids, $logType)
432 {
433 if ('logs' === $logType) {
434 return Log::select([
435 'id',
436 'parent_source_id as form_id',
437 'source_id as submission_id',
438 ])->whereIn('id', $ids)->get();
439 }
440
441 return Scheduler::select([
442 'id',
443 'form_id',
444 'origin_id as submission_id',
445 ])->whereIn('id', $ids)->get();
446 }
447
448 protected function getDeleteQuery($logType)
449 {
450 return 'logs' === $logType ? Log::query() : Scheduler::query();
451 }
452
453 protected function assertDeletePermission($targetLogs, $entryId = 0)
454 {
455 if ($entryId) {
456 foreach ($targetLogs as $targetLog) {
457 if (intval($targetLog->submission_id) !== $entryId) {
458 $this->throwDeletePermissionError();
459 }
460 }
461 }
462
463 $allowedForms = FormManagerService::getUserAllowedFormsScope();
464 if (false === $allowedForms) {
465 return;
466 }
467
468 foreach ($targetLogs as $targetLog) {
469 $formId = intval($targetLog->form_id);
470
471 if (!$formId || !in_array($formId, $allowedForms, true)) {
472 $this->throwDeletePermissionError();
473 }
474 }
475 }
476
477 protected function throwDeletePermissionError()
478 {
479 throw new ValidationException(
480 // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception message, not output
481 __('You do not have permission to delete the selected logs', 'fluentform')
482 );
483 }
484 }
485