PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.12
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.12
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 / Form / HistoryService.php

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

364 lines 12.2 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\Form;
4
5 use FluentForm\App\Models\FormMeta;
6 use FluentForm\App\Services\Parser\Form;
7 use FluentForm\Framework\Helpers\ArrayHelper as Arr;
8
9 class HistoryService
10 {
11 private $changes = [];
12 private $addedFields = [];
13 private $removedFields = [];
14
15 public function init($form, $postData)
16 {
17 $oldData = json_decode($form->form_fields, true);
18 $newData = json_decode($postData['form_fields'], true);
19
20 $this->checkAndStoreFormChanges($oldData, $newData, $form->id);
21 }
22
23
24 public function checkAndStoreFormChanges($oldData, $newData, $formId)
25 {
26
27 $this->compareFields($oldData['fields'], $newData['fields']);
28 $this->compareFields([$oldData['submitButton']], [$newData['submitButton']]);
29
30 if (Arr::isTrue($oldData, 'stepsWrapper.stepStart') && Arr::isTrue($newData, 'stepsWrapper.stepStart')) {
31 $newFirstSteps = Arr::get($newData, 'stepsWrapper.stepStart');
32 $newLastSteps = Arr::get($newData, 'stepsWrapper.stepEnd');
33 $oldFirstSteps = Arr::get($oldData, 'stepsWrapper.stepStart');
34 $oldLastSteps = Arr::get($oldData, 'stepsWrapper.stepEnd');
35 $this->compareFields([$oldFirstSteps, $oldLastSteps], [$newFirstSteps, $newLastSteps]);
36 }
37
38 if (!empty($this->changes)) {
39 $changeTitle = $this->generateChangeTitle();
40 $this->storeFormHistory($formId, $oldData, $changeTitle);
41
42 }
43
44 }
45
46
47 private function compareFields($oldFields, $newFields)
48 {
49 $oldFieldMap = $this->mapFieldsByUniqueKey($oldFields);
50 $newFieldMap = $this->mapFieldsByUniqueKey($newFields);
51
52
53 // Check for removed fields
54 foreach ($oldFieldMap as $key => $oldField) {
55 if (!isset($newFieldMap[$key])) {
56 $fieldLabel = $this->getFieldLabel($oldField);
57 $this->addChange('removed', $fieldLabel);
58 $this->removedFields[] = $key;
59 }
60 }
61
62 // Check for added fields
63 foreach ($newFieldMap as $key => $newField) {
64 if (!isset($oldFieldMap[$key])) {
65 $fieldLabel = $this->getFieldLabel($newField);
66 $this->addChange('added', $fieldLabel);
67 $this->addedFields[] = $key;
68 }
69 }
70 $oldKeys = array_keys($oldFieldMap);
71 $newKeys = array_keys($newFieldMap);
72
73 // Check if it's just a reordering
74 if (count($oldKeys) === count($newKeys) && array_diff($oldKeys, $newKeys) === array_diff($newKeys, $oldKeys)) {
75 if ($oldKeys !== $newKeys) {
76 $this->addChange('reordered', 'Fields');
77 }
78 // Compare field details even if order changed
79 foreach ($newFieldMap as $key => $newField) {
80 $this->compareFieldDetails($oldFieldMap[$key], $newField);
81 }
82 return;
83 }
84
85 // Compare existing fields
86 foreach ($newFieldMap as $key => $newField) {
87 if (isset($oldFieldMap[$key])) {
88 $this->compareFieldDetails($oldFieldMap[$key], $newField);
89 }
90 }
91
92
93 // Check for reordering only if no fields were added or removed
94 if (empty($this->addedFields) && empty($this->removedFields)) {
95 $this->checkFieldOrder($oldFields, $newFields);
96 }
97 }
98
99 private function mapFieldsByUniqueKey($fields)
100 {
101 $map = [];
102 foreach ($fields as $field) {
103 $map[$this->getFieldKey($field)] = $field;
104 }
105 return $map;
106 }
107
108 private function getFieldKey($field)
109 {
110 return $field['uniqElKey'] ?? $field['element'];
111 }
112
113 private function getFieldLabel($field)
114 {
115 return $field['settings']['label'] ?? $field['element'] ?? 'Unnamed Field';
116 }
117
118 private function compareFieldDetails($oldField, $newField)
119 {
120 if (in_array($this->getFieldKey($oldField), $this->addedFields) || in_array($this->getFieldKey($oldField), $this->removedFields)) {
121 return;
122 }
123 $fieldLabel = $this->getFieldLabel($newField);
124 $categories = ['settings', 'attributes', 'fields', 'columns'];
125
126 foreach ($categories as $category) {
127 $this->compareFieldProperties($oldField, $newField, $fieldLabel, $category);
128 }
129 }
130
131 private function compareFieldProperties($oldField, $newField, $fieldLabel, $category)
132 {
133 if (!isset($oldField[$category]) && !isset($newField[$category])) {
134 return;
135 }
136 if ($category === 'columns') {
137 if ($this->areColumnsChanged($oldField[$category] ?? [], $newField[$category] ?? [])) {
138 $this->addChange('modified', $fieldLabel, 'columns', 'Changed', null, $category);
139 }
140 return;
141 }
142 $this->compareArrayRecursively($oldField[$category] ?? [], $newField[$category] ?? [], $fieldLabel, $category);
143 }
144
145 private function compareArrayRecursively($oldValue, $newValue, $fieldLabel, $category, $parentKey = '')
146 {
147
148
149
150 foreach ($newValue as $key => $value) {
151 $currentKey = $parentKey ? "$parentKey.$key" : $key;
152
153 if (!isset($oldValue[$key]) || $oldValue[$key] !== $value) {
154 if (!is_array($value)) {
155 $this->addChange('modified', $fieldLabel, $currentKey, $value, $oldValue[$key] ?? null, $category);
156 } else {
157 $this->compareArrayRecursively($oldValue[$key] ?? [], $value, $fieldLabel, $category, $currentKey);
158 }
159 }
160 }
161 }
162
163 private function areColumnsChanged($oldColumns, $newColumns)
164 {
165 return $oldColumns !== $newColumns;
166 }
167
168 private function checkFieldOrder($oldFields, $newFields)
169 {
170 $oldOrder = array_map(function ($field) {
171 return $field['uniqElKey'];
172 }, $oldFields);
173
174 $newOrder = array_map(function ($field) {
175 return $field['uniqElKey'];
176 }, $newFields);
177
178 if ($oldOrder !== $newOrder) {
179 $this->addChange('reordered', 'Fields');
180 }
181 }
182
183 private function addChange($type, $label, $key = null, $newValue = null, $oldValue = null, $category = null)
184 {
185 $change = [
186 'type' => $type,
187 'label' => $label
188 ];
189
190 if ($key !== null) {
191 $change['key'] = $key;
192 }
193
194 if ($newValue !== null && !is_array($newValue)) {
195 $change['new_value'] = $newValue;
196 }
197
198 if ($oldValue !== null && !is_array($oldValue)) {
199 $change['old_value'] = $oldValue;
200 }
201
202 if ($label !== null && $key !== null && !is_array($newValue)) {
203 $oldValue = $oldValue ?: 'empty';
204 if(is_bool($oldValue)){
205 $oldValue = $oldValue ? 'Active' : 'inActive';
206 }
207 if(is_bool($newValue)){
208 $newValue = $newValue ? 'Active' : 'inActive';
209 }
210 $change['info'] = ucfirst($type) . " $label $key value from '$oldValue' to '$newValue'";
211 } elseif ($type !== null && $label !== null) {
212 $change['info'] = ucfirst($type) . " $label ";
213 }
214
215 if ($category !== null) {
216 $change['category'] = $category;
217 }
218
219 $this->changes[] = $change;
220 }
221
222 private function generateChangeTitle()
223 {
224 $addedChanges = array_filter($this->changes, function($change) {
225 return $change['type'] === 'added';
226 });
227 $removedChanges = array_filter($this->changes, function($change) {
228 return $change['type'] === 'removed';
229 });
230 $modifiedChanges = array_filter($this->changes, function($change) {
231 return $change['type'] === 'modified';
232 });
233 $reorderedChanges = array_filter($this->changes, function($change) {
234 return $change['type'] === 'reordered';
235 });
236
237 $addedCount = count($addedChanges);
238 $removedCount = count($removedChanges);
239 $reorderedCount = count($reorderedChanges);
240
241 // Get unique field labels that were modified
242 $modifiedFieldLabels = array_unique(array_map(function($change) {
243 return $change['label'];
244 }, $modifiedChanges));
245 $modifiedFieldCount = count($modifiedFieldLabels);
246
247 $changeDescriptions = [];
248
249 if ($addedCount > 0) {
250 if ($addedCount === 1) {
251 $fieldLabel = reset($addedChanges)['label'];
252 $changeDescriptions[] = "Added '$fieldLabel' field";
253 } else {
254 $changeDescriptions[] = "Added $addedCount fields";
255 }
256 }
257
258 if ($removedCount > 0) {
259 if ($removedCount === 1) {
260 $fieldLabel = reset($removedChanges)['label'];
261 $changeDescriptions[] = "Removed '$fieldLabel' field";
262 } else {
263 $changeDescriptions[] = "Removed $removedCount fields";
264 }
265 }
266
267 if ($modifiedFieldCount > 0) {
268 if ($modifiedFieldCount === 1) {
269 $fieldLabel = reset($modifiedFieldLabels);
270 $changeDescriptions[] = "Modified '$fieldLabel'";
271 } else {
272 $changeDescriptions[] = "Modified $modifiedFieldCount fields";
273 }
274 }
275
276 if ($reorderedCount > 0) {
277 $changeDescriptions[] = "Reordered fields";
278 }
279
280 if (count($changeDescriptions) > 1) {
281 return "Multiple changes";
282 } elseif (count($changeDescriptions) == 1) {
283 return $changeDescriptions[0];
284 }
285 return "Form structure changed";
286 }
287
288 private function storeFormHistory($formId, $oldData, $changeTitle)
289 {
290 // If there are no existing revisions for this form, mark this entry as the initial state
291 $existingHistoryCount = FormMeta::where([
292 'form_id' => $formId,
293 'meta_key' => 'revision',
294 ])->count();
295
296 if ($existingHistoryCount === 0) {
297 // Use a clear label for the first stored revision of the form
298 $changeTitle = __('Initial state', 'fluentform');
299 }
300 $historyEntry = [
301 'change_title' => $changeTitle,
302 'timestamp' => current_time('mysql'),
303 'old_data' => json_encode($oldData),
304 'changes' => json_encode($this->changes)
305 ];
306 $this->maybeCleanOldData($formId);
307 FormMeta::insert([
308 'form_id' => $formId,
309 'meta_key' => 'revision',
310 'value' => json_encode($historyEntry)
311 ]);
312 }
313
314 public static function get($formId)
315 {
316 $revisions = FormMeta::where('form_id', $formId)
317 ->where('meta_key', 'revision')
318 ->get();
319
320 $formattedRevisions = [];
321 foreach ($revisions as $rev) {
322 $data = json_decode($rev['value'], true);
323 $data['old_data'] = json_decode($data['old_data'], true);
324 $data['changes'] = json_decode($data['changes'], true);
325 $data['timestamp'] = human_time_diff(current_time('timestamp'),strtotime($data['timestamp'])). ' ago';
326 $formattedRevisions[] = $data;
327 }
328
329 return ['history' =>array_reverse( $formattedRevisions) ];
330 }
331
332 public function delete($formId)
333 {
334 FormMeta::remove($formId, 'revision');
335 }
336
337 /**
338 * Delete records keeping only last 10 per form
339 * @param $formId
340 * @return void
341 */
342 private function maybeCleanOldData($formId)
343 {
344 $historyCount = FormMeta::where([
345 'form_id' => $formId,
346 'meta_key' => 'revision',
347 ])->count();
348
349 if ($historyCount >= 10) {
350 $entriesToRemove = $historyCount - 9;
351 $oldestEntries = FormMeta::where([
352 'form_id' => $formId,
353 'meta_key' => 'revision',
354 ])
355 ->orderBy('id', 'asc')
356 ->limit($entriesToRemove)
357 ->pluck('id')
358 ->toArray();
359
360 FormMeta::whereIn('id', $oldestEntries)->delete();
361 }
362 }
363 }
364