AlkantarClanX12

Your IP : 216.73.217.24


Current Path : /www/capitalgmcbuickregina_830/public/wp-content/plugins/wp-cerber/admin/
Upload File :
Current File : /www/capitalgmcbuickregina_830/public/wp-content/plugins/wp-cerber/admin/cerber-admin-settings.php

<?php
/*
	Copyright (C) 2015-26 CERBER TECH INC., https://wpcerber.com

    Licensed under the GNU GPL.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

/*

*========================================================================*
|                                                                        |
|	       ATTENTION!  Do not change or edit this file!                  |
|                                                                        |
*========================================================================*

*/

if ( ! defined( 'WPINC' ) || ! defined( 'CERBER_VER' ) ) {
	exit;
}

/**
 * settings_screen_id: logical identifier of a settings screen, used to select
 * sections, fields, defaults, validation, and saving behavior. The constant
 * value is also the name of the hidden form field that carries the ID in
 * submitted settings forms.
 *
 * @since 8.5.9.1
 */
const CRB_SETTINGS_SCREEN_ID_FIELD = 'cerber_settings_screen_id';

// id prefix for a setting's actual input element
const CRB_INPUT_ID_PREFIX = 'crb-input-';

// id prefix for a setting field's wrapping <div>
const CRB_WRAPPER_ID_PREFIX = 'crb-setting-field-';

// Namespace prefix for a settings screen's field names: cerber-{settings_screen_id}[{field}].
// Also doubles as the key into the defaults array returned by cerber_get_defaults().
const CRB_INPUT_NAME_PREFIX = 'cerber-';

/**
 * Top-level processor of plugin settings form submissions posted to plugin
 * admin pages.
 *
 * Local interactive submissions follow the POST-redirect-GET pattern: after
 * a settings form has been processed, the browser is redirected back to the
 * settings page the form was posted to. Queued admin messages survive the
 * redirect since they are stored in the database.
 *
 * Settings forms submitted on the main Cerber.Hub website for a managed
 * website are forwarded by nexus_show_remote_page() and processed on the
 * managed website by nexus_process_settings_form(), not by this function.
 *
 * @return void
 */
function crb_settings_processor() {

    if ( ! cerber_is_admin_page()
         || ! cerber_is_http_post() ) {
        return;
    }

    // POST-redirect-GET for settings forms posted to a plugin admin page

    if ( cerber_process_settings_form() ) {
        crb_safe_redirect( $_SERVER['REQUEST_URI'] );
        exit;
    }
}

function cerber_role_select( $name = 'cerber-roles', $selected = array(), $class = '', $multiple = '', $placeholder = '', $width = '75%' ) {

	if ( ! is_array( $selected ) ) {
		$selected = array( $selected );
	}
	if ( ! $placeholder ) {
		$placeholder = __( 'Select one or more roles', 'wp-cerber' );
	}
	$roles = wp_roles();
	$options = array();
	foreach ( $roles->get_names() as $key => $title ) {
		$s         = ( in_array( $key, $selected ) ) ? 'selected' : '';
		$options[] = '<option value="' . $key . '" ' . $s . '>' . $title . '</option>';
	}

	$m = ( $multiple ) ? 'multiple="multiple"' : '';

	// Setting width via class is not working
	$style = '';
	if ( $width ) {
		$style = 'width: ' . $width.';';
	}

	return ' <select style="' . $style . '" name="' . $name . '" class="crb-select2 ' . $class . '" ' . $m . ' data-placeholder="' . $placeholder . '" data-allow-clear="true">' . implode( "\n", $options ) . '</select>';
}

function cerber_time_select( $args, $settings ) {

	// Week
	$php_week = array(
		__( 'Sunday' ),
		__( 'Monday' ),
		__( 'Tuesday' ),
		__( 'Wednesday' ),
		__( 'Thursday' ),
		__( 'Friday' ),
		__( 'Saturday' ),
	);
	$field = $args['setting_id'].'-day';
	$selected = $settings[ $field ] ?? '';
	$ret = cerber_select( CRB_INPUT_NAME_PREFIX . $args['settings_screen_id'] . '[' . $field . ']', $php_week, $selected );
	/* translators: 'at' is a preposition of time, used in contexts like "at 11:00" or "at midnight". */
	$ret .= ' &nbsp; ' . _x( 'at', 'preposition of time like: at 11:00', 'wp-cerber' ) . ' &nbsp; ';

	// Hours
	$hours = array();
	for ( $i = 0; $i <= 23; $i ++ ) {
		$hours[] = str_pad( $i, 2, '0', STR_PAD_LEFT ) . ':00';
	}
	$field = $args['setting_id'] . '-time';
	$selected = $settings[ $field ] ?? '';
	$ret .= cerber_select( CRB_INPUT_NAME_PREFIX . $args['settings_screen_id'] . '[' . $field . ']', $hours, $selected );

	return $ret . crb_test_notify_link( array( 'type' => 'report', 'title' => __( 'Click to send now', 'wp-cerber' ) ) );
}

/**
 * Generates day and time picker.
 * Replacement for cerber_time_select()
 *
 * @param array $conf Setting field config
 * @param mixed $val Value of the saved setting
 *
 * @return string HTML code of the picker
 * @since 9.3.5
 */
