*/ private const VALID_MODES = array( 'per_site', 'central' ); /** * Valid settings tabs (reuses same tab structure as per-site Settings). * * @var list */ private const TAB_KEYS = array( 'mode', 'api', 'filters', 'external' ); /** * Returns the active tab from the current request. * * @return string */ private function get_active_tab(): string { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $raw = isset( $_GET['tab'] ) && is_string( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'mode'; return in_array( $raw, self::TAB_KEYS, true ) ? $raw : 'mode'; } /** * Handles the network_admin_edit_ action for saving settings. * * Redirects back to the settings page after saving. * * @return void */ public function save(): void { if ( ! current_user_can( 'manage_network_options' ) ) { wp_die( esc_html__( 'You do not have permission to manage network settings.', 'robotstxt-mediaaudit' ) ); } check_admin_referer( 'mra_network_settings_nonce' ); $tab_raw = isset( $_POST['_tab'] ) && is_string( $_POST['_tab'] ) ? sanitize_key( $_POST['_tab'] ) : ''; $this->save_tab( $tab_raw ); wp_safe_redirect( add_query_arg( array( 'page' => 'robotstxt-mediaaudit-network-settings', 'tab' => $tab_raw, 'updated' => '1', ), network_admin_url( 'admin.php' ) ) ); exit; } /** * Saves one tab's data to the appropriate site option. * * @param string $tab Tab key being saved. * * @return void */ private function save_tab( string $tab ): void { if ( 'mode' === $tab ) { $raw_mode = isset( $_POST['network_mode'] ) && is_string( $_POST['network_mode'] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing ? sanitize_key( $_POST['network_mode'] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing : 'per_site'; $mode = in_array( $raw_mode, self::VALID_MODES, true ) ? $raw_mode : 'per_site'; $previous_mode = get_site_option( self::MODE_OPTION, 'per_site' ); update_site_option( self::MODE_OPTION, $mode ); // Migrate settings from main site on first switch to central mode. if ( 'central' === $mode && 'central' !== $previous_mode ) { $this->maybe_migrate_from_main_site(); } return; } $raw = get_site_option( self::NETWORK_OPTION, array() ); $current = is_array( $raw ) ? $raw : array(); $output = $current; // phpcs:disable WordPress.Security.NonceVerification.Missing if ( 'api' === $tab ) { $output['google_vision_api_key'] = sanitize_text_field( wp_unslash( is_string( $_POST['google_vision_api_key'] ?? null ) ? $_POST['google_vision_api_key'] : '' ) ); $output['tineye_api_key'] = sanitize_text_field( wp_unslash( is_string( $_POST['tineye_api_key'] ?? null ) ? $_POST['tineye_api_key'] : '' ) ); $output['picdefense_user_id'] = sanitize_text_field( wp_unslash( is_string( $_POST['picdefense_user_id'] ?? null ) ? $_POST['picdefense_user_id'] : '' ) ); $output['picdefense_api_key'] = sanitize_text_field( wp_unslash( is_string( $_POST['picdefense_api_key'] ?? null ) ? $_POST['picdefense_api_key'] : '' ) ); } if ( 'filters' === $tab ) { $site_settings = new SiteSettings(); // sanitize_hostname_list_public() fully sanitizes the raw textarea value. // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $output['filter_include'] = $site_settings->sanitize_hostname_list_public( wp_unslash( is_string( $_POST['filter_include'] ?? null ) ? $_POST['filter_include'] : '' ) ); $output['filter_exclude'] = $site_settings->sanitize_hostname_list_public( wp_unslash( is_string( $_POST['filter_exclude'] ?? null ) ? $_POST['filter_exclude'] : '' ) ); // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $overlap = array_intersect( $output['filter_include'], $output['filter_exclude'] ); if ( ! empty( $overlap ) ) { $output['filter_exclude'] = array_values( array_diff( $output['filter_exclude'], $overlap ) ); } } if ( 'external' === $tab ) { $batch_val = is_numeric( $_POST['external_batch_size'] ?? null ) ? (int) $_POST['external_batch_size'] : 10; $output['external_batch_size'] = max( 1, min( 100, $batch_val ) ); $rl_val = is_numeric( $_POST['rate_limit_per_minute'] ?? null ) ? (int) $_POST['rate_limit_per_minute'] : 10; $output['rate_limit_per_minute'] = max( 1, min( 60, $rl_val ) ); } // phpcs:enable WordPress.Security.NonceVerification.Missing update_site_option( self::NETWORK_OPTION, $output ); } /** * Copies the main site's settings to the network option as a starting point. * * Only runs once — if the network option already has API credentials set, * it is not overwritten. * * @return void */ private function maybe_migrate_from_main_site(): void { $existing = get_site_option( self::NETWORK_OPTION, array() ); if ( is_array( $existing ) && ! empty( $existing ) ) { return; } $main_site_id = get_main_site_id(); switch_to_blog( $main_site_id ); $site_settings = get_option( SiteSettings::OPTION_NAME, array() ); restore_current_blog(); if ( is_array( $site_settings ) && ! empty( $site_settings ) ) { update_site_option( self::NETWORK_OPTION, $site_settings ); } } /** * Renders the network Settings page. * * @return void */ public function render(): void { if ( ! current_user_can( 'manage_network_options' ) ) { wp_die( esc_html__( 'You do not have permission to access this page.', 'robotstxt-mediaaudit' ) ); } $tab = $this->get_active_tab(); $mode_raw = get_site_option( self::MODE_OPTION, 'per_site' ); $mode = is_string( $mode_raw ) ? $mode_raw : 'per_site'; $opts_raw = get_site_option( self::NETWORK_OPTION, array() ); $opts = is_array( $opts_raw ) ? $opts_raw : array(); $tabs = array( 'mode' => __( 'Mode', 'robotstxt-mediaaudit' ), 'api' => __( 'API Credentials', 'robotstxt-mediaaudit' ), 'filters' => __( 'Filters', 'robotstxt-mediaaudit' ), 'external' => __( 'External Scanning', 'robotstxt-mediaaudit' ), ); $page_url = network_admin_url( 'admin.php?page=robotstxt-mediaaudit-network-settings' ); // phpcs:disable WordPress.Security.NonceVerification.Recommended $updated = isset( $_GET['updated'] ) && '1' === $_GET['updated']; // phpcs:enable WordPress.Security.NonceVerification.Recommended ?>

render_mode_tab( $mode ); ?> render_api_tab( $opts ); ?> render_filters_tab( $opts ); ?> render_external_tab( $opts ); ?>

$opts Current network settings. * * @return void */ private function render_api_tab( array $opts ): void { $gv_key = is_string( $opts['google_vision_api_key'] ?? null ) ? $opts['google_vision_api_key'] : ''; $te_key = is_string( $opts['tineye_api_key'] ?? null ) ? $opts['tineye_api_key'] : ''; $pd_uid = is_string( $opts['picdefense_user_id'] ?? null ) ? $opts['picdefense_user_id'] : ''; $pd_key = is_string( $opts['picdefense_api_key'] ?? null ) ? $opts['picdefense_api_key'] : ''; ?>

$opts Current network settings. * * @return void */ private function render_filters_tab( array $opts ): void { $include_raw = $opts['filter_include'] ?? array(); $exclude_raw = $opts['filter_exclude'] ?? array(); $include_list = is_array( $include_raw ) ? array_filter( $include_raw, 'is_string' ) : array(); $exclude_list = is_array( $exclude_raw ) ? array_filter( $exclude_raw, 'is_string' ) : array(); ?>

$opts Current network settings. * * @return void */ private function render_external_tab( array $opts ): void { $batch_size = is_numeric( $opts['external_batch_size'] ?? null ) ? (int) $opts['external_batch_size'] : 10; $rate_limit = is_numeric( $opts['rate_limit_per_minute'] ?? null ) ? (int) $opts['rate_limit_per_minute'] : 10; ?>