*/ public function get(): array { $stored = get_option( self::OPTION_NAME, array() ); $settings = wp_parse_args( is_array( $stored ) ? $stored : array(), self::defaults() ); // Front-end only, and only with a multilingual plugin active: give the // Pro addon a chance to layer this language's Organization overrides // over the base values. The admin always sees/edits the base values, // so editing stays unambiguous. M69 Etap 2 moved the overrides // themselves into acez-seo-pro (WP.org Guideline 5 — the base plugin // must not carry license-gated functionality); the two conditions // below are NOT a license gate, they keep this filter off the admin // path and off installs with no multilingual plugin, because get() // runs many times per request. if ( ! is_admin() && \AcezSeo\Support\MultilingualPluginDetector::is_active() ) { /** * Filters the resolved global settings for the current language. * * @param array $settings * @param string $locale Current front-end locale. */ $settings = (array) apply_filters( 'acez_seo_global_settings_for_locale', $settings, \AcezSeo\Support\MultilingualPluginDetector::current_locale() ); } return $settings; } /** * Merges $settings into the CURRENTLY STORED values (not just defaults) — * the Global and LocalBusiness cards submit two separate forms writing to * this same option, so a partial update from one must not wipe out fields * only the other form knows about. * * Deliberately merges with the RAW stored values, not $this->get(): * get() may have per-language overrides layered in, and update() must * never bake an override into the base values. The legacy * `language_overrides` sub-blob is still carried over untouched: since * M69 Etap 2 the Pro addon owns that data and migrates it into its own * option, but installs that never activate Pro must not have it wiped by * an unrelated settings save (sanitize() only returns the flat keys). * * @param array $settings */ public function update( array $settings ): bool { $stored = get_option( self::OPTION_NAME, array() ); $stored = is_array( $stored ) ? $stored : array(); $base = wp_parse_args( $stored, self::defaults() ); $sanitized = self::sanitize( wp_parse_args( $settings, $base ) ); if ( isset( $stored['language_overrides'] ) && is_array( $stored['language_overrides'] ) ) { $sanitized['language_overrides'] = $stored['language_overrides']; } return update_option( self::OPTION_NAME, $sanitized ); } /** * @return array */ private static function defaults(): array { return array( 'organization_name' => '', 'organization_logo_id' => 0, 'organization_scope' => 'site', 'organization_page_ids' => array(), 'social_profiles' => array(), 'no_category_base' => false, 'local_business_enabled' => false, 'local_business_scope' => 'site', 'local_business_page_ids' => array(), 'local_business_type' => 'LocalBusiness', 'local_business_street' => '', 'local_business_locality' => '', 'local_business_region' => '', 'local_business_postal' => '', 'local_business_country' => '', 'local_business_phone' => '', 'local_business_lat' => '', 'local_business_lng' => '', 'local_business_hours' => '', 'force_locale' => '', 'hide_from_search' => false, 'override_seo_conflict' => false, ); } /** * @param array $settings * * @return array */ private static function sanitize( array $settings ): array { $profiles = array(); if ( is_array( $settings['social_profiles'] ) ) { foreach ( $settings['social_profiles'] as $url ) { $clean = esc_url_raw( (string) $url ); if ( '' !== $clean ) { $profiles[] = $clean; } } } return array( 'organization_name' => sanitize_text_field( (string) $settings['organization_name'] ), 'organization_logo_id' => absint( $settings['organization_logo_id'] ), 'organization_scope' => self::sanitize_node_scope( (string) $settings['organization_scope'] ), 'organization_page_ids' => array_values( array_filter( array_map( 'absint', (array) $settings['organization_page_ids'] ) ) ), 'social_profiles' => $profiles, 'no_category_base' => (bool) $settings['no_category_base'], 'local_business_enabled' => (bool) $settings['local_business_enabled'], 'local_business_scope' => self::sanitize_node_scope( (string) $settings['local_business_scope'] ), 'local_business_page_ids' => array_values( array_filter( array_map( 'absint', (array) $settings['local_business_page_ids'] ) ) ), 'local_business_type' => self::sanitize_business_type( (string) $settings['local_business_type'] ), 'local_business_street' => sanitize_text_field( (string) $settings['local_business_street'] ), 'local_business_locality' => sanitize_text_field( (string) $settings['local_business_locality'] ), 'local_business_region' => sanitize_text_field( (string) $settings['local_business_region'] ), 'local_business_postal' => sanitize_text_field( (string) $settings['local_business_postal'] ), 'local_business_country' => sanitize_text_field( (string) $settings['local_business_country'] ), 'local_business_phone' => sanitize_text_field( (string) $settings['local_business_phone'] ), 'local_business_lat' => self::sanitize_coordinate( (string) $settings['local_business_lat'], 90.0 ), 'local_business_lng' => self::sanitize_coordinate( (string) $settings['local_business_lng'], 180.0 ), 'local_business_hours' => sanitize_textarea_field( (string) $settings['local_business_hours'] ), 'force_locale' => self::sanitize_force_locale( (string) $settings['force_locale'] ), 'hide_from_search' => (bool) $settings['hide_from_search'], 'override_seo_conflict' => (bool) $settings['override_seo_conflict'], ); } private static function sanitize_force_locale( string $value ): string { return in_array( $value, array( 'pl_PL', 'en_US' ), true ) ? $value : ''; } private static function sanitize_node_scope( string $value ): string { return in_array( $value, array( 'site', 'front_page', 'front_page_pages' ), true ) ? $value : 'site'; } private static function sanitize_business_type( string $value ): string { $value = (string) preg_replace( '/[^A-Za-z]/', '', $value ); return '' !== $value ? $value : 'LocalBusiness'; } /** * Latitude and longitude, kept as strings so an empty field stays empty * rather than becoming "0" (a real place at 0,0 is in the Atlantic). * Out-of-range values are rejected: a longitude of 999 is not a typo we * can fix, and passing it on would produce invalid LocalBusiness schema. * * @param string $value Raw coordinate. * @param float $limit Absolute bound: 90 for latitude, 180 for longitude. */ private static function sanitize_coordinate( string $value, float $limit = 180.0 ): string { $value = trim( $value ); if ( '' === $value || ! is_numeric( $value ) ) { return ''; } return abs( (float) $value ) <= $limit ? $value : ''; } }