function cerber_time_picker( $conf, $val ) {

	// Week
	if ( $conf['period'] == 'one_week' ) {
		$period = array(
			__( 'Sunday' ),
			__( 'Monday' ),
			__( 'Tuesday' ),
			__( 'Wednesday' ),
			__( 'Thursday' ),
			__( 'Friday' ),
			__( 'Saturday' ),
		);
	}
	elseif ( $conf['period'] == 'one_month' ) {
		$period = range( 0, 31 );
		unset( $period[0] );
    }
    else {
        return 'Not supported period specified';
    }

	$selected = $val['day'] ?? '';
	$picker = cerber_select( CRB_INPUT_NAME_PREFIX . $conf['settings_screen_id'] . '[' . $conf['setting_id'] . '][day]', $period, $selected );
	/* translators: 'at' is a preposition of time, used in contexts like "at 11:00" or "at midnight". */
	$picker .= ' &nbsp; ' . _x( 'at', 'preposition of time like: at 11:00', 'wp-cerber' ) . ' &nbsp; ';

	// Hours
	$hours = array();
	for ( $i = 0; $i <= 23; $i ++ ) {
		$hours[] = str_pad( $i, 2, '0', STR_PAD_LEFT ) . ':00';
	}
	$selected = $val['hours'] ?? '';
	$picker .= cerber_select( CRB_INPUT_NAME_PREFIX . $conf['settings_screen_id'] . '[' . $conf['setting_id'] . '][hours]', $hours, $selected );

	return $picker . crb_test_notify_link( array( 'type' => 'report', 'test_period' => $conf['period'], 'title' => __( 'Click to send now', 'wp-cerber' ) ) );
}


function crb_render_checkbox( $name, $value, $label = '', $id = '', $atts = '' ) {
	if ( ! $id ) {
		$id = CRB_INPUT_ID_PREFIX . $name;
	}

	return '<div style="display: table-cell;"><label class="crb-switch"><input class="screen-reader-text" type="checkbox" id="' . $id . '" name="' . $name . '" value="1" ' . checked( 1, $value, false ) . $atts . ' /><span class="crb-slider round"></span></label></div>
	<div style="display: table-cell;"><label for="' . $id . '">' . $label . '</label></div>';
}

function cerber_digi_field( $name, $value = '', $class = '', $args = array() ) {
	return cerber_txt_field( $name, $value, '', $args['size'] ?? '3', $args['maxln'] ?? '3', '\d+', $class . ' crb-input-digits' );
}

function cerber_txt_field( $name, $value = '', $id = '', $size = '', $maxlength = '', $pattern = '', $class = '' ) {
	$atts = '';

	$atts .= $id ? ' id="' . $id . '" ' : '';
	$atts .= $class ? ' class="' . $class . '" ' : '';
	$atts .= $size ? ' size="' . $size . '" ' : '';
	$atts .= $maxlength ? ' maxlength="' . $maxlength . '" ' : '';
	$atts .= $pattern ? ' pattern="' . $pattern . '" ' : '';

	return '<input type="text" name="' . $name . '" value="' . $value . '" ' . $atts . ' />';
}

function cerber_nonce_field( $action = 'control', $echo = false ) {
	$sf = '';
	if ( nexus_is_valid_request() ) {
		$sf = '<input type="hidden" name="cerber_nexus_seal" value="' . nexus_request_data()->seal . '">';
	}
	$nf = wp_nonce_field( $action, 'cerber_nonce', false, false );
	if ( ! $echo ) {
		return $nf . $sf;
	}

	echo $nf . $sf;
}

/**
 * Generates a WordPress admin submit button, optionally disabled.
 *
 * @param string $text  Optional. Button text. Defaults to "Save Changes".
 * @param bool   $echo  Optional. Whether to echo the HTML. Defaults to false.
 *
 * @return string The button HTML. Safe in any context.
 */
function crb_admin_submit_button( string $text = '', bool $echo = false ) {

	$text = $text ?: __( 'Save Changes', 'wp-cerber' );

	$disabled    = '';
	$hint_text = '';

	if ( nexus_is_valid_request()
         && ! nexus_is_granted( 'submit' ) ) {
		$disabled    = 'disabled="disabled"';
		$hint_text = ' not available in the read-only mode';
	}

	$html = '<p class="submit"><input ' . $disabled . ' type="submit" name="submit" id="submit" class="button button-primary" value="' . esc_attr( $text ) . '" /> ' . esc_html( $hint_text ) . '</p>';

	if ( $echo ) {
		echo $html;
	}

	return $html;
}

/**
 * Legacy per-screen sanitizer for the Main Settings screen. Pending migration
 * to per-field callbacks in the settings config.
 *
 * @param array<string,mixed> $new New setting values to sanitize
 * @param array<string,mixed> $old Currently stored setting values
 *
 * @return array<string,mixed> Sanitized setting values
 */
function crb_pre_update_main( $new, $old ) {
	if ( isset( $new['boot-mode'] ) ) {
		$ret = cerber_set_boot_mode( $new['boot-mode'] );

		if ( crb_is_wp_error( $ret ) ) {
			crb_admin_error_notice( $ret );
			cerber_admin_notice( __( 'Plugin initialization mode has not been changed', 'wp-cerber' ) );
			$new['boot-mode'] = $old['boot-mode'];
		}
        elseif ( $ret == 2 ) {
			cerber_admin_message( __( 'A must-use WP Cerber boot file has been copied to the WordPress must-use directory', 'wp-cerber' ) );
		}
	}

	$new['attempts'] = absint( $new['attempts'] );
	$new['period']   = absint( $new['period'] );
	$new['lockout']  = absint( $new['lockout'] );

	$new['agperiod'] = absint( $new['agperiod'] );
	$new['aglocks']  = absint( $new['aglocks'] );
	$new['aglast']   = absint( $new['aglast'] );

	if ( cerber_is_permalink_enabled() ) {
		$new['loginpath'] = urlencode( str_replace( '/', '', $new['loginpath'] ) );
		$new['loginpath'] = sanitize_text_field( $new['loginpath'] );

		if ( $new['loginpath'] ) {
			if ( $new['loginpath'] == 'wp-admin'
			     || preg_match( '/[#|.!?:\s]/', $new['loginpath'] ) ) {
                crb_add_admin_error( __( 'The entered value is not allowed for the Custom login URL field. Please specify a different one.', 'wp-cerber' ) );
				$new['loginpath'] = $old['loginpath'];
			}
			elseif ( $new['loginpath'] != $old['loginpath'] ) {
				$href    = cerber_get_site_url() . '/' . $new['loginpath'] . '/';
				$url     = urldecode( $href );
				$msg     = array();
				$msg_e   = array();
				$msg[]   = __( 'Attention! You have changed the login URL! The new login URL is', 'wp-cerber' ) . ': <a href="' . $href . '">' . $url . '</a>';
				$msg_e[] = __( 'Attention! You have changed the login URL! The new login URL is', 'wp-cerber' ) . ': ' . $url;
				$msg[]   = __( 'If you use a caching plugin, you have to add your new login URL to the list of pages not to cache.', 'wp-cerber' );
				$msg_e[] = __( 'If you use a caching plugin, you have to add your new login URL to the list of pages not to cache.', 'wp-cerber' );
				cerber_admin_notice( $msg );
                CRB_Messaging::send( 'newlurl', array( 'text' => $msg_e ) );
			}
		}
	}
	else {
		$new['loginpath'] = '';
		$new['loginnowp'] = 0;
	}

	if ( $new['loginnowp'] && empty( $new['loginpath'] ) && ! class_exists( 'WooCommerce' ) ) {
		cerber_admin_notice( array(
			'<b>' . __( 'Heads up!' ) . '</b>',
			__( 'You have disabled the default login page. Ensure that you have configured an alternative login page. Otherwise, you will not be able to log in.', 'wp-cerber' )
		) );
	}

	$new['ciduration'] = absint( $new['ciduration'] );
	$new['cilimit']    = absint( $new['cilimit'] );
	$new['cilimit']    = $new['cilimit'] == 0 ? '' : $new['cilimit'];
	$new['ciperiod']   = absint( $new['ciperiod'] );
	$new['ciperiod']   = $new['ciperiod'] == 0 ? '' : $new['ciperiod'];
	if ( ! $new['cilimit'] ) {
		$new['ciperiod'] = '';
	}
	if ( ! $new['ciperiod'] ) {
		$new['cilimit'] = '';
	}

	return $new;
}

