[ 'type' => 'int', 'default' => 0, 'autoload' => true ], 'login_url' => [ 'type' => 'url', 'default' => '', 'autoload' => true ], 'register_url' => [ 'type' => 'url', 'default' => '', 'autoload' => true ], 'admin_bypass' => [ 'type' => 'bool', 'default' => 0, 'autoload' => true ], 'show_templates_column' => [ 'type' => 'bool', 'default' => 0, 'autoload' => true ], 'post_types' => [ 'type' => 'post_types', 'default' => null, 'autoload' => true ], 'roles' => [ 'type' => 'roles', 'default' => null, 'autoload' => true ], 'hide' => [ 'type' => 'contexts', 'default' => [ 'archives', 'search', 'feeds', 'rest', 'nav_menus', 'adjacent' ], 'autoload' => true ], 'clear_data_on_uninstall' => [ 'type' => 'bool', 'default' => 0, 'autoload' => false ], ]; return apply_filters( 'deadbolt_settings_fields', $fields ); } // End fields() /** * Get every option name managed by the Settings tab. * * @return array */ public static function option_names() : array { return array_map( fn( $key ) => self::PREFIX . $key, array_keys( self::fields() ) ); } // End option_names() /** * Get a setting value. * * @param string $key Key without the option prefix. * @return mixed */ public static function get( string $key ) : mixed { if ( array_key_exists( $key, self::$cache ) ) { return self::$cache[ $key ]; } $fields = self::fields(); if ( ! isset( $fields[ $key ] ) ) { return null; } $value = get_option( self::PREFIX . $key, null ); if ( null === $value ) { $value = self::default( $key ); } if ( 'post_types' === $key ) { $value = (array) apply_filters( 'deadbolt_post_types', (array) $value ); } elseif ( 'roles' === $key ) { $value = (array) apply_filters( 'deadbolt_roles', (array) $value ); } if ( did_action( 'init' ) ) { self::$cache[ $key ] = $value; } return $value; } // End get() /** * Get a setting's default value. * * @param string $key * @return mixed */ public static function default( string $key ) : mixed { if ( 'post_types' === $key ) { return array_fill_keys( array_keys( Helpers::public_post_types() ), 'everyone' ); } if ( 'roles' === $key ) { return array_values( array_intersect( self::DEFAULT_ROLES, array_keys( Helpers::all_roles() ) ) ); } return self::fields()[ $key ][ 'default' ] ?? null; } // End default() /** * Sanitize a value for a setting. * * @param string $key * @param mixed $value * @return mixed */ public static function sanitize( string $key, mixed $value ) : mixed { $field = self::fields()[ $key ] ?? []; if ( isset( $field[ 'sanitize' ] ) && is_callable( $field[ 'sanitize' ] ) ) { return call_user_func( $field[ 'sanitize' ], $value ); } switch ( $field[ 'type' ] ?? '' ) { case 'int': return absint( $value ); case 'url': return is_string( $value ) ? esc_url_raw( trim( $value ) ) : ''; case 'bool': return empty( $value ) ? 0 : 1; case 'post_types': $clean = []; foreach ( (array) $value as $post_type => $access ) { $post_type = sanitize_key( $post_type ); if ( '' !== $post_type && post_type_exists( $post_type ) ) { $access = is_string( $access ) ? $access : ''; $flag = trim( explode( ',', $access )[ 0 ] ); $clean[ $post_type ] = in_array( $flag, self::DEFAULT_ACCESS, true ) ? Access::build( $flag, [], [], (array) ( Access::parse( $access )[ 'param' ] ?? [] ) ) : 'everyone'; } } return $clean; case 'roles': return array_values( array_intersect( array_map( 'sanitize_key', (array) $value ), array_keys( Helpers::all_roles() ) ) ); case 'contexts': return array_values( array_intersect( array_map( 'sanitize_key', (array) $value ), array_keys( Hide::contexts() ) ) ); } return is_string( $value ) ? sanitize_text_field( $value ) : ''; } // End sanitize() /** * Sanitize and store one setting. * * @param string $key * @param mixed $value * @return void */ public static function update( string $key, mixed $value ) : void { $fields = self::fields(); if ( ! isset( $fields[ $key ] ) ) { return; } update_option( self::PREFIX . $key, self::sanitize( $key, $value ), ! empty( $fields[ $key ][ 'autoload' ] ) ); unset( self::$cache[ $key ] ); } // End update() /** * Sanitize and store several settings at once. Keys that are not provided are left alone. * * @param array $values Values keyed without the option prefix, in stored format. * @return void */ public static function save( array $values ) : void { foreach ( array_keys( self::fields() ) as $key ) { if ( array_key_exists( $key, $values ) ) { self::update( $key, $values[ $key ] ); } } do_action( 'deadbolt_settings_saved', $values ); } // End save() /** * Delete every setting so defaults apply again. * * @return void */ public static function reset() : void { foreach ( self::option_names() as $option ) { delete_option( $option ); } self::$cache = []; do_action( 'deadbolt_settings_reset' ); } // End reset() /** * Clear the request cache, used after options are changed outside this class. * * @return void */ public static function flush_cache() : void { self::$cache = []; } // End flush_cache() /** * Get the URL of the Settings tab. * * @return string */ public static function url() : string { return admin_url( 'admin.php?page=' . Menu::SETTINGS_SLUG ); } // End url() /** * Check whether the current screen is the Settings tab. * * @return bool */ private static function is_settings_screen() : bool { $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; return $screen && Menu::settings_screen_id() === $screen->id; } // End is_settings_screen() /** * Enqueue assets on the Settings tab. * * @return void */ public function enqueue() : void { if ( ! self::is_settings_screen() ) { return; } Pickers::enqueue_fields_assets(); wp_enqueue_script( 'deadbolt-settings', Bootstrap::url() . 'inc/js/settings.js', [ 'jquery', 'deadbolt-fields' ], Bootstrap::script_version(), true ); wp_localize_script( 'deadbolt-settings', 'deadboltSettings', [ 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( self::NONCE ), 'savingText' => __( 'Saving', 'deadbolt' ), 'savedText' => __( 'Settings saved successfully.', 'deadbolt' ), 'errorText' => __( 'Error saving settings. Please try again.', 'deadbolt' ), 'importOk' => __( 'Settings imported. Reloading…', 'deadbolt' ), 'importError' => __( 'That file is not a valid settings export.', 'deadbolt' ), 'resetOk' => __( 'Settings reset. Reloading…', 'deadbolt' ), 'pageCreated' => __( 'No Access Page created and saved.', 'deadbolt' ), 'confirmReset' => __( 'Reset all Deadbolt settings to their defaults? Your templates and the access rules on your posts are not affected.', 'deadbolt' ), 'confirmImport' => __( 'Importing will overwrite your current Deadbolt settings. Continue?', 'deadbolt' ), 'confirmMenus' => __( 'Import the visibility rules from Nav Menu Roles into Deadbolt? Existing menu rules on the same items will be overwritten and Nav Menu Roles will be deactivated.', 'deadbolt' ), 'exportName' => 'deadbolt-settings-' . gmdate( 'Y-m-d' ) . '.json', ] ); } // End enqueue() /** * Render the Save button and dirty-state reminder in the subheader. * * @param string $active_tab * @return void */ public function render_subheader_buttons( string $active_tab ) : void { if ( 'settings' !== $active_tab ) { return; } ?>

