40 || ! preg_match( '/^[0-9+().\-\s]+$/', $value ) || ! preg_match( '/[0-9]/', $value ) ) { return ''; } return $value; } /** * Convert cuisine input to unique plain-text values. * * @param mixed $value Comma- or line-separated string, or a list. * @return string[] */ function sts_seo_schema_cuisine( $value ) { if ( is_string( $value ) ) { $value = preg_split( '/[\r\n,]+/', $value ); } if ( ! is_array( $value ) ) { return array(); } $items = array(); foreach ( $value as $item ) { $item = sts_seo_plain_text( $item ); if ( '' !== $item ) { $items[] = $item; } } return array_values( array_unique( $items ) ); } /** * Return a valid 24-hour time or an empty string. * * @param mixed $value Raw value. * @return string HH:MM, or ''. */ function sts_seo_schema_time( $value ) { if ( ! is_scalar( $value ) || ! preg_match( '/^(?:[01][0-9]|2[0-3]):[0-5][0-9]$/D', trim( (string) $value ) ) ) { return ''; } return trim( (string) $value ); } /** * Expand a validated day name to its Schema.org enumeration URL. * * @param string $day English day name. * @return string */ function sts_seo_schema_day_url( $day ) { return 'https://schema.org/' . $day; } /** * Normalize structured or line-based hours, preserving split intervals. * * @param mixed $value Lines of `Days|HH:MM|HH:MM`, or a list of rows. * @return array[] Rows with days, opens and closes. */ function sts_seo_schema_opening_hours( $value ) { $allowed = array( 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ); $rows = array(); if ( is_string( $value ) ) { $lines = preg_split( '/\r\n|\r|\n/', $value ); $value = array(); foreach ( $lines as $line ) { $parts = array_map( 'trim', explode( '|', $line ) ); if ( 3 === count( $parts ) ) { $value[] = array( 'days' => explode( ',', $parts[0] ), 'opens' => $parts[1], 'closes' => $parts[2], ); } } } if ( ! is_array( $value ) ) { return array(); } foreach ( $value as $row ) { if ( ! is_array( $row ) ) { continue; } $days = isset( $row['days'] ) ? $row['days'] : array(); if ( is_string( $days ) ) { $days = explode( ',', $days ); } $days = array_values( array_unique( array_intersect( $allowed, array_map( 'trim', is_array( $days ) ? $days : array() ) ) ) ); $opens = sts_seo_schema_time( isset( $row['opens'] ) ? $row['opens'] : '' ); $closes = sts_seo_schema_time( isset( $row['closes'] ) ? $row['closes'] : '' ); if ( $days && $opens && $closes ) { $rows[] = array( 'days' => $days, 'opens' => $opens, 'closes' => $closes, ); } } return $rows; } /** * Sanitize the complete business entity option. * * @param mixed $value Submitted or stored option. * @return array Clean option. */ function sts_seo_sanitize_schema_settings( $value ) { if ( ! is_array( $value ) ) { return array(); } $type = isset( $value['type'] ) && is_scalar( $value['type'] ) ? (string) $value['type'] : ''; if ( ! in_array( $type, array( 'Organization', 'LocalBusiness', 'Restaurant' ), true ) ) { $type = ''; } $clean = array( 'type' => $type ); foreach ( array( 'name', 'street_address', 'address_locality', 'address_region', 'postal_code', 'address_country' ) as $key ) { $clean[ $key ] = sts_seo_plain_text( isset( $value[ $key ] ) ? $value[ $key ] : '' ); } $clean['url'] = sts_seo_schema_url( isset( $value['url'] ) ? $value['url'] : '' ); $clean['telephone'] = sts_seo_schema_telephone( isset( $value['telephone'] ) ? $value['telephone'] : '' ); $clean['cuisine'] = sts_seo_schema_cuisine( isset( $value['cuisine'] ) ? $value['cuisine'] : array() ); $clean['menu_url'] = sts_seo_schema_url( isset( $value['menu_url'] ) ? $value['menu_url'] : '' ); $year = isset( $value['founding_year'] ) && is_scalar( $value['founding_year'] ) ? trim( (string) $value['founding_year'] ) : ''; $clean['founding_year'] = preg_match( '/^[1-9][0-9]{3}$/D', $year ) ? $year : ''; $clean['opening_hours'] = sts_seo_schema_opening_hours( isset( $value['opening_hours'] ) ? $value['opening_hours'] : array() ); return $clean; } /** * Return the stored option after revalidation. * * @return array */ function sts_seo_schema_settings() { return sts_seo_sanitize_schema_settings( get_option( 'sts_seo_business_schema', array() ) ); } /** * Format one stored field for the Settings API control. * * @param string $key Setting key. * @return string */ function sts_seo_schema_setting_value( $key ) { $settings = sts_seo_schema_settings(); $value = isset( $settings[ $key ] ) ? $settings[ $key ] : ''; if ( 'cuisine' === $key && is_array( $value ) ) { return implode( "\n", $value ); } if ( 'opening_hours' === $key && is_array( $value ) ) { $lines = array(); foreach ( $value as $row ) { $lines[] = implode( ',', $row['days'] ) . '|' . $row['opens'] . '|' . $row['closes']; } return implode( "\n", $lines ); } return is_scalar( $value ) ? (string) $value : ''; } /** * Build one stable, nonduplicated business entity from saved settings. * * @return array JSON-LD data, or an empty array when disabled or incomplete. */ function sts_seo_schema_entity() { $settings = sts_seo_schema_settings(); $type = isset( $settings['type'] ) ? $settings['type'] : ''; if ( ! $type || empty( $settings['name'] ) ) { return array(); } $base = ! empty( $settings['url'] ) ? $settings['url'] : home_url( '/' ); $base = trailingslashit( $base ); $data = array( '@context' => 'https://schema.org', '@type' => $type, '@id' => $base . ( 'Organization' === $type ? '#organization' : '#business' ), 'name' => $settings['name'], ); foreach ( array( 'url', 'telephone' ) as $key ) { if ( ! empty( $settings[ $key ] ) ) { $data[ $key ] = $settings[ $key ]; } } $address_map = array( 'street_address' => 'streetAddress', 'address_locality' => 'addressLocality', 'address_region' => 'addressRegion', 'postal_code' => 'postalCode', 'address_country' => 'addressCountry', ); $address = array( '@type' => 'PostalAddress' ); foreach ( $address_map as $setting => $schema_key ) { if ( ! empty( $settings[ $setting ] ) ) { $address[ $schema_key ] = $settings[ $setting ]; } } if ( count( $address ) > 1 ) { $data['address'] = $address; } if ( ! empty( $settings['founding_year'] ) ) { $data['foundingDate'] = $settings['founding_year']; } if ( in_array( $type, array( 'LocalBusiness', 'Restaurant' ), true ) && ! empty( $settings['opening_hours'] ) ) { $data['openingHoursSpecification'] = array(); foreach ( $settings['opening_hours'] as $row ) { $data['openingHoursSpecification'][] = array( '@type' => 'OpeningHoursSpecification', 'dayOfWeek' => array_map( 'sts_seo_schema_day_url', $row['days'] ), 'opens' => $row['opens'], 'closes' => $row['closes'], ); } } if ( 'Restaurant' === $type ) { if ( ! empty( $settings['cuisine'] ) ) { $data['servesCuisine'] = $settings['cuisine']; } if ( ! empty( $settings['menu_url'] ) ) { $data['menu'] = $settings['menu_url']; } } /** Filter the complete entity. Returning a non-array suppresses output. */ $data = apply_filters( 'sts_seo_schema_entity', $data, $settings ); return is_array( $data ) ? $data : array(); }