/**
 * Legacy per-screen sanitizer for the Anti-spam settings screen. Pending
 * migration to per-field callbacks in the settings config.
 *
 * @param array<string,mixed> $new New setting values to sanitize
 * @param array<string,mixed> $old Currently stored setting values
 *
 * @return array<string,mixed> Sanitized setting values
 */
function crb_pre_update_antispam( $new, $old ) {

	if ( empty( $new['botsany'] ) && empty( $new['botscomm'] ) && empty( $new['botsreg'] ) ) {
		update_site_option( 'cerber-antibot', '' );
	}

	$warn = false;

	if ( ! empty( $new['botsany'] )
         && crb_array_get( $new, 'botsany' ) != crb_array_get( $old, 'botsany' ) ) {
		$warn = true;
	}

	if ( ! empty( $new['botscomm'] )
         && crb_array_get( $new, 'botscomm' ) != crb_array_get( $old, 'botscomm' ) ) {
		$warn = true;
	}

	if ( ! empty( $new['customcomm'] ) ) {
		if ( ! crb_get_compiled( 'custom_comm_slug' ) ) {
			crb_update_compiled( 'custom_comm_slug', crb_random_string( 20, 30 ) );
			crb_update_compiled( 'custom_comm_mark', crb_random_string( 20, 30 ) );
			$warn = true;
		}
	}
	else {
		if ( crb_get_compiled( 'custom_comm_slug' ) ) {
			crb_update_compiled( 'custom_comm_slug', '' );
			$warn = true;
		}
	}

	if ( $warn ) {
		cerber_admin_notice( array(
			'<b>' . __( 'Important note if you have a caching plugin in place', 'wp-cerber' ) . '</b>',
			__( 'To avoid false positives and get better anti-spam performance, please clear the plugin cache.', 'wp-cerber' )
		) );
	}

	return $new;
}
/**
 * Legacy per-screen sanitizer for the reCAPTCHA settings screen. Pending
 * migration to per-field callbacks in the settings config.
 *
 * @param array<string,mixed> $new New setting values to sanitize
 * @param array<string,mixed> $old Currently stored setting values
 *
 * @return array<string,mixed> Sanitized setting values
 */
function crb_pre_update_recaptcha( $new, $old ) {

	// Check ability to make external HTTP requests
	if ( ! empty( $new['sitekey'] ) && ! empty( $new['secretkey'] ) ) {
		if ( ( ! $goo = get_wp_cerber()->reCaptchaRequest( '1' ) )
		     || empty( $goo['success'] ) ) {
            crb_add_admin_error( cerber_get_labels( 'status', 534 ) );
		}
	}

	$new['recaptcha-period'] = absint( $new['recaptcha-period'] );
	$new['recaptcha-number'] = absint( $new['recaptcha-number'] );
	$new['recaptcha-within'] = absint( $new['recaptcha-within'] );

	return $new;
}
/**
 * Legacy per-screen sanitizer for the Notifications settings screen. Pending
 * migration to per-field callbacks in the settings config.
 *
 * @param array<string,mixed> $new New setting values to sanitize
 * @param array<string,mixed> $old Currently stored setting values
 *
 * @return array<string,mixed> Sanitized setting values
 */
function crb_pre_update_notifications( $new, $old ) {

	$new['email'] = crb_email_purify( $new['email'] );
	$new['email-report'] = crb_email_purify( $new['email-report'] );
	$new['email_report_one_month'] = crb_email_purify( $new['email_report_one_month'] );

	$new['emailrate'] = absint( $new['emailrate'] );

	// When we install a new token, we set proper default value for the device setting

	if ( $new['pbtoken'] != $old['pbtoken'] ) {

		if ( ! $new['pbtoken'] ) {
			$new['pbdevice'] = '';
		}
		else {

			$list = cerber_pb_get_devices( $new['pbtoken'] );

			if ( is_array( $list ) && ! empty( $list ) ) {
				$new['pbdevice'] = 'all';
				cerber_admin_message( __( 'The Pushbullet token is valid. You can select devices to receive notifications.', 'wp-cerber' ) );
			}
			else {
				$new['pbdevice'] = '';
				if ( crb_is_wp_error( $list ) ) {
                    crb_add_admin_error( crb_escape_html( $list->get_error_message() ) );
				}
			}
		}
	}

	return $new;
}

