*/ namespace RankMath\Admin\Importers; use RankMath\Helper; use RankMath\Admin\Admin_Helper; use RankMath\Helpers\Str; defined( 'ABSPATH' ) || exit; /** * WP_Schema_Pro class. */ class WP_Schema_Pro extends Plugin_Importer { /** * The plugin name. * * @var string */ protected $plugin_name = 'WP Schema Pro'; /** * Plugin options meta key. * * @var string */ protected $meta_key = 'bsf-aiosrs'; /** * Option keys to import and clean. * * @var array */ protected $option_keys = [ 'wp-schema-pro-general-settings', 'wp-schema-pro-social-profiles', 'wp-schema-pro-global-schemas' ]; /** * Choices keys to import. * * @var array */ protected $choices = [ 'settings', 'postmeta' ]; /** * Convert Schema Pro variables if needed. * * @param string $string Value to convert. * * @return string */ public function convert_variables( $string ) { $string = str_replace( 'blogname', '%sitename%', $string ); $string = str_replace( 'blogdescription', '%sitedesc%', $string ); $string = str_replace( 'site_url', get_bloginfo( 'url' ), $string ); $string = str_replace( 'site_logo', get_theme_mod( 'custom_logo' ), $string ); $string = str_replace( 'featured_image', '', $string ); $string = str_replace( 'featured_img', '', $string ); $string = str_replace( 'post_title', '%seo_title%', $string ); $string = str_replace( 'post_excerpt', '%seo_description%', $string ); $string = str_replace( 'post_content', '%seo_description%', $string ); $string = str_replace( 'post_date', '%date%', $string ); $string = str_replace( 'post_modified', '%modified%', $string ); $string = str_replace( 'post_permalink', '', $string ); $string = str_replace( 'author_name', '%name%', $string ); $string = str_replace( 'author_first_name', '%name%', $string ); $string = str_replace( 'author_last_name', '%name%', $string ); $string = str_replace( 'author_image', '', $string ); return $string; } /** * Import settings of plugin. * * @return bool */ protected function settings() { $this->get_settings(); $schema_general = get_option( 'wp-schema-pro-general-settings' ); $schema_social = get_option( 'wp-schema-pro-social-profiles' ); $schema_global = get_option( 'wp-schema-pro-global-schemas' ); // Knowledge Graph Logo. if ( isset( $schema_general['site-logo-custom'] ) ) { $this->replace_image( $schema_general['site-logo-custom'], $this->titles, 'knowledgegraph_logo', 'knowledgegraph_logo_id' ); } // General. $hash = [ 'site-represent' => 'knowledgegraph_type' ]; $has_key = 'person' === $schema_general['site-represent'] ? 'person-name' : 'site-name'; $hash[ $has_key ] = 'knowledgegraph_name'; $this->replace( $hash, $schema_general, $this->titles ); $this->titles['local_seo'] = isset( $schema_general['site-represent'] ) && ! empty( $yoast_titles['site-represent'] ) ? 'on' : 'off'; // Social. $hash = [ 'facebook' => 'social_url_facebook', 'twitter' => 'twitter_author_names', ]; $this->replace( $hash, $schema_social, $this->titles ); // About & Contact Page. $hash = [ 'about-page' => 'local_seo_about_page', 'contact-page' => 'local_seo_contact_page', ]; $this->replace( $hash, $schema_global, $this->titles ); $this->update_settings(); return true; } /** * Import post meta of plugin. * * @return array */ protected function postmeta() { $this->set_pagination( $this->get_post_ids( true ) ); foreach ( $this->get_post_ids() as $snippet_post ) { $post_id = $snippet_post->ID; $snippet = $this->get_snippet_details( $post_id ); if ( ! $snippet ) { continue; } $this->update_postmeta( $post_id, $snippet ); } return $this->get_pagination_arg(); } /** * Update post meta. * * @param int $post_id Post ID. * @param array $snippet Snippet data. */ private function update_postmeta( $post_id, $snippet ) { $type = $snippet['type']; $hash = $this->get_schema_types(); if ( ! isset( $hash[ $type ] ) ) { return; } $details = $snippet['details']; $methods = [ 'work-example' => 'get_book_editions', 'steps' => 'get_howto_steps', 'tool' => 'get_howto_tools', 'supply' => 'get_howto_supplies', 'rating' => 'get_rating', ]; $data = []; foreach ( $hash[ $type ] as $snippet_key => $snippet_value ) { $method = isset( $methods[ $snippet_key ] ) ? $methods[ $snippet_key ] : 'get_schema_meta'; $value = $this->$method( $details, $snippet_key, $post_id, $snippet, $snippet_value ); $this->validate_schema_data( $data, $value, $snippet_value, $snippet_key ); } if ( ! empty( $data ) ) { if ( isset( $data['schema-type'] ) ) { $type = $data['schema-type']; unset( $data['schema-type'] ); } $type = $this->sanitize_schema_type( $type ); $data['@type'] = $type; $data['metadata'] = [ 'title' => Helper::sanitize_schema_title( $type ), 'type' => 'template', 'isPrimary' => 1, 'shortcode' => uniqid( 's-' ), ]; $type = in_array( $type, [ 'BlogPosting', 'NewsArticle' ], true ) ? 'Article' : $type; update_post_meta( $post_id, 'rank_math_schema_' . $type, $data ); } } /** * Validate schema data. * * @param array $data Schema entity data. * @param string $value Entity value. * @param string $key Entity key. * @param string $snippet_key Snippet key. */ private function validate_schema_data( &$data, $value, $key, $snippet_key ) { if ( 'question-answer' === $snippet_key && ! empty( $value ) ) { foreach ( $value as $question ) { $data[ $key ][] = [ '@type' => 'Question', 'name' => $question['question'], 'acceptedAnswer' => [ '@type' => 'Answer', 'text' => $question['answer'], ], ]; } return; } if ( ! Str::contains( '.', $key ) ) { $data[ $key ] = $value; return; } $element = explode( '.', $key ); if ( 2 === count( $element ) ) { $this->add_type( $data[ $element[0] ], $element[0] ); $data[ $element[0] ][ $element[1] ] = $value; return; } if ( count( $element ) > 2 ) { $this->add_type( $data[ $element[0] ], $element[0] ); $this->add_type( $data[ $element[0] ][ $element[1] ], $element[1] ); $data[ $element[0] ][ $element[1] ][ $element[2] ] = $value; } } /** * Add property type. * * @param array $data Schema entity data. * @param string $key Entity key. */ private function add_type( &$data, $key ) { if ( 'location' === $key || 'jobLocation' === $key ) { $data['@type'] = 'Place'; } if ( 'address' === $key ) { $data['@type'] = 'PostalAddress'; } if ( 'offers' === $key ) { $data['@type'] = 'Offer'; } if ( 'brand' === $key ) { $data['@type'] = 'Brand'; } if ( 'review' === $key ) { $data['@type'] = 'Review'; } if ( 'reviewRating' === $key ) { $data['@type'] = 'Rating'; } if ( 'nutrition' === $key ) { $data['@type'] = 'NutritionInformation'; } if ( 'baseSalary' === $key ) { $data['@type'] = 'MonetaryAmount'; } if ( 'value' === $key ) { $data['@type'] = 'QuantitativeValue'; } if ( 'performer' === $key ) { $data['@type'] = 'Person'; } if ( 'provider' === $key || 'hiringOrganization' === $key ) { $data['@type'] = 'Organization'; } } /** * Get ratings value. * * @param array $details Array of details. * @param string $snippet_key Snippet key. * @param string $post_id Post ID. * @param array $snippet Snippet data. * @param string $snippet_value Snippet value. * @return string */ private function get_rating( $details, $snippet_key, $post_id, $snippet, $snippet_value ) { return get_post_meta( $post_id, 'bsf-schema-pro-rating-' . $snippet['id'], true ); } /** * Get post meta for schema plugin * * @param array $details Array of details. * @param string $snippet_key Snippet key. * @param string $post_id Post ID. * @param array $snippet Snippet data. * @param string $snippet_value Snippet value. * @return string */ private function get_schema_meta( $details, $snippet_key, $post_id, $snippet, $snippet_value ) { $value = isset( $details[ $snippet_key ] ) ? $details[ $snippet_key ] : ''; if ( 'custom-text' === $value ) { $value = isset( $details[ $snippet_key . '-custom-text' ] ) ? $details[ $snippet_key . '-custom-text' ] : ''; } if ( 'create-field' === $value ) { $value = get_post_meta( $post_id, $snippet['type'] . '-' . $snippet['id'] . '-' . $snippet_key, true ); } if ( 'specific-field' === $value ) { $key = isset( $details[ $snippet_key . '-specific-field' ] ) ? $details[ $snippet_key . '-specific-field' ] : ''; $value = get_post_meta( $post_id, $key, true ); } return $this->convert_variables( $value ); } /** * Get Book Editions. * * @param array $details Array of details. * @param string $snippet_key Snippet key. * @param string $post_id Post ID. * @param array $snippet Snippet data. * @param string $snippet_value Snippet value. * @return string */ private function get_howto_steps( $details, $snippet_key, $post_id, $snippet, $snippet_value ) { $steps = get_post_meta( $post_id, "how-to-{$snippet['id']}-steps", true ); if ( empty( $steps ) ) { return []; } $data = []; foreach ( $steps as $step ) { $entity = [ '@type' => 'HowToStep', 'name' => $step['name'], 'url' => $step['url'], ]; if ( ! empty( $step['description'] ) ) { $entity['itemListElement'] = [ '@type' => 'HowToDirection', 'text' => $step['description'], ]; } if ( ! empty( $step['image'] ) ) { $entity['image'] = [ '@type' => 'ImageObject', 'text' => wp_get_attachment_url( $step['image'] ), ]; } $data[] = $entity; } return $data; } /** * Get Book Editions. * * @param array $details Array of details. * @param string $snippet_key Snippet key. * @param string $post_id Post ID. * @param array $snippet Snippet data. * @param string $snippet_value Snippet value. * @return string */ private function get_howto_tools( $details, $snippet_key, $post_id, $snippet, $snippet_value ) { $tools = get_post_meta( $post_id, "how-to-{$snippet['id']}-tool", true ); if ( empty( $tools ) ) { return []; } $data = []; foreach ( $tools as $tool ) { $data[] = [ '@type' => 'HowToTool', 'name' => $tool['name'], ]; } return $data; } /** * Get Book Editions. * * @param array $details Array of details. * @param string $snippet_key Snippet key. * @param string $post_id Post ID. * @param array $snippet Snippet data. * @param string $snippet_value Snippet value. * @return string */ private function get_howto_supplies( $details, $snippet_key, $post_id, $snippet, $snippet_value ) { $supplies = get_post_meta( $post_id, "how-to-{$snippet['id']}-supply", true ); if ( empty( $supplies ) ) { return []; } $data = []; foreach ( $supplies as $supply ) { $data[] = [ '@type' => 'HowToSupply', 'name' => $supply['name'], ]; } return $data; } /** * Get Book Editions. * * @param array $details Array of details. * @param string $snippet_key Snippet key. * @param string $post_id Post ID. * @param array $snippet Snippet data. * @param string $snippet_value Snippet value. * @return string */ private function get_book_editions( $details, $snippet_key, $post_id, $snippet, $snippet_value ) { if ( empty( $details[ $snippet_key ] ) ) { return ''; } $editions = []; $data = [ 'details' => $details, 'snippet_key' => $snippet_key, 'post_id' => $post_id, 'snippet' => $snippet, 'snippet_value' => $snippet_value, ]; foreach ( $details[ $snippet_key ] as $key => $edition ) { $editions[] = [ 'book_edition' => $this->normalize_edition( $key . '-book-edition', $edition['book-edition'], $data ), 'isbn' => $this->normalize_edition( $key . '-serial-number', $edition['serial-number'], $data ), 'url' => $this->normalize_edition( $key . '-url-template', $edition['url-template'], $data ), 'book_format' => $this->normalize_edition( $key . '-book-format', $edition['book-format'], $data ), ]; } return $editions; } /** * Normalize Book Edition. * * @param string $key Custom field key. * @param string $value Custom field value. * @param array $data Snippet data. * @return string */ private function normalize_edition( $key, $value, $data ) { if ( ! $value ) { return ''; } $hash = [ 'custom-text' => 'get_custom_text', 'create-field' => 'get_created_field', 'specific-field' => 'get_specific_field', ]; if ( isset( $hash[ $value ] ) ) { $method = $hash[ $value ]; $value = $this->$method( $key, $value, $data ); } return $this->convert_variables( $value ); } /** * Get Custom Text added in the Settings. * * @param string $key Custom field key. * @param string $value Custom field value. * @param array $data Snippet data. * @return string */ private function get_custom_text( $key, $value, $data ) { $key = $data['snippet_key'] . '-custom-text'; return isset( $data['details'][ $key ] ) ? $data['details'][ $key ] : ''; } /** * Get Created field value added in the post metabox. * * @param string $key Custom field key. * @param string $value Custom field value. * @param array $data Snippet data. * @return string */ private function get_created_field( $key, $value, $data ) { $meta_key = $data['snippet']['type'] . '-' . $data['snippet']['id'] . '-' . $data['snippet_key'] . '-' . $key; return get_post_meta( $data['post_id'], $meta_key, true ); } /** * Get Specific Custom field value. * * @param string $key Custom field key. * @param string $value Custom field value. * @param array $data Snippet data. * @return string */ private function get_specific_field( $key, $value, $data ) { $key = isset( $data['details'][ $data[ $snippet_key . '-specific-field' ] ] ) ? $data['details'][ $data[ $snippet_key . '-specific-field' ] ] : ''; return get_post_meta( $data['post_id'], $key, true ); } /** * Sanitize schema type before saving * * @param string $type Schema type to sanitize. * @return string */ private function sanitize_schema_type( $type ) { $hash = [ 'job-posting' => 'JobPosting', 'video-object' => 'VideoObject', 'software-application' => 'SoftwareApplication', 'faq' => 'FAQPage', 'how-to' => 'HowTo', ]; $type = in_array( $type, [ 'AdvertiserContentArticle', 'Report', 'SatiricalArticle', 'ScholarlyArticle', 'TechArticle' ], true ) ? 'Article' : $type; return isset( $hash[ $type ] ) ? $hash[ $type ] : ucfirst( $type ); } /** * Get Snippet Details stored in aiosrs-schema posts * * @param int $post_id Post ID. * @return array */ private function get_snippet_details( $post_id ) { global $wpdb; $post_type = addcslashes( get_post_type( $post_id ), '_' ); $query = "SELECT p.ID, pm.meta_value FROM {$wpdb->postmeta} as pm INNER JOIN {$wpdb->posts} as p ON pm.post_id = p.ID WHERE pm.meta_key = 'bsf-aiosrs-schema-location' AND p.post_type = 'aiosrs-schema' AND p.post_status = 'publish'"; $orderby = ' ORDER BY p.post_date DESC LIMIT 1'; $meta_args = "pm.meta_value LIKE '%\"basic-global\"%'"; $meta_args .= " OR pm.meta_value LIKE '%\"basic-singulars\"%'"; $meta_args .= " OR pm.meta_value LIKE '%\"{$post_type}|all\"%'"; $meta_args .= " OR pm.meta_value LIKE '%\"post-{$post_id}\"%'"; $local_posts = $wpdb->get_col( $query . ' AND (' . $meta_args . ')' . $orderby ); // phpcs:ignore if ( empty( $local_posts ) ) { return false; } $current_page_data = []; foreach ( $local_posts as $local_post ) { $snippet_type = get_post_meta( $local_post, 'bsf-aiosrs-schema-type', true ); return [ 'id' => $local_post, 'type' => $snippet_type, 'details' => get_post_meta( $local_post, 'bsf-aiosrs-' . $snippet_type, true ), ]; } } /** * Get the actions which can be performed for the plugin. * * @return array */ public function get_choices() { return [ 'settings' => esc_html__( 'Import Settings', 'rank-math' ) . Admin_Helper::get_tooltip( esc_html__( 'Plugin settings and site-wide meta data.', 'rank-math' ) ), 'postmeta' => esc_html__( 'Import Schemas', 'rank-math' ) . Admin_Helper::get_tooltip( esc_html__( 'Import all Schema data for Posts, Pages, and custom post types.', 'rank-math' ) ), ]; } /** * Get schema types * * @return array */ private function get_schema_types() { return [ 'event' => $this->get_event_fields(), 'job-posting' => $this->get_job_posting_fields(), 'product' => $this->get_product_fields(), 'recipe' => $this->get_recipe_fields(), 'software-application' => $this->get_software_fields(), 'video-object' => $this->get_video_fields(), 'article' => [ 'name' => 'headline', 'description' => 'description', 'schema-type' => 'schema-type', ], 'book' => [ 'name' => 'name', 'url' => 'url', 'author' => 'author.name', 'work-example' => 'book_editions', 'rating' => 'review.reviewRating.ratingValue', ], 'course' => [ 'name' => 'name', 'description' => 'description', 'orgnization-name' => 'provider.name', 'same-as' => 'provider.sameAs', 'rating' => 'review.reviewRating.ratingValue', ], 'person' => [ 'name' => 'name', 'email' => 'email', 'gender' => 'gender', 'job-title' => 'jobTitle', 'street' => 'address.streetAddress', 'locality' => 'address.addressLocality', 'region' => 'address.addressRegion', 'postal' => 'address.postalCode', 'country' => 'address.addressCountry', ], 'service' => [ 'name' => 'name', 'description' => 'description', 'type' => 'serviceType', 'price-range' => 'offers.price', ], 'faq' => [ 'question-answer' => 'mainEntity', ], 'how-to' => [ 'name' => 'name', 'description' => 'description', 'total-time' => 'totalTime', 'steps' => 'step', 'supply' => 'supply', 'tool' => 'tool', ], ]; } /** * Get event fields. * * @return array */ private function get_event_fields() { return [ 'name' => 'name', 'description' => 'description', 'schema-type' => 'schema-type', 'event-status' => 'eventStatus', 'event-attendance-mode' => 'eventAttendanceMode', 'start-date' => 'startDate', 'end-date' => 'endDate', 'location' => 'location.name', 'location-street' => 'location.address.streetAddress', 'location-locality' => 'location.address.addressLocality', 'location-region' => 'location.address.addressRegion', 'location-postal' => 'location.address.postalCode', 'location-country' => 'location.address.addressCountry', 'ticket-buy-url' => 'offers.url', 'price' => 'offers.price', 'currency' => 'offers.priceCurrency', 'avail' => 'offers.availability', 'performer' => 'performer.name', 'rating' => 'review.reviewRating.ratingValue', ]; } /** * Get job_posting fields. * * @return array */ private function get_job_posting_fields() { return [ 'title' => 'title', 'description' => 'description', 'job-type' => 'employmentType', 'start-date' => 'datePosted', 'expiry-date' => 'validThrough', 'orgnization-name' => 'hiringOrganization.name', 'same-as' => 'hiringOrganization.sameAs', 'organization-logo' => 'hiringOrganization.logo', 'location-street' => 'jobLocation.address.streetAddress', 'location-locality' => 'jobLocation.address.addressLocality', 'location-region' => 'jobLocation.address.addressRegion', 'location-postal' => 'jobLocation.address.postalCode', 'location-country' => 'jobLocation.address.addressCountry', 'salary' => 'baseSalary.value.value', 'salary-currency' => 'baseSalary.currency', 'salary-unit' => 'baseSalary.value.unitText', ]; } /** * Get product fields. * * @return array */ private function get_product_fields() { return [ 'name' => 'name', 'description' => 'description', 'sku' => 'sku', 'brand-name' => 'brand.name', 'price' => 'offers.price', 'currency' => 'offers.priceCurrency', 'avail' => 'offers.availability', 'price-valid-until' => 'offers.priceValidUntil', 'rating' => 'review.reviewRating.ratingValue', ]; } /** * Get recipe fields. * * @return array */ private function get_recipe_fields() { return [ 'name' => 'name', 'description' => 'description', 'recipe-category' => 'recipeCategory', 'recipe-cuisine' => 'recipeCuisine', 'recipe-yield' => 'recipeYield', 'recipe-keywords' => 'keywords', 'nutrition' => 'nutrition.calories', 'preperation-time' => 'prepTime', 'cook-time' => 'cookTime', 'ingredients' => 'recipeIngredient', 'rating' => 'review.reviewRating.ratingValue', ]; } /** * Get software fields. * * @return array */ private function get_software_fields() { return [ 'name' => 'name', 'rating' => 'review.reviewRating.ratingValue', 'price' => 'offers.price', 'currency' => 'offers.priceCurrency', 'operating-system' => 'operatingSystem', 'category' => 'applicationCategory', ]; } /** * Get video fields. * * @return array */ private function get_video_fields() { return [ 'name' => 'name', 'description' => 'description', 'content-url' => 'contentUrl', 'embed-url' => 'embedUrl', 'duration' => 'duration', 'rating' => 'review.reviewRating.ratingValue', ]; } }