File "HistoryService.php"

Full Path: /home/romayxjt/public_html/wp-content/plugins/fluentform/app/Services/Form/HistoryService.php
File size: 10.94 KB
MIME-type: text/x-php
Charset: utf-8

<?php

namespace FluentForm\App\Services\Form;

use FluentForm\App\Models\FormMeta;
use FluentForm\App\Services\Parser\Form;
use FluentForm\Framework\Helpers\ArrayHelper as Arr;

class HistoryService
{
    private $changes = [];
    private $addedFields = [];
    private $removedFields = [];
    
    public function init($form, $postData)
    {
        $oldData = json_decode($form->form_fields, true);
        $newData = json_decode($postData['form_fields'], true);
        
        $this->checkAndStoreFormChanges($oldData, $newData, $form->id);
    }
    
 
    public function checkAndStoreFormChanges($oldData, $newData, $formId)
    {
     
        $this->compareFields($oldData['fields'], $newData['fields']);
        $this->compareFields([$oldData['submitButton']], [$newData['submitButton']]);

        if (Arr::isTrue($oldData, 'stepsWrapper.stepStart') && Arr::isTrue($newData, 'stepsWrapper.stepStart')) {
            $newFirstSteps = Arr::get($newData, 'stepsWrapper.stepStart');
            $newLastSteps = Arr::get($newData, 'stepsWrapper.stepEnd');
            $oldFirstSteps = Arr::get($oldData, 'stepsWrapper.stepStart');
            $oldLastSteps = Arr::get($oldData, 'stepsWrapper.stepEnd');
            $this->compareFields([$oldFirstSteps, $oldLastSteps], [$newFirstSteps, $newLastSteps]);
        }

        if (!empty($this->changes)) {
            $changeTitle = $this->generateChangeTitle();
            $this->storeFormHistory($formId, $oldData, $changeTitle);
           
        }
        
    }
    
    
    private function compareFields($oldFields, $newFields)
    {
        $oldFieldMap = $this->mapFieldsByUniqueKey($oldFields);
        $newFieldMap = $this->mapFieldsByUniqueKey($newFields);
       
    
        // Check for removed fields
        foreach ($oldFieldMap as $key => $oldField) {
            if (!isset($newFieldMap[$key])) {
                $fieldLabel = $this->getFieldLabel($oldField);
                $this->addChange('removed', $fieldLabel);
                $this->removedFields[] = $key;
            }
        }
    
        // Check for added fields
        foreach ($newFieldMap as $key => $newField) {
            if (!isset($oldFieldMap[$key])) {
                $fieldLabel = $this->getFieldLabel($newField);
                $this->addChange('added', $fieldLabel);
                $this->addedFields[] = $key;
            }
        }
        $oldKeys = array_keys($oldFieldMap);
        $newKeys = array_keys($newFieldMap);
    
        // Check if it's just a reordering
        if (count($oldKeys) === count($newKeys) && array_diff($oldKeys, $newKeys) === array_diff($newKeys, $oldKeys)) {
            if ($oldKeys !== $newKeys) {
                $this->addChange('reordered', 'Fields');
            }
            // Compare field details even if order changed
            foreach ($newFieldMap as $key => $newField) {
                $this->compareFieldDetails($oldFieldMap[$key], $newField);
            }
            return;
        }
    
        // Compare existing fields
        foreach ($newFieldMap as $key => $newField) {
            if (isset($oldFieldMap[$key])) {
                $this->compareFieldDetails($oldFieldMap[$key], $newField);
            }
        }
    
    
        // Check for reordering only if no fields were added or removed
        if (empty($this->addedFields) && empty($this->removedFields)) {
            $this->checkFieldOrder($oldFields, $newFields);
        }
    }
    
    private function mapFieldsByUniqueKey($fields)
    {
        $map = [];
        foreach ($fields as $field) {
            $map[$this->getFieldKey($field)] = $field;
        }
        return $map;
    }

    private function getFieldKey($field)
    {
        return $field['uniqElKey'] ?? $field['element'];
    }
    
    private function getFieldLabel($field)
    {
        return $field['settings']['label'] ?? $field['element'] ?? 'Unnamed Field';
    }
    
    private function compareFieldDetails($oldField, $newField)
    {
        if (in_array($this->getFieldKey($oldField), $this->addedFields) || in_array($this->getFieldKey($oldField), $this->removedFields)) {
            return;
        }
        $fieldLabel = $this->getFieldLabel($newField);
        $categories = ['settings', 'attributes', 'fields', 'columns'];
    
        foreach ($categories as $category) {
            $this->compareFieldProperties($oldField, $newField, $fieldLabel, $category);
        }
    }
    
    private function compareFieldProperties($oldField, $newField, $fieldLabel, $category)
    {
        if (!isset($oldField[$category]) && !isset($newField[$category])) {
            return;
        }
        if ($category === 'columns') {
            if ($this->areColumnsChanged($oldField[$category] ?? [], $newField[$category] ?? [])) {
                $this->addChange('modified', $fieldLabel, 'columns', 'Changed', null, $category);
            }
            return;
        }
        $this->compareArrayRecursively($oldField[$category] ?? [], $newField[$category] ?? [], $fieldLabel, $category);
    }
    