/**
 * Legacy per-screen sanitizer for the Scanner Schedule settings screen.
 * Pending migration to per-field callbacks in the settings config.
 *
 * @param array<string,mixed> $new New setting values to sanitize
 * @param array<string,mixed> $old Currently stored setting values
 *
 * @return array<string,mixed> Sanitized setting values
 */
function crb_pre_update_schedule( $new, $old ) {
	$new['scan_aquick'] = absint( $new['scan_aquick'] );
	$new['scan_afull-enabled'] = ( empty( $new['scan_afull-enabled'] ) ) ? 0 : 1;

	$sec = cerber_sec_from_time( $new['scan_afull'] );
	if ( ! $sec || ! ( $sec >= 0 && $sec <= 86400 ) ) {
		$new['scan_afull'] = '01:00';
	}

	$new['email-scan'] = crb_email_purify( $new['email-scan'] );

	if ( lab_lab() ) {
		if ( cerber_cloud_sync( $new ) ) {
			cerber_admin_message( __( 'The scanner schedule has been updated', 'wp-cerber' ) );
		}
		else {
			cerber_admin_message( __( 'Unable to update the scanner schedule', 'wp-cerber' ) );
		}
	}

	return $new;
}

function cerber_normal_dirs( $list = array() ) {
	if ( ! is_array( $list ) ) {
		$list = cerber_text2array( $list, "\n" );
	}
	$ready = array();

	foreach ( $list as $item ) {
		$item = rtrim( cerber_normal_path( $item ), '/\\' ) . DIRECTORY_SEPARATOR;
		if ( ! @is_dir( $item ) ) {
			$dir = cerber_get_abspath() . ltrim( $item, DIRECTORY_SEPARATOR );
			if ( ! @is_dir( $dir ) ) {
				cerber_admin_notice( 'Directory does not exist: ' . crb_escape_html( $item ) );
				continue;
			}
			$item = $dir;
		}
		$ready[] = $item;
	}

	return $ready;
}

/**
 * @param string $emails
 *
 * @return array
 * @since 9.3.5
 */
function crb_email_purify( $emails ) {

	$list = array();

	if ( $emails = cerber_text2array( $emails, ',' ) ) {

		foreach ( $emails as $item ) {
			if ( is_email( $item ) ) {
				$list[] = $item;
			}
			else {
				cerber_admin_notice( __( '<strong>ERROR</strong>: please enter a valid email address.' ) );
			}
		}
	}

    return $list;
}

/**
 * Process WP Cerber's settings forms
 *
 * @return bool True if a settings form has been processed (with or without
 * validation errors on particular values), false if the request is not a
 * processable settings form submission or the security checks failed.
 *
 * @since 8.6
 *
 */
function cerber_process_settings_form(): bool {

	if ( ! cerber_is_http_post()
         || nexus_is_remote_context()
	     || ! $settings_screen_id = crb_get_post_fields( CRB_SETTINGS_SCREEN_ID_FIELD ) ) {
		return false;
	}

	if ( ! nexus_is_valid_request() ) {

        // Standard local context: we check permissions and the form nonce

		if ( ! cerber_user_can_manage() ) {
			return false;
		}

        // Abort processing if the plugin form nonce is not valid or missing.
		// See cerber_nonce_field() in cerber_show_settings_form()
		if ( ! wp_verify_nonce( (string) crb_get_post_fields( 'cerber_nonce', '' ), 'control' ) ) {
			cerber_admin_notice( __( 'The settings were not saved because the form has expired. Please try again.', 'wp-cerber' ) );

			return false;
		}
	}

    // All checks are passed, now we are processing the submitted form fields

    $post_fields = crb_get_post_fields( CRB_INPUT_NAME_PREFIX . $settings_screen_id, array() );
	crb_trim_deep( $post_fields );
	$post_fields = stripslashes_deep( $post_fields );

	// The Cerber.Hub managed-site edit form carries client website data
	// stored on the main website, not plugin settings

	if ( defined( 'CRB_NX_MANAGED' ) && $settings_screen_id == CRB_NX_MANAGED ) {
		return nexus_save_client_data_form( $post_fields );
	}

    if ( ! defined( '_CRB_PROCESS_SETTING_FORM' ) ) {
        define( '_CRB_PROCESS_SETTING_FORM', true );
    }

    $msg = '';

	if ( 'addon-settings' == crb_get_post_fields( 'page_type' ) ) {
		if ( CRB_Addons::update_settings( $post_fields, crb_get_post_fields( 'addon_id' ) ) ) {
			$msg = __( 'Add-on settings updated', 'wp-cerber' );
		}
	}
	else {
		// Fields extracted from the setting fields registry
		$fields_one = array_fill_keys( array_keys( CRB_Settings_Registry::get_setting_fields( $settings_screen_id ) ), '' );

        // Fields extracted from the default WP Cerber settings
		$defs = cerber_get_defaults();
		$fields_two = array_fill_keys( array_keys( $defs[ CRB_INPUT_NAME_PREFIX . $settings_screen_id ] ), '' );

        // For the best coverage, we combine them, thereby ensuring that we save the complete configuration.
		$fields = array_merge( $fields_one, $fields_two );
		$update_settings = array_merge( $fields, $post_fields );

		if ( cerber_settings_update( $update_settings, $settings_screen_id ) ) {
			$msg = __( 'Plugin settings updated', 'wp-cerber' );
		}
	}

	if ( $msg ) {
		cerber_admin_message( $msg, true );

		// A non-empty message means the settings were successfully written to the DB

		CRB_Settings_Backup::sync( CRB_Settings_Backup::CONTEXT_SETTINGS_UPDATE );
	}

	return true;
}

/**
 * Fully deletes specified settings stored in the DB.
 *
 * @param string[] $delete_list Setting IDs to delete
 *
 * @return string[] Deleted setting IDs
 *
 * @since 9.6.7.5
 */
