File "FieldsData.js"

Full path: /home/satkhirabarta/public_html/wp-content/plugins/really-simple-ssl/settings/src/Settings/FieldsData.js
File size: 15.3 B (15.3 KB bytes)
MIME-type: text/x-java
Charset: utf-8

Download   Open   Edit   Advanced Editor &nnbsp; Back

import {create} from 'zustand';
import {produce} from 'immer';
import * as rsssl_api from "../utils/api";
import {__} from '@wordpress/i18n';
import {toast} from 'react-toastify';

const fetchFields = () => {
    return rsssl_api.getFields().then((response) => {
        let fields = response.fields;
        let progress = response.progress;
        let error = response.error;
        return {fields, progress, error};
    }).catch((error) => {
        console.error(error);
    });
}

const useFields = create(( set, get ) => ({
    fieldsLoaded: false,
    error:false,
    fields: [],
    changedFields:[],
    progress:[],
    nextButtonDisabled:false,
    overrideNextButtonDisabled:false,
    refreshTests:false,
    highLightField: '',
    setHighLightField: (highLightField) => {
        set({ highLightField });
    },

    setRefreshTests: (refreshTests) => set(state => ({ refreshTests })),
    handleNextButtonDisabled: (nextButtonDisabled) => {
        set({overrideNextButtonDisabled: nextButtonDisabled});
    },
    setChangedField: (id, value) => {
        set(
            produce((state) => {
                //remove current reference
                const existingFieldIndex = state.changedFields.findIndex(field => {
                    return field.id===id;
                });

                if (existingFieldIndex!==-1){
                    state.changedFields.splice(existingFieldIndex, 1);
                }

                //add again, with new value
                let field = {};
                field.id = id;
                field.value = value;
                state.changedFields.push(field);
            })
        )
    },
    showSavedSettingsNotice : (text , type = 'success') => {
        handleShowSavedSettingsNotice(text, type);
    },

    updateField: (id, value) => {
        set(
            produce((state) => {
                let index = state.fields.findIndex(fieldItem => fieldItem.id === id);
                if (index !== -1) {
                    state.fields[index].value = value;
                }
            })
        )
    },
    updateFieldAttribute: (id, attribute, value) => {
        set(
            produce((state) => {
                let index = state.fields.findIndex(fieldItem => fieldItem.id === id);
                if (index !== -1) {
                    state.fields[index][attribute] = value;
                }
            })
        )
    },
    updateSubField: (id, subItemId, value) => {
        set(
            produce((state) => {
                let index = state.fields.findIndex(fieldItem => fieldItem.id === id);
                let itemValue = state.fields[index].value;
                if (!Array.isArray(itemValue)) {
                    itemValue = [];
                }

                let subIndex = itemValue.findIndex(subItem => subItem.id === subItemId);
                if (subIndex !== -1) {
                    state.fields[index].updateItemId = subItemId;
                    state.fields[index].value[subIndex]['value'] = value;
                    state.fields[index].value = itemValue.map(item => {
                        const { deleteControl, valueControl, statusControl, ...rest } = item;
                        return rest;
                    });
                }
            })
        )
    },
    removeHelpNotice: (id) => {
        set(
            produce((state) => {
                const fieldIndex = state.fields.findIndex(field => {
                    return field.id===id;
                });
                state.fields[fieldIndex].help = false;
            })
        )
    },
    addHelpNotice : (id, label, text, title, url) => {
        get().removeHelpNotice(id);
        //create help object

        let help = {};
        help.label=label;
        help.text=text;
        if (url) help.url=url;
        if (title) help.title=title;
        set(
            produce((state) => {
                const fieldIndex = state.fields.findIndex(field => {
                    return field.id===id;
                });
                if (fieldIndex!==-1) {
                    state.fields[fieldIndex].help = help;
                }
            })
        )
    },
    fieldAlreadyEnabled: (id) => {
        let fieldIsChanged = get().changedFields.filter(field => field.id === id ).length>0;
        let fieldIsEnabled = get().getFieldValue(id);
        return !fieldIsChanged && fieldIsEnabled;
    },
    getFieldValue : (id) => {
        let fields = get().fields;
        let fieldItem = fields.filter(field => field.id === id )[0];
        if (fieldItem){
            return fieldItem.value;
        }
        return false;
    },
    getField : (id) => {
        let fields = get().fields;
        let fieldItem = fields.filter(field => field.id === id )[0];
        if (fieldItem){
            return fieldItem;
        }
        return false;
    },
    saveFields: async (skipRefreshTests, showSavedNotice, force = false) => {
        let refreshTests = typeof skipRefreshTests !== 'undefined' ? skipRefreshTests : true;
        showSavedNotice = typeof showSavedNotice !== 'undefined' ? showSavedNotice : true;
        let fields = get().fields;
        fields = fields.filter(field => field.data_target !== 'banner');
        let changedFields = get().changedFields;
        let saveFields = [];
        //data_target
        for (const field of fields) {
            let fieldIsIncluded = changedFields.filter(changedField => changedField.id === field.id).length > 0;
            //also check if there's no saved value yet for radio fields, by checking the never_saved attribute.
            //a radio or select field looks like it's completed, but won't save if it isn't changed.
            //this should not be the case for disabled fields, as these fields often are enabled server side because they're enabled outside Really Simple Security.
            let select_or_radio = field.type === 'select' || field.type === 'radio';
            if (fieldIsIncluded || (field.never_saved && !field.disabled && select_or_radio)) {
                saveFields.push(field);
            }
        }

        //if no fields were changed, do nothing.
        if (saveFields.length > 0 || force === true) {
            let response = rsssl_api.setFields(saveFields).then((response) => {
                return response;
            })

            if (showSavedNotice) {
                toast.promise(
                    response,
                    {
                        pending: __('Saving settings...', 'really-simple-ssl'),
                        success: __('Settings saved', 'really-simple-ssl'),
                        error: __('Something went wrong', 'really-simple-ssl'),
                    }
                );
            }
            await response.then((response) => {
                set(
                    produce((state) => {
                        state.changedFields = [];
                        state.fields = response.fields;
                        state.progress = response.progress;
                        state.refreshTests = refreshTests;
                    })
                )
            });
        }

        if (showSavedNotice && saveFields.length === 0) {
            //nothing to save. show instant success.
            toast.promise(
                Promise.resolve(),
                {
                    success: __('Settings saved', 'really-simple-ssl'),
                }
            );
        }

    },

    updateFieldsData: (selectedSubMenuItem) => {
        let fields = get().fields;
        fields = updateFieldsListWithConditions(fields);

        //only if selectedSubMenuItem is actually passed
        if (selectedSubMenuItem) {
            let nextButtonDisabled = isNextButtonDisabled(fields, selectedSubMenuItem);
            //if the button was set to disabled with the handleNextButtonDisabled function, we give that priority until it's released.
            if (get().overrideNextButtonDisabled) {
                nextButtonDisabled = get().overrideNextButtonDisabled;
            }
            set(
                produce((state) => {
                    state.nextButtonDisabled = nextButtonDisabled;
                })
            )
        }

        set(
            produce((state) => {
                state.fields = fields;
            })
        )
    },
    fetchFieldsData: async ( selectedSubMenuItem ) => {
        const { fields, progress, error }   = await fetchFields();
        let conditionallyEnabledFields = updateFieldsListWithConditions(fields);
        let selectedFields = conditionallyEnabledFields.filter(field => field.menu_id === selectedSubMenuItem);
        set({fieldsLoaded: true, fields:conditionallyEnabledFields, selectedFields:selectedFields, progress:progress, error: error });
    }
}));