'deadbolt[no_access_page]', 'id' => 'deadbolt_no_access_page', 'selected' => absint( $no_access_page ), 'show_option_none' => esc_html__( '— Select a Page —', 'deadbolt' ), 'option_none_value' => 0, ] ); ?>

$context ) : ?>

$object ) : ?> 'everyone' ]; ?>
>

$label ) : ?>

$field ) { $raw = $form[ $key ] ?? null; if ( 'post_types' === $field[ 'type' ] ) { $enabled = []; foreach ( (array) $raw as $post_type => $row ) { if ( is_array( $row ) && ! empty( $row[ 'enabled' ] ) ) { $enabled[ $post_type ] = Access::build( sanitize_key( (string) ( $row[ 'default' ] ?? 'everyone' ) ), [], [], (array) ( $row[ 'param' ] ?? [] ) ); } } $values[ $key ] = $enabled; } elseif ( in_array( $field[ 'type' ], [ 'roles', 'contexts' ], true ) ) { $values[ $key ] = (array) $raw; } else { $values[ $key ] = $raw ?? ''; } } return $values; } // End form_to_values() /** * AJAX: save every field on the Settings tab. * * @return void */ public function ajax_save() : void { Helpers::verify_ajax( self::NONCE ); $form = isset( $_POST[ 'deadbolt' ] ) && is_array( $_POST[ 'deadbolt' ] ) ? wp_unslash( $_POST[ 'deadbolt' ] ) : []; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- the nonce is verified by Helpers::verify_ajax() before this runs, and every value is sanitized individually in self::sanitize(). self::save( self::form_to_values( $form ) ); wp_send_json_success(); } // End ajax_save() /** * AJAX: create a published No Access Page containing the shortcode and select it. * * @return void */ public function ajax_create_page() : void { Helpers::verify_ajax( self::NONCE ); $page_id = wp_insert_post( [ 'post_type' => 'page', 'post_status' => 'publish', 'post_title' => __( 'Access Denied', 'deadbolt' ), 'post_content' => '[' . Shortcode::TAG . ']', ], true ); if ( is_wp_error( $page_id ) ) { wp_send_json_error( $page_id->get_error_message() ); } self::update( 'no_access_page', $page_id ); wp_send_json_success( [ 'id' => $page_id, 'title' => get_the_title( $page_id ) ] ); } // End ajax_create_page() } // End class Settings Settings::instance();