function cerber_settings_delete( array $delete_list ) {

	if ( ( ! $all_settings = get_site_option( CERBER_CONFIG ) )
	     || ! is_array( $all_settings ) ) {
		return array();
	}

	$deleted = array();
	$protected = CRB_Settings_Registry::get_setting_fields();
	$protected [ CRB_ADDON_STS ] = 1;
	$protected [ CRB_ROLE_STS ] = 1;

	foreach ( $delete_list as $id ) {
		if ( ! isset( $all_settings[ $id ] )
		     || isset( $protected[ $id ] ) ) {
			continue;
		}

		unset( $all_settings[ $id ] );
		$deleted[] = $id;
	}

	if ( $deleted
	     && update_site_option( CERBER_CONFIG, $all_settings ) ) {
		return $deleted;
	}

	return array();
}

/**
 * Updates WP Cerber's settings in the database in the new format
 *
 * @param array $new_settings Array of settings (id => value) to update
 * @param string $settings_screen_id ID of the settings screen the settings belong to
 *
 * @return bool
 *
 * @since 9.3.4
 */
function cerber_settings_update( array $new_settings, string $settings_screen_id = '' ) {

	if ( ( ! $stored_settings = get_site_option( CERBER_CONFIG ) )
	     || ! is_array( $stored_settings ) ) {
		$stored_settings = array();
	}

    // Ensure that all settings keys are in place in $stored_settings

	$all_settings = array_fill_keys( array_keys( crb_get_default_values() ), '' );
	$stored_settings = array_merge( $all_settings, $stored_settings );

    // Preserve PRO settings if the license is expired

	if ( ! lab_lab()
	     && $preserve = array_intersect_key( $new_settings, CRB_PRO_SETTINGS ) ) {
		$new_settings = array_merge( $new_settings, array_intersect_key( $stored_settings, $preserve ) );
	}

	// Pre-process in the old way @before 9.3.4

	$new_settings = cerber_settings_pre_update( $stored_settings, $new_settings, $settings_screen_id );

    // Merge existing settings and new ones

	$save = array_merge( $stored_settings, $new_settings );

    // Pre-process in the new way @since 9.3.4

	$defs = CRB_Settings_Registry::get_setting_fields();
	$defs = array_intersect_key( $defs, $new_settings );

	foreach ( $defs as $id => $config ) {

        // Validate the setting value if a validation callback is defined

        if ( ( $validate_cb = crb_array_get( $config, 'validate_value' ) )
             && is_callable( $validate_cb ) ) {

            $validation_check = call_user_func_array( $validate_cb, array( $save[ $id ], $save ) );

            if ( crb_is_wp_error( $validation_check )
                 && defined( '_CRB_PROCESS_SETTING_FORM' ) ) {

                $error_messages = $validation_check->get_error_messages();

                /* translators: %s is a setting title. */
                array_unshift( $error_messages, sprintf( __( 'Please check the values in %s. See the errors below.', 'wp-cerber' ), '<strong>' . esc_html( $config['title'] ) . '</strong>' ) );

                cerber_admin_notice( $error_messages );
            }
        }

        // Alter the setting value if a pre-update callback is defined

        if ( ( $pre_update = crb_array_get( $config, 'pre_update' ) )
		     && is_callable( $pre_update ) ) {

	        $save[ $id ] = call_user_func_array( $pre_update, array( $save[ $id ], $stored_settings[ $id ], &$save, $stored_settings, $new_settings ) );
		}
	}

    // Check if any changes in the settings

	$changed = array();

	foreach ( $save as $key => $val ) {

		if ( ! isset( $stored_settings[ $key ] ) ) {
			continue;
		}

		$old = $stored_settings[ $key ];

		// We compare only non-empty values, for WP Cerber settings '', 0, false, and empty array are equal

		if ( empty( $old ) && empty( $val ) ) {

			continue;
		}

		if ( ! is_array( $val ) && ! is_array( $old ) ) {
			if ( (string) $old != (string) $val ) {
				$changed[] = $key;
			}
		}
        elseif ( is_array( $val ) && is_array( $old ) ) {
			if ( json_encode( $old ) !== json_encode( $val ) ) {
				$changed[] = $key;
			}
		}
		else {
			$changed[] = $key;
		}

	}

    $is_equal = empty( $changed );
    $result = false;

	if ( ! $is_equal ) {

		// Save settings to the DB

		if ( ! $result = update_site_option( CERBER_CONFIG, $save ) ) {
			cerber_admin_notice( 'Critical I/O error #77 occurred while updating WP Cerber settings.' );
		}
	}

	$diag_log = array();

	if ( $result ) {

		crb_purge_settings_cache(); // Delete outdated values from the static cache

		// The stored configuration has been rewritten with a valid value: the corrupted
		// settings issue registered by crb_get_settings() is resolved now

		CRB_Issues::delete_item( 'corrupted_settings', 'settings' );

		// The administrator has reviewed and saved the settings: the notice about
		// the automatic restoration from a backup copy is resolved as well

		CRB_Issues::delete_item( 'settings_restored', 'settings' );

		$back = array();

		foreach ( $defs as $id => $config ) {

            if ( ! in_array( $id, $changed ) ) {
				continue;
			}

			if ( $msg = crb_array_get( $config, 'diag_log' ) ) {
				$diag_log[ $id ] = $msg;
			}

            // Post-processing if any needed

			if ( ( $on_change_cb = crb_array_get( $config, 'on_change' ) )
			     && is_callable( $on_change_cb ) ) {

				call_user_func( $on_change_cb, $save[ $id ], $save, $stored_settings );
			}

			// Rolling back value if a rollback returns true

			if ( ( $rollback_cb = crb_array_get( $config, 'rollback' ) )
			     && is_callable( $rollback_cb ) ) {

				if ( call_user_func( $rollback_cb, $save[ $id ], $save, $stored_settings ) ) {
					$back[ $id ] = crb_array_get( $stored_settings, $id, '' );
				}
			}
		}

		if ( $back ) {

			// Rolling back values

			$save = array_merge( $save, $back );

			if ( ! update_site_option( CERBER_CONFIG, $save ) ) {
				cerber_admin_notice( 'Critical I/O error #78 occurred while updating WP Cerber settings.' );
			}
			else {
				$changed = array_flip( array_diff_key( array_flip( $changed ), $back ) );
			}
		}

	}

	$update_details = array(
		'settings_screen_id' => $settings_screen_id,
		'group'              => $settings_screen_id, // Legacy alias for add-on handlers.
		'equal'              => $is_equal,
		'result'             => $result,
		'changed'            => $changed,
		'new_values'         => $new_settings,
		'old_values'         => $stored_settings,
	);

	if ( $result && $changed && $diag_log ) {
		crb_journaling( $update_details, $diag_log );
	}

	crb_event_handler( 'update_settings', $update_details );

    return $result;
}