export default useFields;

//check if all required fields have been enabled. If so, enable save/continue button
const isNextButtonDisabled = (fields, selectedMenuItem) => {
    let fieldsOnPage = [];
    //get all fields with group_id this.props.group_id
    for (const field of fields){
        if (field.menu_id === selectedMenuItem ){
            fieldsOnPage.push(field);
        }
    }

    let requiredFields = fieldsOnPage.filter(field => field.required && !field.conditionallyDisabled && (field.value.length==0 || !field.value) );
    return requiredFields.length > 0;
}

const updateFieldsListWithConditions = (fields) => {
    let newFields = [];
    if (!fields || !Array.isArray(fields)) {
        return [];
    }
    fields.forEach(function(field, i) {
        let enabled = !( field.hasOwnProperty('react_conditions') && !validateConditions(field.react_conditions, fields, field.id) );
        let previouslyEnabled = !field.conditionallyDisabled;
        //we want to update the changed fields if this field has just become visible. Otherwise the new field won't get saved.
        const newField = {...field};
        newField.conditionallyDisabled = !enabled;
        newField.visible = !(!enabled && (newField.type === 'letsencrypt' || newField.condition_action === 'hide'));

        newFields.push(newField);
        //if this is a learning mode field, do not add it to the changed fields list
        if ( !previouslyEnabled && newField.enabled && field.type!=='learningmode') {
            set().setChangedField(field.id, field.value);
        }
    });
    return newFields;
}

