'post', 'per_page' => 50, 'autosave' => 0, 'enable_woo' => 1, 'enable_history' => 1, 'allowed_roles' => array( 'administrator', 'editor', 'shop_manager' ), 'max_bulk' => 500, 'protected_columns' => array(), 'language' => 'auto', 'enable_scheduled_export' => 0, 'webhook_url' => '', 'feed_url' => '', 'ai_api_key' => '', 'ai_base_url' => '', 'ai_model' => '', 'enable_stock_alerts' => 0, 'own_only' => 0, 'menu_title' => '', 'merchant_feed_key' => '', 'enable_mcp' => 1, 'mcp_mode' => 'full', 'mcp_roles' => array( 'administrator', 'editor', 'shop_manager' ), 'mcp_groups' => array( 'read', 'write', 'destructive', 'admin' ), 'mcp_tools_denied' => array(), 'mcp_rate_limit' => 120, 'mcp_ip_allowlist' => '', 'scheduled_owner' => 0, ); } /** * Ensure defaults exist. */ public static function ensure_defaults() { if ( false === get_option( self::OPTION, false ) ) { add_option( self::OPTION, self::defaults() ); } } /** * @var array */ private $options = null; /** * @param array|null $override For tests. */ public function __construct( $override = null ) { if ( is_array( $override ) ) { $this->options = wp_parse_args( $override, self::defaults() ); } } /** * Get all. * * @return array */ public function all() { if ( null === $this->options ) { $this->options = wp_parse_args( (array) get_option( self::OPTION, array() ), self::defaults() ); } return $this->options; } /** * Get one. * * @param string $key Key. * @param mixed $default Default. * @return mixed */ public function get( $key, $default = null ) { $all = $this->all(); return isset( $all[ $key ] ) ? $all[ $key ] : $default; } /** * Save. * * @param array $data Data. * @return bool */ public function save( $data ) { $clean = array( 'default_post_type' => isset( $data['default_post_type'] ) ? sanitize_key( $data['default_post_type'] ) : 'post', 'per_page' => isset( $data['per_page'] ) ? max( 10, min( 200, absint( $data['per_page'] ) ) ) : 50, 'autosave' => ! empty( $data['autosave'] ) ? 1 : 0, 'enable_woo' => ! empty( $data['enable_woo'] ) ? 1 : 0, 'enable_history' => ! empty( $data['enable_history'] ) ? 1 : 0, 'max_bulk' => isset( $data['max_bulk'] ) ? max( 50, min( 2000, absint( $data['max_bulk'] ) ) ) : 500, 'allowed_roles' => array(), ); if ( isset( $data['allowed_roles'] ) && is_array( $data['allowed_roles'] ) ) { $editable = array_keys( get_editable_roles() ); foreach ( $data['allowed_roles'] as $role ) { $role = sanitize_key( $role ); if ( in_array( $role, $editable, true ) ) { $clean['allowed_roles'][] = $role; } } if ( empty( $clean['allowed_roles'] ) ) { $clean['allowed_roles'] = array( 'administrator' ); } } else { $clean['allowed_roles'] = (array) $this->get( 'allowed_roles', array( 'administrator' ) ); } $clean['language'] = isset( $data['language'] ) ? sanitize_text_field( wp_unslash( $data['language'] ) ) : 'auto'; if ( 'auto' !== $clean['language'] && ! array_key_exists( $clean['language'], Plugin::LANGUAGES ) ) { $clean['language'] = 'auto'; } $clean['enable_scheduled_export'] = ! empty( $data['enable_scheduled_export'] ) ? 1 : 0; $clean['webhook_url'] = isset( $data['webhook_url'] ) ? esc_url_raw( wp_unslash( $data['webhook_url'] ) ) : ''; $clean['feed_url'] = isset( $data['feed_url'] ) ? esc_url_raw( wp_unslash( $data['feed_url'] ) ) : ''; $clean['ai_api_key'] = isset( $data['ai_api_key'] ) ? sanitize_text_field( wp_unslash( $data['ai_api_key'] ) ) : ''; $clean['ai_base_url'] = isset( $data['ai_base_url'] ) ? esc_url_raw( wp_unslash( $data['ai_base_url'] ), array( 'https', 'http' ) ) : ''; $clean['ai_model'] = isset( $data['ai_model'] ) ? sanitize_text_field( wp_unslash( $data['ai_model'] ) ) : ''; // Scheduled export and feed sync run as the administrator who saved these settings. $clean['scheduled_owner'] = get_current_user_id(); $clean['enable_stock_alerts'] = ! empty( $data['enable_stock_alerts'] ) ? 1 : 0; $clean['own_only'] = ! empty( $data['own_only'] ) ? 1 : 0; $clean['menu_title'] = isset( $data['menu_title'] ) ? sanitize_text_field( wp_unslash( $data['menu_title'] ) ) : ''; $clean['merchant_feed_key'] = isset( $data['merchant_feed_key'] ) ? sanitize_text_field( wp_unslash( $data['merchant_feed_key'] ) ) : (string) $this->get( 'merchant_feed_key', '' ); $clean['enable_mcp'] = ! empty( $data['enable_mcp'] ) ? 1 : 0; $clean['mcp_mode'] = isset( $data['mcp_mode'] ) && in_array( $data['mcp_mode'], array( 'full', 'readonly', 'custom' ), true ) ? $data['mcp_mode'] : 'full'; $clean['mcp_roles'] = array(); if ( isset( $data['mcp_roles'] ) && is_array( $data['mcp_roles'] ) ) { $editable = array_keys( get_editable_roles() ); foreach ( $data['mcp_roles'] as $role ) { $role = sanitize_key( $role ); if ( in_array( $role, $editable, true ) ) { $clean['mcp_roles'][] = $role; } } } $clean['mcp_groups'] = array(); if ( isset( $data['mcp_groups'] ) && is_array( $data['mcp_groups'] ) ) { foreach ( $data['mcp_groups'] as $g ) { $g = sanitize_key( $g ); if ( in_array( $g, array( 'read', 'write', 'destructive', 'admin' ), true ) ) { $clean['mcp_groups'][] = $g; } } } $clean['mcp_tools_denied'] = array(); if ( isset( $data['mcp_tools_denied'] ) && is_array( $data['mcp_tools_denied'] ) ) { $known = array_column( MCP::tools(), 'name' ); foreach ( $data['mcp_tools_denied'] as $tool ) { $tool = sanitize_key( $tool ); if ( in_array( $tool, $known, true ) ) { $clean['mcp_tools_denied'][] = $tool; } } } $clean['mcp_rate_limit'] = isset( $data['mcp_rate_limit'] ) ? max( 0, min( 10000, absint( $data['mcp_rate_limit'] ) ) ) : 120; $clean['mcp_ip_allowlist'] = isset( $data['mcp_ip_allowlist'] ) ? sanitize_text_field( wp_unslash( $data['mcp_ip_allowlist'] ) ) : ''; $clean['protected_columns'] = array(); if ( isset( $data['protected_columns'] ) ) { $raw = is_array( $data['protected_columns'] ) ? implode( ',', $data['protected_columns'] ) : (string) $data['protected_columns']; foreach ( array_filter( array_map( 'trim', explode( ',', $raw ) ) ) as $k ) { if ( preg_match( '/^[A-Za-z0-9_:\-]{1,100}$/', $k ) ) { $clean['protected_columns'][] = $k; } } } $this->options = $clean; return update_option( self::OPTION, $clean ); } }