/**
 * Sanitizing and processing setting values before saving to the DB.
 * Runs the legacy per-screen sanitizers pending their migration to
 * per-field callbacks in the settings config.
 *
 * @param array<string,mixed> $stored_settings Currently stored setting values
 * @param array<string,mixed> $new_settings New setting values to sanitize
 * @param string $settings_screen_id ID of the settings screen being saved, 'all' to run all per-screen sanitizers
 *
 * @return array<string,mixed> Sanitized setting values
 *
 * @since 9.3.4
 */
function cerber_settings_pre_update( $stored_settings, $new_settings, $settings_screen_id = 'all' ) {

	if ( $settings_screen_id === 'all' ) {
		$new_settings = crb_pre_update_main( $new_settings, $stored_settings );
		$new_settings = crb_pre_update_antispam( $new_settings, $stored_settings );
		$new_settings = crb_pre_update_recaptcha( $new_settings, $stored_settings );
		$new_settings = crb_pre_update_notifications( $new_settings, $stored_settings );
		$new_settings = crb_pre_update_schedule( $new_settings, $stored_settings );
	}
    elseif ( $settings_screen_id ) {

		// Screen specific sanitizing and processing

		$defaults_group_key = CRB_INPUT_NAME_PREFIX . $settings_screen_id;

		switch ( $defaults_group_key ) {
			case CERBER_OPT:
				$new_settings = crb_pre_update_main( $new_settings, $stored_settings );
				break;
			case CERBER_OPT_A:
				$new_settings = crb_pre_update_antispam( $new_settings, $stored_settings );
				break;
			case CERBER_OPT_C:
				$new_settings = crb_pre_update_recaptcha( $new_settings, $stored_settings );
				break;
			case CERBER_OPT_N:
				$new_settings = crb_pre_update_notifications( $new_settings, $stored_settings );
				break;
			case CERBER_OPT_E:
				$new_settings = crb_pre_update_schedule( $new_settings, $stored_settings );
				break;
		}
	}

    // Global sanitizing and processing

	return cerber_grand_sanitizing( $new_settings );
}

/**
 *  Global sanitizing, processing, formatting
 *
 * @param array $settings
 *
 * @return array
 */
function cerber_grand_sanitizing( $settings ) {

	$pre_sanitize = $settings;
	$set_minimum = array();
	$defs = CRB_Settings_Registry::get_setting_fields();

	// Parsing settings, applying formatting, etc.

	foreach ( $settings as $setting_id => &$setting_val ) {

		if ( ! $conf = crb_array_get( $defs, $setting_id ) ) {
			continue;
		}

		if ( $enabler = $conf['enabler'] ?? '' ) {
			if ( crb_check_enabler( $conf, $settings[ $enabler[0] ] ) ) {
				continue;
			}
		}

		$callback = crb_array_get( $conf, 'apply' );
		$regex = crb_array_get( $conf, 'regex_filter' ); // Filtering out not allowed chars
		$validate = crb_array_get( $conf, 'validate' );

		if ( isset( $conf['list'] ) ) {
			// Process the values
			$setting_val = cerber_text2array( $setting_val, $conf['delimiter'], $callback, $regex );

			// Remove not allowed values
			global $_deny;
			if ( $_deny = crb_array_get( $conf, 'deny_filter' ) ) {
				$setting_val = array_filter( $setting_val, function ( $e ) {
					global $_deny;

					return ! in_array( $e, $_deny );
				} );
			}
		}
		else {
			// Process the value
			if ( $callback && is_callable( $callback ) ) {
				$setting_val = call_user_func( $callback, $setting_val );
			}
			if ( $regex ) {
				$setting_val = mb_ereg_replace( $regex, '', $setting_val );
			}

            // Validating the value
			if ( $validate ) {

				$field_name = $conf['title'] ?? $conf['label'] ?? 'Unknown field';
				$error_msg = '';

				if ( ! empty( $validate['required'] )
				     && ! $setting_val ) {
					/* translators: %s is the field name. */
					$error_msg = sprintf( __( 'Field %s may not be empty', 'wp-cerber' ), '<b>' . $field_name . '</b>' );
				}
                elseif ( $setting_val
				         && ( $sat = $validate['satisfy'] ?? false )
				         && is_callable( $sat )
				         && ! call_user_func( $sat, $setting_val ) ) {
					/* translators: %s is the field name. */
					$error_msg = sprintf( __( 'Field %s contains an invalid value', 'wp-cerber' ), '<b>' . $field_name . '</b>' );
				}

				if ( $error_msg ) {
                    crb_add_admin_error( $error_msg );
				}
			}

			// Limits for numeric values

			if ( isset( $conf['min_val'] )
			     && ! ( ( $conf['empty_val'] ?? false ) && $setting_val === '' ) // Is empty values allowed
			     && $setting_val < $conf['min_val'] ) {
				$setting_val = $conf['min_val'];
				$set_minimum[] = $setting_id;
			}
		}
	}

	crb_sanitize_deep( $settings );

	// Warn the user if we have altered some values the user has entered

	if ( $set_minimum ) {
		$list = array_intersect_key( $defs, array_flip( $set_minimum ) );
		$msg = '<div style="font-weight: 600;"><p>' . implode( '</p><p>', array_column( $list, 'title' ) ) . '</p></div>';
		cerber_admin_notice( __( 'The following settings have been set to their minimum acceptable values.', 'wp-cerber' ) . $msg );
	}

	$changed = array();

	foreach ( $pre_sanitize as $setting_id => $pre_val ) {
		if ( empty( $pre_val ) ) {
			continue;
		}

		if ( is_array( $pre_val ) ) { // Usually checkboxes
			if ( json_encode( $pre_val ) !== json_encode( $settings[ $setting_id ] ) ) {
				$changed[] = $setting_id;
			}
		}
        elseif ( is_array( $settings[ $setting_id ] ) ) {
			$pre_val = preg_replace( '/\s+/', '', $pre_val );
			$san_val = preg_replace( '/\s+/', '', crb_format_field_value( $settings[ $setting_id ], crb_array_get( $defs, $setting_id ) ) );
			if ( $pre_val != $san_val ) {
				$changed[] = $setting_id;
			}
		}
        elseif ( $pre_val != $settings[ $setting_id ] ) {
			$changed[] = $setting_id;
		}
	}

	if ( $changed ) {
		$list = array_intersect_key( $defs, array_flip( $changed ) );
		$msg = '<div style="font-weight: 600;"><p>' . implode( '</p><p>', array_column( $list, 'title' ) ) . '</p></div>';
		cerber_admin_notice( __( 'For safety reasons, prohibited symbols and invalid values have been removed from the following settings. Please check their values.', 'wp-cerber' ) . $msg );
	}

	return $settings;
}