const handleShowSavedSettingsNotice = ( text, type ) => {
    if (typeof text === 'undefined') {
        text = __( 'Settings saved', 'really-simple-ssl' );
    }

    if (typeof type === 'undefined') {
        type = 'success';
    }

    if (type === 'error') {
        toast.error(text);
    }

    if (type === 'warning') {
        toast.warning(text);
    }

    if (type === 'info') {
        toast.info(text);
    }

    if (type === 'success') {
        toast.success(text);
    }
}

const validateConditions = (conditions, fields, fieldId) => {
    let relation = conditions[0].relation === 'OR' ? 'OR' : 'AND';
    let conditionApplies = relation==='AND';
    for (const key in conditions) {
        if ( conditions.hasOwnProperty(key) ) {
            let thisConditionApplies = relation==='AND';
            let subConditionsArray = conditions[key];
            if ( subConditionsArray.hasOwnProperty('relation') ) {
                thisConditionApplies = validateConditions(subConditionsArray, fields, fieldId)
            } else {
                for ( let conditionField in subConditionsArray ) {
                    let invert = conditionField.indexOf('!')===0;
                    if ( subConditionsArray.hasOwnProperty(conditionField) ) {
                        let conditionValue = subConditionsArray[conditionField];
                        conditionField = conditionField.replace('!','');
                        let conditionFields = fields.filter(field => field.id === conditionField);
                        if ( conditionFields.hasOwnProperty(0) ){
                            let field = conditionFields[0];
                            let actualValue = field.value;
                            if ( field.type==='text_checkbox' ) {
                                thisConditionApplies = actualValue.hasOwnProperty('show') && actualValue['show'] == conditionValue;//can be 1/true or 0/false
                            } else if ( field.type==='checkbox' ) {
                                thisConditionApplies = actualValue == conditionValue;//can be 1/true or 0/false
                            } else if ( field.type==='multicheckbox' ) {
                                //multicheckbox conditions
                                //loop through objects
                                thisConditionApplies = false;
                                let arrayValue = actualValue;
                                if ( arrayValue.length===0 ) {
                                    thisConditionApplies = false;
                                } else {
                                    for (const key of Object.keys(arrayValue)) {
                                        if ( !Array.isArray(conditionValue) ) conditionValue = [conditionValue];
                                        if ( conditionValue.includes(arrayValue[key])){
                                            thisConditionApplies = true;
                                            break;
                                        }
                                    }
                                }
                            } else if ( field.type==='radio' ) {
                                //as the regions field can be both radio and multicheckbox, an array is possible for a radio field
                                if ( Array.isArray(conditionValue) ) {
                                    thisConditionApplies = conditionValue.includes(actualValue);
                                } else {
                                    thisConditionApplies = conditionValue === actualValue;
                                }

                            } else {
                                if (conditionValue === true ) {
                                    thisConditionApplies = actualValue===1 || actualValue === "1" || actualValue === true;
                                } else if (conditionValue === false ) {
                                    thisConditionApplies = actualValue === 0 || actualValue === "0" || actualValue === false;
                                } else if (conditionValue.indexOf('EMPTY')!==-1) {
                                    thisConditionApplies = actualValue.length === 0;
                                } else {
                                    thisConditionApplies = String(actualValue).toLowerCase() === conditionValue.toLowerCase();
                                }
                            }
                        }
                    }
                    if ( invert ){
                        thisConditionApplies = !thisConditionApplies;
                    }
                    if ( relation === 'AND' ) {
                        conditionApplies = conditionApplies && thisConditionApplies;
                    } else {
                        conditionApplies = conditionApplies || thisConditionApplies;
                    }
                }

                if ( relation === 'AND' ) {
                    conditionApplies = conditionApplies && thisConditionApplies;
                } else {
                    conditionApplies = conditionApplies || thisConditionApplies;
                }
            }
        }
    }

    return conditionApplies ? 1 : 0;
}