    private function compareArrayRecursively($oldValue, $newValue, $fieldLabel, $category, $parentKey = '')
    {
       
    
    
        foreach ($newValue as $key => $value) {
            $currentKey = $parentKey ? "$parentKey.$key" : $key;
            
            if (!isset($oldValue[$key]) || $oldValue[$key] !== $value) {
                if (!is_array($value)) {
                    $this->addChange('modified', $fieldLabel, $currentKey, $value, $oldValue[$key] ?? null, $category);
                } else {
                    $this->compareArrayRecursively($oldValue[$key] ?? [], $value, $fieldLabel, $category, $currentKey);
                }
            }
        }
    }
    
    private function areColumnsChanged($oldColumns, $newColumns)
    {
        return $oldColumns !== $newColumns;
    }
    
    private function checkFieldOrder($oldFields, $newFields)
    {
        $oldOrder = array_map(function ($field) {
            return $field['uniqElKey'];
        }, $oldFields);
        
        $newOrder = array_map(function ($field) {
            return $field['uniqElKey'];
        }, $newFields);
        
        if ($oldOrder !== $newOrder) {
            $this->addChange('reordered', 'Fields');
        }
    }
    
    private function addChange($type, $label, $key = null, $newValue = null, $oldValue = null, $category = null)
    {
        $change = [
            'type'  => $type,
            'label' => $label
        ];
        
        if ($key !== null) {
            $change['key'] = $key;
        }
        
        if ($newValue !== null && !is_array($newValue)) {
            $change['new_value'] = $newValue;
        }
        
        if ($oldValue !== null && !is_array($oldValue)) {
            $change['old_value'] = $oldValue;
        }
        
        if ($label !== null && $key !== null && !is_array($newValue)) {
            $oldValue = $oldValue ?: 'empty';
            if(is_bool($oldValue)){
                $oldValue = $oldValue ? 'Active' : 'inActive';
            }
            if(is_bool($newValue)){
                $newValue = $newValue ? 'Active' : 'inActive';
            }
            $change['info'] = ucfirst($type) . " $label $key value from '$oldValue' to '$newValue'";
        } elseif ($type !== null && $label !== null) {
            $change['info'] = ucfirst($type) . " $label ";
        }
        
        if ($category !== null) {
            $change['category'] = $category;
        }
        
        $this->changes[] = $change;
    }
    
    private function generateChangeTitle()
    {
        $addedCount = count($this->addedFields);
        $removedCount = count($this->removedFields);
        $modifiedCount = count(array_filter($this->changes, function($change) {
            return $change['type'] === 'modified';
        }));
        $reorderedCount = count(array_filter($this->changes, function($change) {
            return $change['type'] === 'reordered';
        }));
    
        $changeDescriptions = [];
        if ($addedCount > 0) {
            $changeDescriptions[] = "Added " . ($addedCount > 1 ? "$addedCount fields" : "1 field");
        }
        if ($removedCount > 0) {
            $changeDescriptions[] = "Removed " . ($removedCount > 1 ? "$removedCount fields" : "1 field");
        }
        if ($modifiedCount > 0) {
            $changeDescriptions[] = "Modified " . ($modifiedCount > 1 ? "$modifiedCount attributes/settings" : "1 attribute/setting");
        }
        if ($reorderedCount > 0) {
            $changeDescriptions[] = "Reordered fields";
        }
        
        if (count($changeDescriptions) > 1) {
            return "Multiple changes";
        } elseif (count($changeDescriptions) == 1) {
            return $changeDescriptions[0];
        }
        return "Form structure changed";
    }
    
    private function storeFormHistory($formId, $oldData, $changeTitle)
    {
        $revisions = FormMeta::where('form_id', $formId)
            ->where('meta_key', 'revision')
            ->count();
        if ($revisions === 0) {
            $changeTitle = __('Initial state','fluentform');
        }
        $historyEntry = [
            'change_title' => $changeTitle,
            'timestamp'    => current_time('mysql'),
            'old_data'     => json_encode($oldData),
            'changes'      => json_encode($this->changes)
        ];
        $this->maybeCleanOldData($formId);
        FormMeta::insert([
            'form_id'  => $formId,
            'meta_key' => 'revision',
            'value'    => json_encode($historyEntry)
        ]);
    }
    
    public static function get($formId)
    {
        $revisions = FormMeta::where('form_id', $formId)
            ->where('meta_key', 'revision')
            ->get();
        
        $formattedRevisions = [];
        foreach ($revisions as $rev) {
            $data = json_decode($rev['value'], true);
            $data['old_data'] = json_decode($data['old_data'], true);
            $data['changes'] = json_decode($data['changes'], true);
            $data['timestamp'] = human_time_diff(current_time('timestamp'),strtotime($data['timestamp'])). ' ago';
            $formattedRevisions[] = $data;
        }
        
        return ['history' =>array_reverse( $formattedRevisions) ];
    }
    
    public function delete($formId)
    {
        FormMeta::remove($formId, 'revision');
    }
    
    /**
     * Delete records keeping only last 10 per form
     * @param $formId
     * @return void
     */
    private function maybeCleanOldData($formId)
    {
        $historyCount = FormMeta::where([
            'form_id'  => $formId,
            'meta_key' => 'revision',
        ])->count();
        
        if ($historyCount >= 10) {
            $entriesToRemove = $historyCount - 9;
            $oldestEntries = FormMeta::where([
                'form_id'  => $formId,
                'meta_key' => 'revision',
            ])
                ->orderBy('id', 'asc')
                ->limit($entriesToRemove)
                ->pluck('id');
            
            FormMeta::whereIn('id', $oldestEntries)->delete();
        }
    }
}