/**
 * Logs changes of specific WP Cerber settings to the diagnostic log.
 * To be logged, a setting has to have a 'diag_log' field in the setting config.
 *
 * @param array $update_details Information about updated settings.
 * @param array $diag_log_list The list of settings that have to be logged to the diagnostic log.
 *
 * @return void
 *
 * @since 9.0.1
 */
function crb_journaling( $update_details, $diag_log_list ) {
	if ( $update_details['equal'] ) {
		return;
	}

	if ( ! $changed = array_intersect( array_keys( $diag_log_list ), $update_details['changed'] ) ) {
		return;
	}

	foreach ( $changed as $key ) {

		$what = $diag_log_list[ $key ];

		if ( empty( $update_details['new_values'][ $key ] ) ) {
			$msg = 'Disabled: ' . $what;
		}
		else {
			$msg = 'Enabled: ' . $what;
		}

		cerber_diag_log( $msg, '*' );
	}

}

/**
 * Check setting field enabler and returns conditional inputs CSS class
 *
 * @param array $config The config of a setting field
 * @param mixed $enab_val The value of the enabler field
 *
 * @return string CSS class to be used
 */
function crb_check_enabler( $config, $enab_val ) {
	if ( ! isset( $config['enabler'] ) ) {
		return '';
	}

	$enabled = true;

	if ( isset( $config['enabler'][1] ) ) {
		$target_val = $config['enabler'][1];
		if ( 0 === strpos( $target_val, '[' ) ) {
			$list = json_decode( $target_val );
			if ( ! in_array( $enab_val, $list ) ) {
				$enabled = false;
			}
		}
		else {
			if ( $enab_val != $target_val ) {
				$enabled = false;
			}
		}
	}
	else {
		if ( empty( $enab_val ) ) {
			$enabled = false;
		}
	}

	return ( ! $enabled ) ? ' crb-disable-this' : '';
}

/**
 * @param mixed $value
 * @param array $config
 *
 * @return string
 *
 * @since 9.1.5
 */
function crb_format_field_value( $value, $config ) {
	if ( isset( $config['list'] ) ) {
		$dlt = $config['delimiter_show'] ?? $config['delimiter'];
		$value = cerber_array2text( $value, $dlt );
	}

	return $value;
}

/**
 * @param string $file
 *
 * @return bool False if .htaccess was not updated
 */
function crb_htaccess_admin( $file ): bool {

	$result = cerber_htaccess_sync( $file );

	if ( crb_is_wp_error( $result ) ) {
		CRB_Globals::$htaccess_failure[ $file ] = true;
		crb_admin_error_notice( $result );

		return false;
	}
    elseif ( $result ) {
		cerber_admin_message( $result );
	}

	return true;
}

/**
 * Retrieves and caches the list of languages available for the WP Cerber admin interface.
 * Function fetches the list of translations from the WP Cerber repo.
 *
 * @param array[] &$set Languages in an array as pairs [ locale => language name ]
 *
 * @return void|WP_Error Returns WP_Error if fetching available languages fails
 *
 * @since 9.6.6.14
 */
function crb_get_admin_languages( &$set = array() ) {

	if ( ( $set = get_site_transient( 'wp_cerber_lang_set' ) )
	     && is_array( $set ) ) {

		return;
	}

	$set = array();

	if ( ! include_once( ABSPATH . 'wp-admin/includes/translation-install.php' ) ) {

		return new WP_Error( 'include_failed', 'Unable to load translation-install.php' );
	}

	$list = array();

	// Get list if language names

	if ( $wp_langs = wp_get_available_translations() ) {
		$list = crb_extract_column_preserve_keys( $wp_langs, 'native_name' );
	}

	// Get available translations

	$data = crb_get_remote_json( 'https://downloads.wpcerber.com/versions/wp-cerber.json' );

	if ( crb_is_wp_error( $data ) ) {
		crb_admin_error_notice( $data );

		return $data;
	}

	if ( ! $crb_locales = $data[ CERBER_PLUGIN_ID ]['trans_bucket'] ?? false ) {

		return new WP_Error( 'no_trans_bucket', 'Unable to load available languages' );
	}

	// Prepare list of languages

	$crb_locales = array_filter( $crb_locales );
	$list = array_intersect_key( $list, $crb_locales );

	$list = array_filter( $list, function ( $key ) {
		return strpos( $key, 'en_' ) !== 0;
	}, ARRAY_FILTER_USE_KEY );

	$list['en_US'] = 'English';
	ksort( $list );

	set_site_transient( 'wp_cerber_lang_set', $list, DAY_IN_SECONDS );

    $set = $list;
}

/**
 * Determines the locale (translation language) for rendering WP Cerber admin pages for the current user.
 *
 * The locale is determined based on the WP Cerber settings and user preferences,
 * prioritizing the configured admin language, with a fallback to the user's locale.
 *
 * @param int $user_id User's ID or a WP_User object. Defaults to current user.
 *
 * @return string WordPress locale used for rendering admin pages.
 *
 * @since 9.6.6.14
 */
function crb_get_admin_locale( int $user_id = 0 ): string {
    static $locale;

	if ( $locale ) {
		return $locale;
	}

	$locale = '';

	if ( nexus_is_valid_request()
	     && $locale = nexus_request_data()->locale ) {

		return $locale;
	}

	if ( crb_get_settings( 'cerber_sw_repo' ) ) {
		$locale = crb_get_settings( 'admin_locale' );
	}
    elseif ( crb_get_settings( 'admin_lang' ) ) {
		$locale = 'en_US';
	}

	if ( ! $locale ) {
		$locale = get_user_locale( $user_id );
	}

	return $locale;
}

/**
 * Validates values of a settings field (user input) for proper REGEX patterns.
 *
 * Only values wrapped in curly braces {} are treated as REGEX patterns
 * and validated. All other values are considered plain strings and skipped.
 *
 * @param string|string[] $val The value to validate. Can be a string or an array of strings.
 *
 * @return true|WP_Error Returns true if all patterns are valid or the value is empty.
 *                       Returns a WP_Error object if invalid patterns are detected.
 *
 * @since 9.7.1
 */
function crb_validate_regex_field( $val ) {

    if ( ! $val ) {
        return true;
    }

    if ( ! is_array( $val ) ) {
        $val = array( $val );
    }

    $ret = new WP_Error();

    foreach ( $val as $item ) {

        if ( ! $item ) {
            continue;
        }

        $item = (string) $item;

        if ( $item[0] === '{' && substr( $item, - 1 ) === '}' ) {
            if ( ! $regex_body = substr( $item, 1, - 1 ) ) {
                continue;
            }

            $pattern = '~' . str_replace( '~', '\~', $regex_body ) . '~i';

            $result = crb_validate_regex( $pattern );

            if ( ! $result['valid'] ) {

                /* translators: %s is an invalid REGEX pattern. */
                $ret->add( 'invalid_regex', sprintf( __( 'The REGEX pattern %s is not valid.', 'wp-cerber' ), '<span class="crb-monospace-bold">' . crb_escape_html( $regex_body ) . '</span>' ) . ' ' . $result['error'] );
            }
        }
    }

    return $ret->has_errors() ? $ret : true;
}

/**
 * Validates a regular expression pattern using PHP PCRE engine.
 *
 * This function performs syntax-level validation by attempting to compile
 * the given pattern via preg_match(). It is intended for validating user-defined
 * REGEX patterns in settings before they are used in runtime logic.
 *
 * @param string $pattern The REGEX pattern including delimiters and optional modifiers.
 *
 * @return array{
 *     valid: bool,
 *     error: string
 * }
 *
 * @since 9.7.1
 */
function crb_validate_regex( string $pattern ): array {

    if ( $pattern === '' ) {
        return [
                'valid' => false,
                'error' => 'Empty pattern',
        ];
    }

    // Capture PCRE warning text produced during pattern compilation.
    $error_message = '';

    set_error_handler(
            static function ( $errno, $errstr ) use ( &$error_message ) {
                $error_message = $errstr;

                return true;
            },
            E_WARNING
    );

    try {
        $result = preg_match( $pattern, '' );
    }
    finally {
        restore_error_handler();
    }

    // Return the captured compilation error if the pattern is invalid.
    if ( $result === false ) {
        return [
                'valid' => false,
                'error' => $error_message ?: __( 'Invalid regular expression', 'wp-cerber' ),
        ];
    }

    // The pattern is syntactically valid.
    return [
            'valid' => true,
            'error' => '',
    ];
}

/**
 * Generates an HTML link tag to the admin setting page where the specified setting is located
 *
 * @param string $setting_id WP Cerber setting ID
 * @param int $user_id User ID, if the setting is role-based and a user is involved.
 * @param bool $show_title Whether to prepend the setting title before the link.
 *
 * @return string Safe HTML markup for rendering in the admin UI, or an empty string
 *                 if the setting location cannot be determined.
 *
 * @since 9.7
 */
function crb_get_setting_link( string $setting_id, int $user_id = 0, bool $show_title = false ): string {

	$title = '';
	$tab_id = '';
	$fragment = '';

	if ( $setting = CRB_Settings_Registry::get_config( array( 'setting' => $setting_id ) ) ) {
		if ( $title = $setting['title'] ?? '' ) {
			$tab_id = $setting['page_tab_id'];
			$fragment = '#' . CRB_WRAPPER_ID_PREFIX . 'global-' . $setting_id;
		}
	}
	elseif ( $setting = crb_get_role_settings_config( $setting_id ) ) {
		if ( $title = $setting['title'] ?? '' ) {
			$tab_id = 'role_policies';

			if ( $user_id
			     && ( $user = crb_get_userdata( $user_id ) )
			     && $role = crb_sanitize_id( array_shift( $user->roles ) ) ) { // Currently, we do not support multiple roles
				$fragment = '#' . $role;
			}
		}
	}

	if ( ! $tab_id ) {
		return '';
	}

	return ( $show_title ? $title : '' ) . ' [&nbsp;<a href="' . crb_admin_link_for_html( $tab_id ) . $fragment . '" target="_blank">' . __( 'Manage', 'wp-cerber' ) . '</a>&nbsp;]';
}