Commit realizado el 12:13:52 08-04-2024
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 74 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* The Block Admin
|
||||
*
|
||||
* @since 1.0.104
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema\Blocks;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Traits\Hooker;
|
||||
use RankMath\Helpers\Arr;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Block Admin class.
|
||||
*/
|
||||
class Admin {
|
||||
|
||||
use Hooker;
|
||||
|
||||
/**
|
||||
* The Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->filter( 'rank_math/settings/general', 'add_general_settings' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add block settings into general optional panel.
|
||||
*
|
||||
* @param array $tabs Array of option panel tabs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_general_settings( $tabs ) {
|
||||
Arr::insert(
|
||||
$tabs,
|
||||
[
|
||||
'blocks' => [
|
||||
'icon' => 'rm-icon rm-icon-stories',
|
||||
'title' => esc_html__( 'Blocks', 'rank-math' ),
|
||||
'desc' => esc_html__( 'Take control over the default settings available for Rank Math Blocks.', 'rank-math' ),
|
||||
'file' => dirname( __FILE__ ) . '/views/options-general.php',
|
||||
],
|
||||
],
|
||||
7
|
||||
);
|
||||
|
||||
return $tabs;
|
||||
}
|
||||
}
|
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
/**
|
||||
* The FAQ Block
|
||||
*
|
||||
* @since 0.9.0
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use WP_Block_Type_Registry;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Block_FAQ class.
|
||||
*/
|
||||
class Block_FAQ extends Block {
|
||||
|
||||
/**
|
||||
* Block type name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $block_type = 'rank-math/faq-block';
|
||||
|
||||
/**
|
||||
* The single instance of the class.
|
||||
*
|
||||
* @var Block_FAQ
|
||||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* Retrieve main Block_FAQ instance.
|
||||
*
|
||||
* Ensure only one instance is loaded or can be loaded.
|
||||
*
|
||||
* @return Block_FAQ
|
||||
*/
|
||||
public static function get() {
|
||||
if ( is_null( self::$instance ) && ! ( self::$instance instanceof Block_FAQ ) ) {
|
||||
self::$instance = new Block_FAQ();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
if ( WP_Block_Type_Registry::get_instance()->is_registered( $this->block_type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
register_block_type(
|
||||
$this->block_type,
|
||||
[
|
||||
'render_callback' => [ $this, 'render' ],
|
||||
'editor_style' => 'rank-math-block-admin',
|
||||
'attributes' => [
|
||||
'listStyle' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'titleWrapper' => [
|
||||
'type' => 'string',
|
||||
'default' => 'h3',
|
||||
],
|
||||
'sizeSlug' => [
|
||||
'type' => 'string',
|
||||
'default' => 'thumbnail',
|
||||
],
|
||||
'questions' => [
|
||||
'type' => 'array',
|
||||
'default' => [],
|
||||
'items' => [ 'type' => 'object' ],
|
||||
],
|
||||
'listCssClasses' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'titleCssClasses' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'contentCssClasses' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'textAlign' => [
|
||||
'type' => 'string',
|
||||
'default' => 'left',
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
add_filter( 'rank_math/schema/block/faq-block', [ $this, 'add_graph' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add FAQ schema data in JSON-LD array.
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param array $block JsonLD Instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_graph( $data, $block ) {
|
||||
// Early bail.
|
||||
if ( ! $this->has_questions( $block['attrs'] ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( ! isset( $data['faqs'] ) ) {
|
||||
$data['faqs'] = [
|
||||
'@type' => 'FAQPage',
|
||||
'mainEntity' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$permalink = get_permalink() . '#';
|
||||
foreach ( $block['attrs']['questions'] as $question ) {
|
||||
if ( empty( $question['title'] ) || empty( $question['content'] ) || empty( $question['visible'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$question['title'] = do_shortcode( $question['title'] );
|
||||
$question['content'] = do_shortcode( $question['content'] );
|
||||
|
||||
if ( empty( $question['id'] ) ) {
|
||||
$question['id'] = 'rm-faq-' . md5( $question['title'] );
|
||||
}
|
||||
|
||||
$data['faqs']['mainEntity'][] = [
|
||||
'@type' => 'Question',
|
||||
'url' => $permalink . $question['id'],
|
||||
'name' => wp_strip_all_tags( $question['title'] ),
|
||||
'acceptedAnswer' => [
|
||||
'@type' => 'Answer',
|
||||
'text' => $this->clean_text( $question['content'] ),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render block content.
|
||||
*
|
||||
* @param array $attributes Array of atributes.
|
||||
* @return string
|
||||
*/
|
||||
public static function markup( $attributes = [] ) {
|
||||
$list_tag = self::get()->get_list_style( $attributes['listStyle'] );
|
||||
$item_tag = self::get()->get_list_item_style( $attributes['listStyle'] );
|
||||
$class = 'rank-math-block';
|
||||
if ( ! empty( $attributes['className'] ) ) {
|
||||
$class .= ' ' . esc_attr( $attributes['className'] );
|
||||
}
|
||||
|
||||
// HTML.
|
||||
$out = [];
|
||||
$out[] = sprintf( '<div id="rank-math-faq" class="%1$s"%2$s>', $class, self::get()->get_styles( $attributes ) );
|
||||
$out[] = sprintf( '<%1$s class="rank-math-list %2$s">', $list_tag, esc_attr( $attributes['listCssClasses'] ) );
|
||||
|
||||
// Questions.
|
||||
foreach ( $attributes['questions'] as $question ) {
|
||||
if ( empty( $question['title'] ) || empty( $question['content'] ) || empty( $question['visible'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( empty( $question['id'] ) ) {
|
||||
$question['id'] = 'rm-faq-' . md5( $question['title'] );
|
||||
}
|
||||
|
||||
$out[] = sprintf( '<%1$s id="%2$s" class="rank-math-list-item">', $item_tag, $question['id'] );
|
||||
|
||||
$out[] = sprintf(
|
||||
'<%1$s class="rank-math-question %2$s">%3$s</%1$s>',
|
||||
apply_filters( 'rank_math/blocks/faq/title_wrapper', esc_attr( $attributes['titleWrapper'] ) ),
|
||||
esc_attr( $attributes['titleCssClasses'] ),
|
||||
wp_kses_post( $question['title'] )
|
||||
);
|
||||
|
||||
$out[] = '<div class="rank-math-answer ' . esc_attr( $attributes['contentCssClasses'] ) . '">';
|
||||
|
||||
if ( ! empty( $question['imageUrl'] ) ) {
|
||||
$out[] = '<img src="' . esc_url( $question['imageUrl'] ) . '" />';
|
||||
} else {
|
||||
$out[] = self::get()->get_image( $question, $attributes['sizeSlug'] );
|
||||
}
|
||||
|
||||
$out[] = self::get()->normalize_text( $question['content'], 'faq' );
|
||||
$out[] = '</div>';
|
||||
|
||||
$out[] = sprintf( '</%1$s>', $item_tag );
|
||||
}
|
||||
|
||||
$out[] = sprintf( '</%1$s>', $list_tag );
|
||||
$out[] = '</div>';
|
||||
|
||||
return join( "\n", $out );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render block content
|
||||
*
|
||||
* @param array $attributes Array of atributes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render( $attributes ) {
|
||||
// Early bail.
|
||||
if ( ! $this->has_questions( $attributes ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return self::markup( $attributes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if FAQ block has questions data.
|
||||
*
|
||||
* @param array $attributes Array of attributes.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function has_questions( $attributes ) {
|
||||
return ! isset( $attributes['questions'] ) || empty( $attributes['questions'] ) ? false : true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,513 @@
|
||||
<?php
|
||||
/**
|
||||
* The HowTo Block
|
||||
*
|
||||
* @since 0.9.0
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Paper\Paper;
|
||||
use RankMath\Helpers\Str;
|
||||
use RankMath\Helpers\Attachment;
|
||||
use WP_Block_Type_Registry;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* HowTo Block class.
|
||||
*/
|
||||
class Block_HowTo extends Block {
|
||||
|
||||
/**
|
||||
* Block type name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $block_type = 'rank-math/howto-block';
|
||||
|
||||
/**
|
||||
* The single instance of the class.
|
||||
*
|
||||
* @var Block_HowTo
|
||||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* Retrieve main Block_HowTo instance.
|
||||
*
|
||||
* Ensure only one instance is loaded or can be loaded.
|
||||
*
|
||||
* @return Block_HowTo
|
||||
*/
|
||||
public static function get() {
|
||||
if ( is_null( self::$instance ) && ! ( self::$instance instanceof Block_HowTo ) ) {
|
||||
self::$instance = new Block_HowTo();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( WP_Block_Type_Registry::get_instance()->is_registered( $this->block_type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
register_block_type(
|
||||
$this->block_type,
|
||||
[
|
||||
'render_callback' => [ $this, 'render' ],
|
||||
'editor_style' => 'rank-math-block-admin',
|
||||
'attributes' => [
|
||||
'hasDuration' => [
|
||||
'type' => 'boolean',
|
||||
'default' => false,
|
||||
],
|
||||
'days' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'hours' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'minutes' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'description' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'steps' => [
|
||||
'type' => 'array',
|
||||
'default' => [],
|
||||
'items' => [ 'type' => 'object' ],
|
||||
],
|
||||
'sizeSlug' => [
|
||||
'type' => 'string',
|
||||
'default' => 'full',
|
||||
],
|
||||
'imageID' => [
|
||||
'type' => 'integer',
|
||||
],
|
||||
'mainSizeSlug' => [
|
||||
'type' => 'string',
|
||||
'default' => 'full',
|
||||
],
|
||||
'listStyle' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'timeLabel' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'titleWrapper' => [
|
||||
'type' => 'string',
|
||||
'default' => 'h3',
|
||||
],
|
||||
'listCssClasses' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'titleCssClasses' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'contentCssClasses' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'textAlign' => [
|
||||
'type' => 'string',
|
||||
'default' => 'left',
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
add_filter( 'rank_math/schema/block/howto-block', [ $this, 'add_graph' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add HowTO schema data in JSON-LD array..
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param array $block JsonLD Instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_graph( $data, $block ) {
|
||||
// Early bail.
|
||||
if ( ! $this->has_steps( $block['attrs'] ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$attrs = $block['attrs'];
|
||||
|
||||
if ( ! isset( $data['howto'] ) ) {
|
||||
$data['howto'] = [
|
||||
'@type' => 'HowTo',
|
||||
'name' => Paper::get()->get_title(),
|
||||
'description' => isset( $attrs['description'] ) ? $this->clean_text( do_shortcode( $attrs['description'] ) ) : '',
|
||||
'totalTime' => '',
|
||||
'step' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$this->add_step_image( $data['howto'], $attrs );
|
||||
$this->add_duration( $data['howto'], $attrs );
|
||||
$permalink = get_permalink() . '#';
|
||||
|
||||
foreach ( $attrs['steps'] as $index => $step ) {
|
||||
if ( empty( $step['visible'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$schema_step = $this->add_step( $step, $permalink . $step['id'] );
|
||||
if ( $schema_step ) {
|
||||
$data['howto']['step'][] = $schema_step;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render block content.
|
||||
*
|
||||
* @param array $attributes Array of atributes.
|
||||
* @return string
|
||||
*/
|
||||
public static function markup( $attributes = [] ) {
|
||||
$list_style = isset( $attributes['listStyle'] ) ? esc_attr( $attributes['listStyle'] ) : '';
|
||||
$list_css_classes = isset( $attributes['listCssClasses'] ) ? esc_attr( $attributes['listCssClasses'] ) : '';
|
||||
$title_wrapper = isset( $attributes['titleWrapper'] ) ? esc_attr( $attributes['titleWrapper'] ) : 'h2';
|
||||
$title_css_classes = isset( $attributes['titleCssClasses'] ) ? esc_attr( $attributes['titleCssClasses'] ) : '';
|
||||
$content_css_classes = isset( $attributes['contentCssClasses'] ) ? esc_attr( $attributes['contentCssClasses'] ) : '';
|
||||
$size_slug = isset( $attributes['sizeSlug'] ) ? esc_attr( $attributes['sizeSlug'] ) : '';
|
||||
|
||||
$list_tag = self::get()->get_list_style( $list_style );
|
||||
$item_tag = self::get()->get_list_item_style( $list_style );
|
||||
$class = 'rank-math-block';
|
||||
if ( ! empty( $attributes['className'] ) ) {
|
||||
$class .= ' ' . esc_attr( $attributes['className'] );
|
||||
}
|
||||
|
||||
// HTML.
|
||||
$out = [];
|
||||
$out[] = sprintf( '<div id="rank-math-howto" class="%1$s" %2$s>', $class, self::get()->get_styles( $attributes ) );
|
||||
|
||||
// HeaderContent.
|
||||
$out[] = '<div class="rank-math-howto-description">';
|
||||
|
||||
if ( ! empty( $attributes['imageUrl'] ) ) {
|
||||
$out[] = '<img src="' . esc_url( $attributes['imageUrl'] ) . '" />';
|
||||
} elseif ( ! empty( $attributes['mainSizeSlug'] ) ) {
|
||||
$out[] = self::get()->get_image( $attributes, $attributes['mainSizeSlug'], '' );
|
||||
}
|
||||
|
||||
if ( ! empty( $attributes['description'] ) ) {
|
||||
$out[] = self::get()->normalize_text( $attributes['description'], 'howto' );
|
||||
}
|
||||
|
||||
$out[] = '</div>';
|
||||
|
||||
$out[] = self::get()->build_duration( $attributes );
|
||||
|
||||
$out[] = sprintf( '<%1$s class="rank-math-steps %2$s">', $list_tag, $list_css_classes );
|
||||
|
||||
// Steps.
|
||||
foreach ( $attributes['steps'] as $index => $step ) {
|
||||
if ( empty( $step['visible'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$step_id = isset( $step['id'] ) ? esc_attr( $step['id'] ) : '';
|
||||
|
||||
$out[] = sprintf( '<%1$s id="%2$s" class="rank-math-step">', $item_tag, $step_id );
|
||||
|
||||
if ( ! empty( $step['title'] ) ) {
|
||||
$out[] = sprintf(
|
||||
'<%1$s class="rank-math-step-title %2$s">%3$s</%1$s>',
|
||||
$title_wrapper,
|
||||
$title_css_classes,
|
||||
$step['title']
|
||||
);
|
||||
}
|
||||
|
||||
$step_content = ! empty( $step['content'] ) ? self::get()->normalize_text( $step['content'], 'howto' ) : '';
|
||||
$step_image = ! empty( $step['imageUrl'] ) ? '<img src="' . esc_url( $step['imageUrl'] ) . '" />' : self::get()->get_image( $step, $size_slug, '' );
|
||||
|
||||
$out[] = sprintf(
|
||||
'<div class="rank-math-step-content %2$s">%4$s%3$s</div>',
|
||||
$title_wrapper,
|
||||
$content_css_classes,
|
||||
$step_content,
|
||||
$step_image
|
||||
);
|
||||
|
||||
$out[] = sprintf( '</%1$s>', $item_tag );
|
||||
}
|
||||
|
||||
$out[] = sprintf( '</%1$s>', $list_tag );
|
||||
$out[] = '</div>';
|
||||
|
||||
return apply_filters( 'rank_math/schema/block/howto/content', join( "\n", $out ), $out, $attributes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render block content.
|
||||
*
|
||||
* @param array $attributes Array of atributes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render( $attributes ) {
|
||||
// Early bail.
|
||||
if ( ! $this->has_steps( $attributes ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return self::markup( $attributes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Step
|
||||
*
|
||||
* @param array $step Step.
|
||||
* @param string $permalink Permalink.
|
||||
*/
|
||||
private function add_step( $step, $permalink ) {
|
||||
$name = wp_strip_all_tags( do_shortcode( $step['title'] ) );
|
||||
$text = $this->clean_text( do_shortcode( $step['content'] ) );
|
||||
|
||||
if ( empty( $name ) && empty( $text ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$schema_step = [
|
||||
'@type' => 'HowToStep',
|
||||
'url' => '' . $permalink,
|
||||
];
|
||||
|
||||
if ( empty( $name ) ) {
|
||||
$schema_step['text'] = '';
|
||||
|
||||
if ( empty( $text ) && empty( $schema_step['image'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! empty( $text ) ) {
|
||||
$schema_step['text'] = $text;
|
||||
}
|
||||
} elseif ( empty( $text ) ) {
|
||||
$schema_step['text'] = $name;
|
||||
} else {
|
||||
$schema_step['name'] = $name;
|
||||
if ( ! empty( $text ) ) {
|
||||
$schema_step['itemListElement'] = [
|
||||
[
|
||||
'@type' => 'HowToDirection',
|
||||
'text' => $text,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ( false === $this->add_step_image( $schema_step, $step ) ) {
|
||||
$this->add_step_image_from_content( $schema_step, $step );
|
||||
}
|
||||
|
||||
return $schema_step;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we have an inline image and add it.
|
||||
*
|
||||
* @param array $schema_step Our Schema output for the Step.
|
||||
* @param array $step The step block data.
|
||||
*/
|
||||
private function add_step_image_from_content( &$schema_step, $step ) {
|
||||
// Early Bail.
|
||||
if ( empty( $step['content'] ) || ! Str::contains( '<img', $step['content'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Search for image.
|
||||
preg_match_all( '/<img.+?src=[\'"]([^\'"]+)[\'"].*?>/i', $step['content'], $matches );
|
||||
|
||||
if ( ! isset( $matches[1][0] ) || empty( $matches[1][0] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$schema_image = [
|
||||
'@type' => 'ImageObject',
|
||||
'url' => $matches[1][0],
|
||||
];
|
||||
|
||||
$image_id = Attachment::get_by_url( $schema_image['url'] );
|
||||
|
||||
if ( $image_id > 0 ) {
|
||||
$this->add_caption( $schema_image, $image_id );
|
||||
$this->add_image_size( $schema_image, $image_id );
|
||||
}
|
||||
|
||||
$schema_step['image'] = $schema_image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we have a step image and add it.
|
||||
*
|
||||
* @copyright Copyright (C) 2008-2019, Yoast BV
|
||||
* The following code is a derivative work of the code from the Yoast(https://github.com/Yoast/wordpress-seo/), which is licensed under GPL v3.
|
||||
*
|
||||
* @param array $schema_step Our Schema output for the Step.
|
||||
* @param array $step The step block data.
|
||||
*/
|
||||
private function add_step_image( &$schema_step, $step ) {
|
||||
if ( empty( $step['imageID'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$image_id = absint( $step['imageID'] );
|
||||
if ( ! ( $image_id > 0 ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$image_url = wp_get_attachment_image_url( $image_id, 'full' );
|
||||
if ( ! $image_url ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$schema_image = [
|
||||
'@type' => 'ImageObject',
|
||||
'url' => $image_url,
|
||||
];
|
||||
|
||||
$this->add_caption( $schema_image, $image_id );
|
||||
$this->add_image_size( $schema_image, $image_id );
|
||||
|
||||
$schema_step['image'] = $schema_image;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add caption to schema.
|
||||
*
|
||||
* @param array $schema_image Our Schema output for the Image.
|
||||
* @param int $image_id The image ID.
|
||||
*/
|
||||
private function add_caption( &$schema_image, $image_id ) {
|
||||
$caption = wp_get_attachment_caption( $image_id );
|
||||
if ( ! empty( $caption ) ) {
|
||||
$schema_image['caption'] = $caption;
|
||||
return;
|
||||
}
|
||||
|
||||
$caption = Attachment::get_alt_tag( $image_id );
|
||||
if ( ! empty( $caption ) ) {
|
||||
$schema_image['caption'] = $caption;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add image size to schema.
|
||||
*
|
||||
* @param array $schema_image Our Schema output for the Image.
|
||||
* @param int $image_id The image ID.
|
||||
*/
|
||||
private function add_image_size( &$schema_image, $image_id ) {
|
||||
$image_meta = wp_get_attachment_metadata( $image_id );
|
||||
if ( empty( $image_meta['width'] ) || empty( $image_meta['height'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$schema_image['width'] = $image_meta['width'];
|
||||
$schema_image['height'] = $image_meta['height'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add duration to schema.
|
||||
*
|
||||
* @param array $data Our Schema output.
|
||||
* @param array $attrs The block attributes.
|
||||
*/
|
||||
private function add_duration( &$data, $attrs ) {
|
||||
if ( ! empty( $attrs['hasDuration'] ) ) {
|
||||
$days = absint( $attrs['days'] ?? 0 );
|
||||
$hours = absint( $attrs['hours'] ?? 0 );
|
||||
$minutes = absint( $attrs['minutes'] ?? 0 );
|
||||
if ( ( $days + $hours + $minutes ) > 0 ) {
|
||||
$data['totalTime'] = esc_attr( 'P' . $days . 'DT' . $hours . 'H' . $minutes . 'M' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate HowTo duration property.
|
||||
*
|
||||
* @param array $attrs The block attributes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function build_duration( $attrs ) {
|
||||
if ( empty( $attrs['hasDuration'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$days = isset( $attrs['days'] ) ? absint( $attrs['days'] ) : 0;
|
||||
$hours = isset( $attrs['hours'] ) ? absint( $attrs['hours'] ) : 0;
|
||||
$minutes = isset( $attrs['minutes'] ) ? absint( $attrs['minutes'] ) : 0;
|
||||
|
||||
$elements = [];
|
||||
if ( $days > 0 ) {
|
||||
/* translators: %d is the number of days. */
|
||||
$elements[] = sprintf( _n( '%d day', '%d days', $days, 'rank-math' ), $days );
|
||||
}
|
||||
|
||||
if ( $hours > 0 ) {
|
||||
/* translators: %d is the number of hours. */
|
||||
$elements[] = sprintf( _n( '%d hour', '%d hours', $hours, 'rank-math' ), $hours );
|
||||
}
|
||||
|
||||
if ( $minutes > 0 ) {
|
||||
/* translators: %d is the number of minutes. */
|
||||
$elements[] = sprintf( _n( '%d minute', '%d minutes', $minutes, 'rank-math' ), $minutes );
|
||||
}
|
||||
|
||||
$count = count( $elements );
|
||||
$formats = [
|
||||
1 => '%1$s',
|
||||
/* translators: placeholders are units of time, e.g. '1 hour and 30 minutes' */
|
||||
2 => __( '%1$s and %2$s', 'rank-math' ),
|
||||
/* translators: placeholders are units of time, e.g. '1 day, 8 hours and 30 minutes' */
|
||||
3 => __( '%1$s, %2$s and %3$s', 'rank-math' ),
|
||||
];
|
||||
|
||||
return sprintf(
|
||||
'<p class="rank-math-howto-duration"><strong>%2$s</strong> <span>%1$s</span></p>',
|
||||
isset( $formats[ $count ] ) ? vsprintf( $formats[ $count ], $elements ) : '',
|
||||
empty( $attrs['timeLabel'] ) ? __( 'Total Time:', 'rank-math' ) : esc_html( $attrs['timeLabel'] )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to check the HowTo block have steps data.
|
||||
*
|
||||
* @param array $attributes Array of attributes.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function has_steps( $attributes ) {
|
||||
return ! isset( $attributes['steps'] ) || empty( $attributes['steps'] ) ? false : true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* The Block Parser
|
||||
*
|
||||
* @since 0.9.0
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Helpers\Str;
|
||||
use RankMath\Traits\Hooker;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Block_Parser class.
|
||||
*/
|
||||
class Block_Parser {
|
||||
|
||||
use Hooker;
|
||||
|
||||
/**
|
||||
* Holds the parsed blocks.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $blocks = [];
|
||||
|
||||
/**
|
||||
* The Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->action( 'rank_math/json_ld', 'parse', 8 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter function to add Blocks data in schema.
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function parse( $data ) {
|
||||
if ( ! function_exists( 'parse_blocks' ) || ! is_singular() ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->get_parsed_blocks();
|
||||
foreach ( $this->blocks as $block_type => $blocks ) {
|
||||
foreach ( $blocks as $block ) {
|
||||
/**
|
||||
* Filter: 'rank_math/schema/block/<block-type>' - Allows filtering graph output per block.
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param array $block The block.
|
||||
*/
|
||||
$data = $this->do_filter( 'schema/block/' . $block_type, $data, $block );
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the blocks and loop through them.
|
||||
*/
|
||||
private function get_parsed_blocks() {
|
||||
$post = get_post();
|
||||
$parsed_blocks = parse_blocks( $post->post_content );
|
||||
|
||||
/**
|
||||
* Filter: 'rank_math/schema/block/before_filter'
|
||||
*
|
||||
* @param array $parsed_blocks Array of parsed blocks.
|
||||
*/
|
||||
$parsed_blocks = $this->do_filter( 'schema/block/before_filter', $parsed_blocks );
|
||||
|
||||
$this->filter_blocks( $parsed_blocks );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter blocks.
|
||||
*
|
||||
* @param array $blocks Blocks to filter.
|
||||
*/
|
||||
private function filter_blocks( $blocks ) {
|
||||
foreach ( $blocks as $block ) {
|
||||
if ( $this->is_nested_block( $block ) || ! $this->is_valid_block( $block ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = \str_replace( 'rank-math/', '', $block['blockName'] );
|
||||
$name = strtolower( $name );
|
||||
if ( ! isset( $this->blocks[ $name ] ) || ! is_array( $this->blocks[ $name ] ) ) {
|
||||
$this->blocks[ $name ] = [];
|
||||
}
|
||||
|
||||
$this->blocks[ $name ][] = $block;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is nested block.
|
||||
*
|
||||
* @param array $block Block.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function is_nested_block( $block ) {
|
||||
if ( empty( $block['blockName'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: 'rank_math/schema/nested_blocks' - Allows filtering for nested blocks.
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param array $block The block.
|
||||
*/
|
||||
$nested = $this->do_filter(
|
||||
'schema/nested_blocks',
|
||||
[
|
||||
'core/group',
|
||||
'core/columns',
|
||||
'core/column',
|
||||
]
|
||||
);
|
||||
|
||||
if ( ! in_array( $block['blockName'], $nested, true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->filter_blocks( $block['innerBlocks'] );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is block valid.
|
||||
*
|
||||
* @param array $block Block.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function is_valid_block( $block ) {
|
||||
return ! empty( $block['blockName'] ) && Str::starts_with( 'rank-math', $block['blockName'] );
|
||||
}
|
||||
}
|
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
/**
|
||||
* The Block Base
|
||||
*
|
||||
* @since 0.9.0
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Traits\Hooker;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Block class.
|
||||
*/
|
||||
class Block {
|
||||
|
||||
/**
|
||||
* Function to certain tags from the text.
|
||||
*
|
||||
* @param string $text Block content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function clean_text( $text ) {
|
||||
return strip_tags( $text, '<h1><h2><h3><h4><h5><h6><br><ol><ul><li><a><p><b><strong><i><em>' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get the block image.
|
||||
*
|
||||
* @param array $attrs Block attributes data.
|
||||
* @param string $size Image size.
|
||||
* @param string $class Attachment image class.
|
||||
*
|
||||
* @return string The HTML image element.
|
||||
*/
|
||||
protected function get_image( $attrs, $size = 'thumbnail', $class = 'class=alignright' ) {
|
||||
$image_id = empty( $attrs['imageID'] ) ? '' : absint( $attrs['imageID'] );
|
||||
if ( ! $image_id ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$html = wp_get_attachment_image( $image_id, $size, false, $class );
|
||||
|
||||
return $html ? $html : wp_get_attachment_image( $image_id, 'full', false, $class );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get styles
|
||||
*
|
||||
* @param array $attributes Array of attributes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_styles( $attributes ) {
|
||||
return empty( $attributes['textAlign'] ) || 'left' === $attributes['textAlign']
|
||||
? ''
|
||||
: ' style="' . join( ';', [ 'text-align:' . $attributes['textAlign'] ] ) . '"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list style
|
||||
*
|
||||
* @param string $style Style.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_list_style( $style ) {
|
||||
if ( 'numbered' === $style ) {
|
||||
return 'ol';
|
||||
}
|
||||
|
||||
return 'unordered' === $style ? 'ul' : 'div';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list item style
|
||||
*
|
||||
* @param string $style Style.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_list_item_style( $style ) {
|
||||
return in_array( $style, [ 'numbered', 'unordered' ], true ) ? 'li' : 'div';
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the block text.
|
||||
*
|
||||
* @param string $text Text.
|
||||
* @param string $block Block name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function normalize_text( $text, $block ) {
|
||||
/**
|
||||
* Filter: Allow developers to preserve line breaks.
|
||||
*
|
||||
* @param bool $return If set, this will convert all remaining line breaks after paragraphing.
|
||||
* @param string $block Block name.
|
||||
*/
|
||||
return wpautop( $text, apply_filters( 'rank_math/block/preserve_line_breaks', true, $block ) );
|
||||
}
|
||||
}
|
@@ -0,0 +1 @@
|
||||
.wp-block-rank-math-toc-block nav li,.wp-block-rank-math-toc-block nav div{position:relative;min-height:28px;margin-bottom:0}.wp-block-rank-math-toc-block nav li.disabled,.wp-block-rank-math-toc-block nav div.disabled{display:block !important;opacity:0.5}.wp-block-rank-math-toc-block nav li .components-base-control,.wp-block-rank-math-toc-block nav div .components-base-control{position:absolute;top:2px;left:-4px;right:-3px}.wp-block-rank-math-toc-block nav li .rank-math-block-actions,.wp-block-rank-math-toc-block nav div .rank-math-block-actions{position:absolute;top:1px;right:0;display:none;line-height:1}.wp-block-rank-math-toc-block nav li .rank-math-block-actions button.components-button,.wp-block-rank-math-toc-block nav div .rank-math-block-actions button.components-button{min-width:24px;width:24px;height:24px;line-height:34px}.wp-block-rank-math-toc-block nav li:hover .rank-math-block-actions,.wp-block-rank-math-toc-block nav li:focus .rank-math-block-actions,.wp-block-rank-math-toc-block nav li .components-base-control+.rank-math-block-actions .rank-math-block-actions,.wp-block-rank-math-toc-block nav div:hover .rank-math-block-actions,.wp-block-rank-math-toc-block nav div:focus .rank-math-block-actions,.wp-block-rank-math-toc-block nav div .components-base-control+.rank-math-block-actions .rank-math-block-actions{display:block}.rank-math-toc-exclude-headings{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.rank-math-toc-exclude-headings>div{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;margin-bottom:10px !important}
|
@@ -0,0 +1 @@
|
||||
.wp-block-rank-math-toc-block nav ol{counter-reset:item}.wp-block-rank-math-toc-block nav ol li{display:block}.wp-block-rank-math-toc-block nav ol li:before{content:counters(item, ".") ". ";counter-increment:item}
|
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* Block script dependencies.
|
||||
*
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
return [
|
||||
'dependencies' => [
|
||||
'wp-blocks',
|
||||
'wp-element',
|
||||
'wp-components',
|
||||
'wp-block-editor',
|
||||
'wp-data',
|
||||
'wp-dom',
|
||||
'wp-url',
|
||||
'wp-i18n',
|
||||
'lodash',
|
||||
'wp-primitives',
|
||||
'wp-reusable-blocks',
|
||||
],
|
||||
'version' => rank_math()->version,
|
||||
];
|
File diff suppressed because one or more lines are too long
@@ -0,0 +1,52 @@
|
||||
.wp-block-rank-math-toc-block {
|
||||
nav {
|
||||
li, div {
|
||||
position: relative;
|
||||
min-height: 28px;
|
||||
margin-bottom: 0;
|
||||
|
||||
&.disabled {
|
||||
display: block !important;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.components-base-control {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: -4px;
|
||||
right: -3px;
|
||||
}
|
||||
|
||||
.rank-math-block-actions {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 0;
|
||||
display: none;
|
||||
line-height: 1;
|
||||
|
||||
button.components-button {
|
||||
min-width: 24px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 34px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover, &:focus, .components-base-control + .rank-math-block-actions {
|
||||
.rank-math-block-actions {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rank-math-toc-exclude-headings {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
> div {
|
||||
flex: 0 0 50%;
|
||||
margin-bottom: 10px!important;
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
.wp-block-rank-math-toc-block {
|
||||
nav {
|
||||
ol {
|
||||
counter-reset: item;
|
||||
|
||||
li {
|
||||
display: block;
|
||||
}
|
||||
|
||||
li:before {
|
||||
content: counters(item, ".") ". ";
|
||||
counter-increment: item;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
import { linearToNestedHeadingList } from './utils'
|
||||
import { useBlockProps } from '@wordpress/block-editor'
|
||||
import List from './list'
|
||||
|
||||
const attributes = {
|
||||
title: {
|
||||
type: 'text',
|
||||
},
|
||||
headings: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
listStyle: {
|
||||
type: 'text',
|
||||
},
|
||||
titleWrapper: {
|
||||
type: 'text',
|
||||
default: 'h2',
|
||||
},
|
||||
excludeHeadings: {
|
||||
type: 'array',
|
||||
},
|
||||
}
|
||||
|
||||
const v1 = {
|
||||
attributes,
|
||||
save( { attributes } ) {
|
||||
if ( attributes.headings.length === 0 ) {
|
||||
return null
|
||||
}
|
||||
|
||||
const TitleWrapper = attributes.titleWrapper
|
||||
const headings = linearToNestedHeadingList( attributes.headings )
|
||||
const ListStyle = attributes.listStyle
|
||||
|
||||
return (
|
||||
<div { ...useBlockProps.save() }>
|
||||
{ attributes.title && <TitleWrapper dangerouslySetInnerHTML={ { __html: attributes.title } }></TitleWrapper> }
|
||||
<nav>
|
||||
<ListStyle>
|
||||
<List
|
||||
headings={ headings }
|
||||
ListStyle={ ListStyle }
|
||||
isSave={ true }
|
||||
/>
|
||||
</ListStyle>
|
||||
</nav>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
export default [ v1 ]
|
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isUndefined, map, includes, remove } from 'lodash'
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n'
|
||||
import {
|
||||
useBlockProps,
|
||||
RichText,
|
||||
store as blockEditorStore,
|
||||
} from '@wordpress/block-editor'
|
||||
import { useDispatch } from '@wordpress/data'
|
||||
import { Placeholder } from '@wordpress/components'
|
||||
import { useEffect, useState } from '@wordpress/element'
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { GetLatestHeadings, linearToNestedHeadingList } from './utils'
|
||||
import List from './list'
|
||||
import InspectControls from './inspectControls'
|
||||
import Toolbar from './toolbar'
|
||||
|
||||
export default ( {
|
||||
attributes,
|
||||
setAttributes,
|
||||
} ) => {
|
||||
const blockProps = useBlockProps()
|
||||
|
||||
// State to monitor edit heading links.
|
||||
const [ edit, toggleEdit ] = useState( false )
|
||||
const [ excludeHeading, toggleExcludeHeading ] = useState( {} )
|
||||
if ( ! attributes.listStyle ) {
|
||||
setAttributes( { listStyle: rankMath.listStyle } )
|
||||
}
|
||||
|
||||
const ListStyle = attributes.listStyle
|
||||
const tocTitle = attributes.title ?? rankMath.tocTitle
|
||||
const excludeHeadings = ! isUndefined( attributes.excludeHeadings ) ? attributes.excludeHeadings : rankMath.tocExcludeHeadings
|
||||
|
||||
// Function to hide certain heading.
|
||||
const hideHeading = ( value, key ) => {
|
||||
const headings = map( attributes.headings, ( heading ) => {
|
||||
if ( heading.key === key ) {
|
||||
heading.disable = value
|
||||
}
|
||||
|
||||
return heading
|
||||
} )
|
||||
|
||||
setAttributes( { headings } )
|
||||
}
|
||||
|
||||
// Function to update Heading link.
|
||||
const onHeadingUpdate = ( value, key, isContent = false ) => {
|
||||
const headings = map( attributes.headings, ( heading ) => {
|
||||
if ( heading.key === key ) {
|
||||
if ( isContent ) {
|
||||
heading.content = value
|
||||
heading.isUpdated = true
|
||||
} else {
|
||||
heading.isGeneratedLink = false
|
||||
heading.link = value
|
||||
}
|
||||
}
|
||||
|
||||
return heading
|
||||
} )
|
||||
|
||||
setAttributes( { headings } )
|
||||
}
|
||||
|
||||
const setExcludeHeadings = ( headingLevel ) => {
|
||||
if ( includes( excludeHeadings, headingLevel ) ) {
|
||||
remove( excludeHeadings, ( heading ) => {
|
||||
return heading === headingLevel
|
||||
} )
|
||||
} else {
|
||||
excludeHeadings.push( headingLevel )
|
||||
}
|
||||
setAttributes( { excludeHeadings } )
|
||||
toggleExcludeHeading( ! excludeHeading )
|
||||
}
|
||||
|
||||
const { __unstableMarkNextChangeAsNotPersistent } = useDispatch( blockEditorStore )
|
||||
|
||||
// Get Latest headings from the content.
|
||||
const latestHeadings = GetLatestHeadings( attributes.headings, excludeHeadings )
|
||||
useEffect( () => {
|
||||
if ( latestHeadings !== null ) {
|
||||
__unstableMarkNextChangeAsNotPersistent();
|
||||
setAttributes( { headings: latestHeadings } )
|
||||
}
|
||||
}, [ latestHeadings ] )
|
||||
|
||||
const headingTree = linearToNestedHeadingList( attributes.headings )
|
||||
if ( isUndefined( attributes.headings ) || attributes.headings.length === 0 ) {
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<Placeholder
|
||||
label={ __( 'Table of Contents', 'rank-math' ) }
|
||||
instructions={ __( 'Add Heading blocks to this page to generate the Table of Contents.', 'rank-math' ) }
|
||||
/>
|
||||
<InspectControls attributes={ attributes } setAttributes={ setAttributes } excludeHeadings={ excludeHeadings } setExcludeHeadings={ setExcludeHeadings } />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<RichText
|
||||
tagName={ attributes.titleWrapper }
|
||||
value={ tocTitle }
|
||||
onChange={ ( newTitle ) => {
|
||||
setAttributes( { title: newTitle } )
|
||||
} }
|
||||
placeholder={ __( 'Enter a title', 'rank-math' ) }
|
||||
/>
|
||||
<nav>
|
||||
<ListStyle>
|
||||
<List
|
||||
headings={ headingTree }
|
||||
onHeadingUpdate={ onHeadingUpdate }
|
||||
edit={ edit }
|
||||
toggleEdit={ toggleEdit }
|
||||
hideHeading={ hideHeading }
|
||||
ListStyle={ ListStyle }
|
||||
/>
|
||||
</ListStyle>
|
||||
</nav>
|
||||
<Toolbar setAttributes={ setAttributes } />
|
||||
<InspectControls attributes={ attributes } setAttributes={ setAttributes } excludeHeadings={ excludeHeadings } setExcludeHeadings={ setExcludeHeadings } />
|
||||
</div>
|
||||
)
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { registerBlockType } from '@wordpress/blocks'
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import edit from './edit'
|
||||
import save from './save'
|
||||
import deprecated from './deprecated'
|
||||
|
||||
/**
|
||||
* Register TOC block
|
||||
*/
|
||||
registerBlockType(
|
||||
'rank-math/toc-block',
|
||||
{
|
||||
edit,
|
||||
save,
|
||||
deprecated,
|
||||
}
|
||||
)
|
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { map, includes, toUpper } from 'lodash'
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n'
|
||||
import { InspectorControls } from '@wordpress/block-editor'
|
||||
import {
|
||||
PanelBody,
|
||||
SelectControl,
|
||||
CheckboxControl,
|
||||
} from '@wordpress/components'
|
||||
|
||||
export default ( { attributes, setAttributes, excludeHeadings, setExcludeHeadings } ) => {
|
||||
return (
|
||||
<InspectorControls>
|
||||
<PanelBody title={ __( 'Settings', 'rank-math' ) }>
|
||||
|
||||
<SelectControl
|
||||
label={ __( 'Title Wrapper', 'rank-math' ) }
|
||||
value={ attributes.titleWrapper }
|
||||
options={ [
|
||||
{ value: 'h2', label: __( 'H2', 'rank-math' ) },
|
||||
{ value: 'h3', label: __( 'H3', 'rank-math' ) },
|
||||
{ value: 'h4', label: __( 'H4', 'rank-math' ) },
|
||||
{ value: 'h5', label: __( 'H5', 'rank-math' ) },
|
||||
{ value: 'h6', label: __( 'H6', 'rank-math' ) },
|
||||
{ value: 'p', label: __( 'P', 'rank-math' ) },
|
||||
{ value: 'div', label: __( 'DIV', 'rank-math' ) },
|
||||
] }
|
||||
onChange={ ( titleWrapper ) => {
|
||||
setAttributes( { titleWrapper } )
|
||||
} }
|
||||
/>
|
||||
|
||||
<br />
|
||||
<h3>{ __( 'Exclude Headings', 'rank-math' ) }</h3>
|
||||
<div className="rank-math-toc-exclude-headings">
|
||||
{
|
||||
map( [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ], ( value ) => {
|
||||
return (
|
||||
<CheckboxControl
|
||||
key={ value }
|
||||
label={ __( 'Heading ', 'rank-math' ) + toUpper( value ) }
|
||||
checked={ includes( excludeHeadings, value ) }
|
||||
onChange={ ( newVlaue ) => setExcludeHeadings( value, newVlaue ) }
|
||||
/>
|
||||
)
|
||||
} )
|
||||
}
|
||||
</div>
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
)
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isEmpty } from 'lodash'
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n'
|
||||
import { Button, TextControl } from '@wordpress/components'
|
||||
import { RichText } from '@wordpress/block-editor'
|
||||
|
||||
export default function List( { headings = {}, onHeadingUpdate = {}, edit = {}, toggleEdit = {}, hideHeading = {}, ListStyle = 'ul', isSave = false } ) {
|
||||
if ( isEmpty( headings ) ) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{ headings.map( ( heading ) => {
|
||||
if ( isSave && heading.heading.disable ) {
|
||||
return false
|
||||
}
|
||||
|
||||
const { content, link, disable, key } = heading.heading
|
||||
const TagName = 'div' === ListStyle ? 'div' : 'li'
|
||||
return (
|
||||
<TagName key={ key } className={ disable ? 'disabled' : '' }>
|
||||
{
|
||||
isSave &&
|
||||
<a href={ link }>
|
||||
{ content }
|
||||
</a>
|
||||
}
|
||||
{
|
||||
! isSave &&
|
||||
<RichText
|
||||
tagName="a"
|
||||
value={ content }
|
||||
allowedFormats={ [] }
|
||||
onChange={ ( newContent ) => onHeadingUpdate( newContent, key, true ) }
|
||||
placeholder={ __( 'Heading text', 'rank-math' ) }
|
||||
/>
|
||||
}
|
||||
{
|
||||
heading.children &&
|
||||
<ListStyle>
|
||||
<List
|
||||
headings={ heading.children }
|
||||
onHeadingUpdate={ onHeadingUpdate }
|
||||
edit={ edit }
|
||||
toggleEdit={ toggleEdit }
|
||||
hideHeading={ hideHeading }
|
||||
ListStyle={ ListStyle }
|
||||
isSave={ isSave }
|
||||
/>
|
||||
</ListStyle>
|
||||
}
|
||||
{
|
||||
key === edit &&
|
||||
<TextControl
|
||||
placeholder={ __( 'Heading Link', 'rank-math' ) }
|
||||
value={ link }
|
||||
onChange={ ( newLink ) => onHeadingUpdate( newLink, key ) }
|
||||
/>
|
||||
}
|
||||
{
|
||||
! isSave &&
|
||||
<span className="rank-math-block-actions">
|
||||
<Button
|
||||
icon={ edit === key ? 'saved' : 'admin-links' }
|
||||
className="rank-math-item-visbility"
|
||||
onClick={ () => toggleEdit( edit === key ? false : key ) }
|
||||
title={ __( 'Edit Link', 'rank-math' ) }
|
||||
/>
|
||||
|
||||
<Button
|
||||
className="rank-math-item-delete"
|
||||
icon={ ! disable ? 'visibility' : 'hidden' }
|
||||
onClick={ () => hideHeading( ! disable, key ) }
|
||||
title={ __( 'Hide', 'rank-math' ) }
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
</TagName>
|
||||
)
|
||||
} ) }
|
||||
</>
|
||||
)
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isUndefined } from 'lodash'
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { useBlockProps } from '@wordpress/block-editor'
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { linearToNestedHeadingList } from './utils'
|
||||
import List from './list'
|
||||
|
||||
export default function save( { attributes } ) {
|
||||
if ( isUndefined( attributes.headings ) || attributes.headings.length === 0 ) {
|
||||
return null
|
||||
}
|
||||
|
||||
const TitleWrapper = attributes.titleWrapper
|
||||
const headings = linearToNestedHeadingList( attributes.headings )
|
||||
const ListStyle = attributes.listStyle
|
||||
|
||||
return (
|
||||
<div { ...useBlockProps.save() } id="rank-math-toc">
|
||||
{ attributes.title && <TitleWrapper dangerouslySetInnerHTML={ { __html: attributes.title } }></TitleWrapper> }
|
||||
<nav>
|
||||
<ListStyle>
|
||||
<List
|
||||
headings={ headings }
|
||||
ListStyle={ ListStyle }
|
||||
isSave={ true }
|
||||
/>
|
||||
</ListStyle>
|
||||
</nav>
|
||||
</div>
|
||||
)
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n'
|
||||
import { BlockControls } from '@wordpress/block-editor'
|
||||
import {
|
||||
Toolbar,
|
||||
ToolbarButton,
|
||||
} from '@wordpress/components'
|
||||
import { formatListBullets, formatListNumbered, alignLeft } from '@wordpress/icons'
|
||||
|
||||
export default ( { setAttributes } ) => {
|
||||
return (
|
||||
<BlockControls>
|
||||
<Toolbar label={ __( 'Table of Content Options', 'rank-math' ) }>
|
||||
<ToolbarButton
|
||||
icon={ formatListBullets }
|
||||
label={ __( 'Unordered List', 'rank-math' ) }
|
||||
onClick={ () => setAttributes( { listStyle: 'ul' } ) }
|
||||
/>
|
||||
<ToolbarButton
|
||||
icon={ formatListNumbered }
|
||||
label={ __( 'Ordered List', 'rank-math' ) }
|
||||
onClick={ () => setAttributes( { listStyle: 'ol' } ) }
|
||||
/>
|
||||
<ToolbarButton
|
||||
icon={ alignLeft }
|
||||
label={ __( 'None', 'rank-math' ) }
|
||||
onClick={ () => setAttributes( { listStyle: 'div' } ) }
|
||||
/>
|
||||
</Toolbar>
|
||||
</BlockControls>
|
||||
)
|
||||
}
|
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isEmpty, isUndefined, isString, kebabCase, includes, forEach, isEqual, map, isNull } from 'lodash'
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { store as blockEditorStore } from '@wordpress/block-editor'
|
||||
import { __unstableStripHTML as stripHTML } from '@wordpress/dom'
|
||||
import { useSelect, useDispatch } from '@wordpress/data'
|
||||
|
||||
/**
|
||||
* Get the headings from the content.
|
||||
*
|
||||
* @param {Array} headings Array of headings data
|
||||
* @param {Array} excludeHeadings Heading levels to exclude
|
||||
*/
|
||||
export function GetLatestHeadings( headings, excludeHeadings ) {
|
||||
return useSelect(
|
||||
( select ) => {
|
||||
const {
|
||||
getBlockAttributes,
|
||||
getBlockName,
|
||||
getClientIdsWithDescendants,
|
||||
} = select( blockEditorStore )
|
||||
const { __experimentalConvertBlockToStatic: convertBlockToStatic } = useDispatch( 'core/reusable-blocks' )
|
||||
|
||||
// Get the client ids of all blocks in the editor.
|
||||
const allBlockClientIds = getClientIdsWithDescendants()
|
||||
const _latestHeadings = []
|
||||
let i = 0
|
||||
const anchors = []
|
||||
for ( const blockClientId of allBlockClientIds ) {
|
||||
const blockName = getBlockName( blockClientId )
|
||||
if ( blockName === 'core/block' ) {
|
||||
const attrs = getBlockAttributes( blockClientId )
|
||||
if ( ! isNull( attrs.ref ) ) {
|
||||
const reusableBlock = wp.data.select( 'core' ).getEditedEntityRecord( 'postType', 'wp_block', attrs.ref )
|
||||
const blocks = map( reusableBlock.blocks, ( block ) => {
|
||||
return block.name
|
||||
} )
|
||||
|
||||
if ( includes( blocks, 'rank-math/toc-block' ) && ! isNull( getBlockAttributes( blockClientId ) ) ) {
|
||||
convertBlockToStatic( blockClientId )
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if ( ! includes( [ 'rank-math/faq-block', 'rank-math/howto-block', 'core/heading' ], blockName ) ) {
|
||||
continue
|
||||
}
|
||||
|
||||
const headingAttributes = getBlockAttributes( blockClientId )
|
||||
if ( blockName === 'rank-math/faq-block' || blockName === 'rank-math/howto-block' ) {
|
||||
const titleWrapper = headingAttributes.titleWrapper
|
||||
if (
|
||||
includes( excludeHeadings, titleWrapper ) ||
|
||||
includes( [ 'div', 'p' ], titleWrapper )
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
const data = blockName === 'rank-math/howto-block' ? headingAttributes.steps : headingAttributes.questions
|
||||
if ( isEmpty( data ) ) {
|
||||
continue
|
||||
}
|
||||
|
||||
forEach( data, ( value ) => {
|
||||
const currentHeading = ! isUndefined( headings ) && ! isEmpty( headings[ _latestHeadings.length ] ) ? headings[ _latestHeadings.length ] : {
|
||||
content: '',
|
||||
level: '',
|
||||
disable: false,
|
||||
isUpdated: false,
|
||||
isGeneratedLink: true,
|
||||
}
|
||||
|
||||
const isGeneratedLink = ! isUndefined( currentHeading.isGeneratedLink ) && currentHeading.isGeneratedLink
|
||||
const content = ! isUndefined( currentHeading.isUpdated ) && currentHeading.isUpdated ? currentHeading.content : value.title
|
||||
|
||||
_latestHeadings.push( {
|
||||
key: value.id,
|
||||
content: stripHTML( content ),
|
||||
level: parseInt( headingAttributes.titleWrapper.replace( 'h', '' ) ),
|
||||
link: ! isGeneratedLink ? currentHeading.link : `#${ value.id }`,
|
||||
disable: currentHeading.disable ? currentHeading.disable : false,
|
||||
isUpdated: ! isUndefined( currentHeading.isUpdated ) ? currentHeading.isUpdated : false,
|
||||
isGeneratedLink,
|
||||
} )
|
||||
} )
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if ( blockName === 'core/heading' ) {
|
||||
if ( includes( excludeHeadings, 'h' + headingAttributes.level ) ) {
|
||||
continue
|
||||
}
|
||||
|
||||
const currentHeading = ! isUndefined( headings ) && ! isEmpty( headings[ _latestHeadings.length ] ) ? headings[ _latestHeadings.length ] : {
|
||||
content: '',
|
||||
level: '',
|
||||
disable: false,
|
||||
isUpdated: false,
|
||||
isGeneratedLink: true,
|
||||
}
|
||||
|
||||
const isGeneratedLink = ! isUndefined( currentHeading.isGeneratedLink ) && currentHeading.isGeneratedLink
|
||||
|
||||
let anchor = headingAttributes.anchor
|
||||
const headingText = ! isEmpty( headingAttributes.content.text ) ? headingAttributes.content.text : headingAttributes.content
|
||||
if ( isEmpty( headingAttributes.anchor ) || isGeneratedLink ) {
|
||||
anchor = kebabCase( stripHTML( headingText ) )
|
||||
}
|
||||
|
||||
if ( includes( anchors, anchor ) ) {
|
||||
i += 1
|
||||
anchor = anchor + '-' + i
|
||||
}
|
||||
|
||||
anchors.push( anchor )
|
||||
headingAttributes.anchor = anchor
|
||||
const headingContent = isString( headingText ) ? stripHTML(
|
||||
headingText.replace(
|
||||
/(<br *\/?>)+/g,
|
||||
' '
|
||||
)
|
||||
) : ''
|
||||
|
||||
const content = ! isUndefined( currentHeading.isUpdated ) && currentHeading.isUpdated ? currentHeading.content : headingContent
|
||||
|
||||
_latestHeadings.push( {
|
||||
key: blockClientId,
|
||||
content: stripHTML( content ),
|
||||
level: headingAttributes.level,
|
||||
link: ! isGeneratedLink ? currentHeading.link : `#${ headingAttributes.anchor }`,
|
||||
disable: currentHeading.disable ? currentHeading.disable : false,
|
||||
isUpdated: ! isUndefined( currentHeading.isUpdated ) ? currentHeading.isUpdated : false,
|
||||
isGeneratedLink,
|
||||
} )
|
||||
}
|
||||
}
|
||||
|
||||
if ( isEqual( headings, _latestHeadings ) ) {
|
||||
return null
|
||||
}
|
||||
|
||||
return _latestHeadings
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Nest heading based on the Heading level.
|
||||
*
|
||||
* @param {Array} headingList The flat list of headings to nest.
|
||||
*
|
||||
* @return {Array} The nested list of headings.
|
||||
*/
|
||||
export function linearToNestedHeadingList( headingList = [] ) {
|
||||
const nestedHeadingList = []
|
||||
forEach( headingList, ( heading, key ) => {
|
||||
if ( isEmpty( heading.content ) ) {
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure we are only working with the same level as the first iteration in our set.
|
||||
if ( heading.level === headingList[ 0 ].level ) {
|
||||
if ( headingList[ key + 1 ]?.level > heading.level ) {
|
||||
let endOfSlice = headingList.length
|
||||
for ( let i = key + 1; i < headingList.length; i++ ) {
|
||||
if ( headingList[ i ].level === heading.level ) {
|
||||
endOfSlice = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
nestedHeadingList.push( {
|
||||
heading,
|
||||
children: linearToNestedHeadingList(
|
||||
headingList.slice( key + 1, endOfSlice )
|
||||
),
|
||||
} )
|
||||
} else {
|
||||
nestedHeadingList.push( {
|
||||
heading,
|
||||
children: null,
|
||||
} )
|
||||
}
|
||||
}
|
||||
} )
|
||||
|
||||
return nestedHeadingList
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"apiVersion": 2,
|
||||
"title": "Table of Contents by Rank Math",
|
||||
"description": "Automatically generate the Table of Contents from the Headings added to this page.",
|
||||
"name": "rank-math/toc-block",
|
||||
"category": "rank-math-blocks",
|
||||
"icon": "rm-icon rm-icon-stories",
|
||||
"textdomain": "rank-math",
|
||||
"editorScript": "file:./assets/js/index.js",
|
||||
"editorStyle": "file:./assets/css/toc.css",
|
||||
"style": "file:./assets/css/toc_list_style.css",
|
||||
"attributes": {
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"headings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"listStyle": {
|
||||
"type": "string"
|
||||
},
|
||||
"titleWrapper": {
|
||||
"type": "string",
|
||||
"default": "h2"
|
||||
},
|
||||
"excludeHeadings": {
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"supports": {
|
||||
"color": {
|
||||
"link": true,
|
||||
"gradients": true
|
||||
},
|
||||
"multiple": false,
|
||||
"spacing": {
|
||||
"margin": true,
|
||||
"padding": true
|
||||
},
|
||||
"typography": {
|
||||
"fontSize": true,
|
||||
"lineHeight": true,
|
||||
"__experimentalDefaultControls": {
|
||||
"fontSize": true
|
||||
}
|
||||
},
|
||||
"align": true
|
||||
}
|
||||
}
|
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
/**
|
||||
* The TOC Block
|
||||
*
|
||||
* @since 1.0.104
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use WP_Block_Type_Registry;
|
||||
use RankMath\Helper;
|
||||
use RankMath\Traits\Hooker;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* HowTo Block class.
|
||||
*/
|
||||
class Block_TOC extends Block {
|
||||
|
||||
use Hooker;
|
||||
|
||||
/**
|
||||
* Block type name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $block_type = 'rank-math/toc-block';
|
||||
|
||||
/**
|
||||
* The single instance of the class.
|
||||
*
|
||||
* @var Block_TOC
|
||||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* Retrieve main Block_TOC instance.
|
||||
*
|
||||
* Ensure only one instance is loaded or can be loaded.
|
||||
*
|
||||
* @return Block_TOC
|
||||
*/
|
||||
public static function get() {
|
||||
if ( is_null( self::$instance ) && ! ( self::$instance instanceof Block_TOC ) ) {
|
||||
self::$instance = new Block_TOC();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( WP_Block_Type_Registry::get_instance()->is_registered( $this->block_type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->filter( 'rank_math/schema/block/toc-block', 'add_graph', 10, 2 );
|
||||
$this->filter( 'render_block_rank-math/toc-block', 'render_toc_block_content', 10, 2 );
|
||||
$this->filter( 'rank_math/metabox/post/values', 'block_settings_metadata' );
|
||||
register_block_type( RANK_MATH_PATH . 'includes/modules/schema/blocks/toc/block.json' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add meta data to use in the TOC block.
|
||||
*
|
||||
* @param array $values Aray of tabs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function block_settings_metadata( $values ) {
|
||||
$values['tocTitle'] = Helper::get_settings( 'general.toc_block_title' );
|
||||
$values['tocExcludeHeadings'] = Helper::get_settings( 'general.toc_block_exclude_headings', [] );
|
||||
$values['listStyle'] = Helper::get_settings( 'general.toc_block_list_style', 'ul' );
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add default TOC title.
|
||||
*
|
||||
* @param string $block_content Block content.
|
||||
* @param array $parsed_block The full block, including name and attributes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render_toc_block_content( $block_content, $parsed_block ) {
|
||||
if ( isset( $parsed_block['attrs']['title'] ) ) {
|
||||
return $block_content;
|
||||
}
|
||||
|
||||
$title = Helper::get_settings( 'general.toc_block_title' );
|
||||
if ( ! $title ) {
|
||||
return $block_content;
|
||||
}
|
||||
|
||||
$title_wrapper = $parsed_block['attrs']['titleWrapper'] ?? 'h2';
|
||||
|
||||
$block_content = preg_replace_callback(
|
||||
'/(<div class=".*?wp-block-rank-math-toc-block.*?"\>)/i',
|
||||
function( $value ) use ( $title, $block_content, $title_wrapper ) {
|
||||
if ( ! isset( $value[0] ) ) {
|
||||
return $block_content;
|
||||
}
|
||||
|
||||
$value[0] = str_replace( '>', ' id="rank-math-toc">', $value[0] );
|
||||
return $value[0] . '<' . tag_escape( $title_wrapper ) . '>' . esc_html( $title ) . '</' . tag_escape( $title_wrapper ) . '>';
|
||||
},
|
||||
$block_content
|
||||
);
|
||||
|
||||
return str_replace( 'class=""', '', $block_content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add TOC schema data in JSON-LD array.
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param array $block JsonLD Instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_graph( $data, $block ) {
|
||||
$attributes = $block['attrs'];
|
||||
// Early bail.
|
||||
if ( empty( $attributes['headings'] ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( ! isset( $data['toc'] ) ) {
|
||||
$data['toc'] = [];
|
||||
}
|
||||
|
||||
foreach ( $attributes['headings'] as $heading ) {
|
||||
if ( ! empty( $heading['disable'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data['toc'][] = [
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'SiteNavigationElement',
|
||||
'@id' => '#rank-math-toc',
|
||||
'name' => $heading['content'],
|
||||
'url' => get_permalink() . $heading['link'],
|
||||
];
|
||||
}
|
||||
|
||||
if ( empty( $data['toc'] ) ) {
|
||||
unset( $data['toc'] );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* Blocks general settings.
|
||||
*
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
*/
|
||||
|
||||
use RankMath\Helper;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
$cmb->add_field(
|
||||
[
|
||||
'id' => 'toc_block_title',
|
||||
'type' => 'text',
|
||||
'name' => esc_html__( 'Table of Contents Title', 'rank-math' ),
|
||||
'desc' => esc_html__( 'Enter the default title to use for the Table of Contents block.', 'rank-math' ),
|
||||
'classes' => 'rank-math-advanced-option',
|
||||
'default' => esc_html__( 'Table of Contents', 'rank-math' ),
|
||||
]
|
||||
);
|
||||
|
||||
$cmb->add_field(
|
||||
[
|
||||
'id' => 'toc_block_list_style',
|
||||
'type' => 'select',
|
||||
'name' => esc_html__( 'Table of Contents List style', 'rank-math' ),
|
||||
'desc' => esc_html__( 'Select the default list style for the Table of Contents block.', 'rank-math' ),
|
||||
'options' => [
|
||||
'div' => esc_html__( 'None', 'rank-math' ),
|
||||
'ol' => esc_html__( 'Numbered', 'rank-math' ),
|
||||
'ul' => esc_html__( 'Unordered', 'rank-math' ),
|
||||
],
|
||||
'default' => 'unordered',
|
||||
]
|
||||
);
|
||||
|
||||
$cmb->add_field(
|
||||
[
|
||||
'id' => 'toc_block_exclude_headings',
|
||||
'name' => esc_html__( 'Table of Contents Exclude Headings', 'rank-math' ),
|
||||
'desc' => esc_html__( 'Choose the headings to exclude from the Table of Contents block.', 'rank-math' ),
|
||||
'type' => 'multicheck',
|
||||
'options' => [
|
||||
'h1' => esc_html__( 'Heading H1', 'rank-math' ),
|
||||
'h2' => esc_html__( 'Heading H2', 'rank-math' ),
|
||||
'h3' => esc_html__( 'Heading H3', 'rank-math' ),
|
||||
'h4' => esc_html__( 'Heading H4', 'rank-math' ),
|
||||
'h5' => esc_html__( 'Heading H5', 'rank-math' ),
|
||||
'h6' => esc_html__( 'Heading H6', 'rank-math' ),
|
||||
],
|
||||
]
|
||||
);
|
@@ -0,0 +1,306 @@
|
||||
<?php
|
||||
/**
|
||||
* The admin-side code of the Schema module.
|
||||
*
|
||||
* @since 0.9.0
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Module\Base;
|
||||
use RankMath\Admin\Admin_Helper;
|
||||
use RankMath\Helpers\Str;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Admin class.
|
||||
*/
|
||||
class Admin extends Base {
|
||||
|
||||
/**
|
||||
* Module ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id = '';
|
||||
|
||||
/**
|
||||
* Module directory.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $directory = '';
|
||||
|
||||
/**
|
||||
* The Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
$directory = dirname( __FILE__ );
|
||||
$this->config(
|
||||
[
|
||||
'id' => 'rich-snippet',
|
||||
'directory' => $directory,
|
||||
]
|
||||
);
|
||||
parent::__construct();
|
||||
|
||||
$this->action( 'cmb2_admin_init', 'add_kb_links', 50 );
|
||||
$this->action( 'rank_math/admin/editor_scripts', 'enqueue' );
|
||||
$this->action( 'rank_math/post/column/seo_details', 'display_schema_type', 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Display schema type in the `seo_details` column on the posts.
|
||||
*
|
||||
* @param int $post_id The current post ID.
|
||||
* @param array $data SEO data of current post.
|
||||
*/
|
||||
public function display_schema_type( $post_id, $data ) {
|
||||
$schema = absint( get_option( 'page_for_posts' ) ) !== $post_id ? $this->get_schema_types( $data, $post_id ) : 'CollectionPage';
|
||||
$schema = ! empty( $schema ) ? $schema : $this->get_schema_name( Helper::get_default_schema_type( $post_id, true ) );
|
||||
$schema = $schema ? $schema : esc_html__( 'Off', 'rank-math' );
|
||||
?>
|
||||
<span class="rank-math-column-display schema-type">
|
||||
<strong><?php esc_html_e( 'Schema', 'rank-math' ); ?>:</strong>
|
||||
<?php echo esc_html( Helper::sanitize_schema_title( $schema ) ); ?>
|
||||
</span>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue Styles and Scripts required for the metabox on the post screen.
|
||||
*/
|
||||
public function enqueue() {
|
||||
if ( ! Helper::has_cap( 'onpage_snippet' ) || Admin_Helper::is_posts_page() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$values = [];
|
||||
$cmb = $this->get_metabox();
|
||||
if ( false === $cmb ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Helper::add_json( 'schemas', $this->get_schema_data( $cmb->object_id() ) );
|
||||
Helper::add_json( 'customSchemaImage', esc_url( rank_math()->plugin_url() . 'includes/modules/schema/assets/img/custom-schema-builder.jpg' ) );
|
||||
|
||||
wp_enqueue_style( 'rank-math-schema', rank_math()->plugin_url() . 'includes/modules/schema/assets/css/schema.css', [ 'wp-components', 'rank-math-editor' ], rank_math()->version );
|
||||
$this->enqueue_translation();
|
||||
|
||||
$screen = get_current_screen();
|
||||
if ( 'rank_math_schema' !== $screen->post_type ) {
|
||||
wp_enqueue_script( 'rank-math-schema', rank_math()->plugin_url() . 'includes/modules/schema/assets/js/schema-gutenberg.js', [ 'rank-math-editor' ], rank_math()->version, true );
|
||||
wp_set_script_translations( 'rank-math-schema', 'rank-math' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* KB Links for gutenberg
|
||||
*/
|
||||
public function add_kb_links() {
|
||||
Helper::add_json(
|
||||
'assessor',
|
||||
[
|
||||
'reviewConverterLink' => Helper::get_admin_url( 'status', 'view=tools' ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Schema Data.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*
|
||||
* @return array $schemas Schema Data.
|
||||
*/
|
||||
private function get_schema_data( $post_id ) {
|
||||
$schemas = DB::get_schemas( $post_id );
|
||||
if ( ! empty( $schemas ) ) {
|
||||
return $schemas;
|
||||
}
|
||||
|
||||
$default_type = $this->get_default_schema_type( $post_id );
|
||||
if ( ! $default_type ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$schemas['new-9999'] = [
|
||||
'@type' => $default_type,
|
||||
'metadata' => [
|
||||
'title' => Helper::sanitize_schema_title( $default_type ),
|
||||
'type' => 'template',
|
||||
'shortcode' => uniqid( 's-' ),
|
||||
'isPrimary' => true,
|
||||
],
|
||||
];
|
||||
|
||||
if ( ! in_array( $default_type, [ 'Article', 'NewsArticle', 'BlogPosting' ], true ) ) {
|
||||
return $schemas;
|
||||
}
|
||||
|
||||
$post_type = get_post_type( $post_id );
|
||||
$name = Helper::get_settings( "titles.pt_{$post_type}_default_snippet_name" );
|
||||
$description = Helper::get_settings( "titles.pt_{$post_type}_default_snippet_desc" );
|
||||
|
||||
$schemas['new-9999']['headline'] = $name ? $name : '';
|
||||
$schemas['new-9999']['description'] = $description ? $description : '';
|
||||
$schemas['new-9999']['keywords'] = '%keywords%';
|
||||
$schemas['new-9999']['author'] = [
|
||||
'@type' => 'Person',
|
||||
'name' => '%name%',
|
||||
];
|
||||
|
||||
return $schemas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default schema type.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*
|
||||
* @return string|bool Schema type.
|
||||
*/
|
||||
private function get_default_schema_type( $post_id ) {
|
||||
$default_type = ucfirst( Helper::get_default_schema_type( $post_id ) );
|
||||
if ( ! $default_type ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( 'Video' === $default_type ) {
|
||||
return 'VideoObject';
|
||||
}
|
||||
|
||||
if ( 'Software' === $default_type ) {
|
||||
return 'SoftwareApplication';
|
||||
}
|
||||
|
||||
if ( 'Jobposting' === $default_type ) {
|
||||
return 'JobPosting';
|
||||
}
|
||||
|
||||
return $default_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue translation.
|
||||
*/
|
||||
private function enqueue_translation() {
|
||||
if ( function_exists( 'wp_set_script_translations' ) ) {
|
||||
wp_set_script_translations( 'rank-math-schema', 'rank-math', rank_math()->plugin_dir() . 'languages/' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schema types for current post.
|
||||
*
|
||||
* @param array $data Current post SEO data.
|
||||
* @param int $post_id Current post ID.
|
||||
*
|
||||
* @return string Comma separated schema types.
|
||||
*/
|
||||
private function get_schema_types( $data, $post_id ) {
|
||||
if ( empty( $data ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$types = [];
|
||||
foreach ( $data as $key => $value ) {
|
||||
if ( ! Str::starts_with( 'rank_math_schema_', $key ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$schema = maybe_unserialize( $value );
|
||||
if ( empty( $schema['@type'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! is_array( $schema['@type'] ) ) {
|
||||
$types[] = $this->get_schema_name( $schema['@type'] );
|
||||
continue;
|
||||
}
|
||||
|
||||
$types = array_merge(
|
||||
$types,
|
||||
array_map(
|
||||
function( $type ) {
|
||||
return $this->get_schema_name( $type );
|
||||
},
|
||||
$schema['@type']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( empty( $types ) && Helper::get_default_schema_type( $post_id ) ) {
|
||||
$types[] = $this->get_schema_name( Helper::get_default_schema_type( $post_id ) );
|
||||
}
|
||||
|
||||
if ( has_block( 'rank-math/faq-block', $post_id ) ) {
|
||||
$types[] = 'FAQPage';
|
||||
}
|
||||
|
||||
if ( has_block( 'rank-math/howto-block', $post_id ) ) {
|
||||
$types[] = 'HowTo';
|
||||
}
|
||||
|
||||
return empty( $types ) ? false : implode( ', ', $types );
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get Sanitized schema name with sub-schema.
|
||||
*
|
||||
* @param string $schema Selected schema type.
|
||||
*
|
||||
* @return string Schema name with sub-schema.
|
||||
*/
|
||||
private function get_schema_name( $schema ) {
|
||||
$subtitle = in_array( $schema, [ 'BlogPosting', 'NewsArticle' ], true ) ? " ($schema)" : '';
|
||||
return Helper::sanitize_schema_title( $schema ) . $subtitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a CMB2 instance by the metabox ID.
|
||||
*
|
||||
* @return bool|CMB2
|
||||
*/
|
||||
private function get_metabox() {
|
||||
if ( Admin_Helper::is_term_profile_page() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return cmb2_get_metabox( 'rank_math_metabox' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Can exclude field.
|
||||
*
|
||||
* @param string $id Field id.
|
||||
* @param string $type Field type.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function can_exclude( $id, $type ) {
|
||||
$exclude = [ 'meta_tab_container_open', 'tab_container_open', 'tab_container_close', 'tab', 'raw', 'notice' ];
|
||||
return in_array( $type, $exclude, true ) || ! Str::starts_with( 'rank_math_snippet_', $id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert string to camel case.
|
||||
*
|
||||
* @param string $str String to convert.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function camelize( $str ) {
|
||||
$sep = '_';
|
||||
$str = str_replace( 'rank_math_snippet_', '', $str );
|
||||
$str = str_replace( $sep, '', ucwords( $str, $sep ) );
|
||||
|
||||
return lcfirst( $str );
|
||||
}
|
||||
}
|
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
/**
|
||||
* The Schema Blocks
|
||||
*
|
||||
* @since 0.9.0
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Traits\Hooker;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Blocks class.
|
||||
*/
|
||||
class Blocks {
|
||||
|
||||
use Hooker;
|
||||
|
||||
/**
|
||||
* The Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->action( 'init', 'init' );
|
||||
|
||||
$filter = version_compare( get_bloginfo( 'version' ), '5.8', '>=' ) ? 'block_categories_all' : 'block_categories';
|
||||
$this->filter( $filter, 'block_categories' );
|
||||
$this->action( 'enqueue_block_editor_assets', 'editor_assets' ); // Backend.
|
||||
}
|
||||
|
||||
/**
|
||||
* Init blocks.
|
||||
*/
|
||||
public function init() {
|
||||
if ( ! function_exists( 'register_block_type' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_register_style(
|
||||
'rank-math-block-admin',
|
||||
rank_math()->plugin_url() . 'assets/admin/css/blocks.css',
|
||||
null,
|
||||
rank_math()->version
|
||||
);
|
||||
|
||||
new Blocks\Admin();
|
||||
new Block_FAQ();
|
||||
new Block_HowTo();
|
||||
new Block_TOC();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new (Rank Math) block category.
|
||||
*
|
||||
* @param array $categories Array of block categories.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function block_categories( $categories ) {
|
||||
return array_merge(
|
||||
$categories,
|
||||
[
|
||||
[
|
||||
'slug' => 'rank-math-blocks',
|
||||
'title' => __( 'Rank Math', 'rank-math' ),
|
||||
'icon' => 'wordpress',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue Styles and Scripts required for blocks at backend.
|
||||
*/
|
||||
public function editor_assets() {
|
||||
if ( ! $this->is_block_faq() && ! $this->is_block_howto() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Helper::add_json(
|
||||
'blocks',
|
||||
[
|
||||
'faq' => $this->is_block_faq(),
|
||||
'howTo' => $this->is_block_howto(),
|
||||
]
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'rank-math-block-faq',
|
||||
rank_math()->plugin_url() . 'assets/admin/js/blocks.js',
|
||||
[],
|
||||
rank_math()->version,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is FAQ Block enabled.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function is_block_faq() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is HowTo Block enabled.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function is_block_howto() {
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
/**
|
||||
* The Schema module database operations.
|
||||
*
|
||||
* @since 1.4.3
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Admin\Database\Database;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* DB class.
|
||||
*/
|
||||
class DB {
|
||||
|
||||
/**
|
||||
* Get query builder object.
|
||||
*
|
||||
* @param string $table Meta table name.
|
||||
*
|
||||
* @return Query_Builder
|
||||
*/
|
||||
private static function table( $table = 'postmeta' ) {
|
||||
return Database::table( $table );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all schemas by Object ID.
|
||||
*
|
||||
* @param int $object_id Object ID.
|
||||
* @param string $table Meta table name.
|
||||
* @param bool $from_db If set to true, the schema will be retrieved from the database.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_schemas( $object_id, $table = 'postmeta', $from_db = false ) {
|
||||
static $schema_cache = [];
|
||||
|
||||
// Add exception handler.
|
||||
if ( is_null( $object_id ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get from cache.
|
||||
if ( ! $from_db && isset( $schema_cache[ $table . '_' . $object_id ] ) ) {
|
||||
return $schema_cache[ $table . '_' . $object_id ];
|
||||
}
|
||||
|
||||
$key = 'termmeta' === $table ? 'term_id' : 'post_id';
|
||||
$data = self::table( $table )
|
||||
->select( 'meta_id' )
|
||||
->select( 'meta_value' )
|
||||
->where( $key, $object_id )
|
||||
->whereLike( 'meta_key', 'rank_math_schema', '' )
|
||||
->get();
|
||||
|
||||
$schemas = [];
|
||||
foreach ( $data as $schema ) {
|
||||
$value = maybe_unserialize( $schema->meta_value );
|
||||
if ( empty( $value ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$id = 'schema-' . $schema->meta_id;
|
||||
$schemas[ $id ] = maybe_unserialize( $schema->meta_value );
|
||||
}
|
||||
|
||||
// Add to cache.
|
||||
$schema_cache[ $table . '_' . $object_id ] = $schemas;
|
||||
|
||||
return $schemas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Schema types by Object ID.
|
||||
*
|
||||
* @param int $object_id Object ID.
|
||||
* @param bool $sanitize Sanitize schema types.
|
||||
* @param bool $translate Whether to get the schema name.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_schema_types( $object_id, $sanitize = false, $translate = true ) {
|
||||
$schemas = self::get_schemas( $object_id );
|
||||
|
||||
if ( empty( $schemas ) && Helper::get_default_schema_type( $object_id ) ) {
|
||||
$schemas[] = [ '@type' => ucfirst( Helper::get_default_schema_type( $object_id ) ) ];
|
||||
}
|
||||
|
||||
if ( has_block( 'rank-math/faq-block', $object_id ) ) {
|
||||
$schemas[] = [ '@type' => 'FAQPage' ];
|
||||
}
|
||||
|
||||
if ( has_block( 'rank-math/howto-block', $object_id ) ) {
|
||||
$schemas[] = [ '@type' => 'HowTo' ];
|
||||
}
|
||||
|
||||
if ( empty( $schemas ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$types = array_reduce(
|
||||
wp_list_pluck( $schemas, '@type' ),
|
||||
function( $carry, $type ) {
|
||||
if ( is_array( $type ) ) {
|
||||
return array_merge( $carry, $type );
|
||||
}
|
||||
|
||||
$carry[] = $type;
|
||||
return $carry;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
$types = array_unique( $types );
|
||||
|
||||
if ( $sanitize ) {
|
||||
$types = array_map(
|
||||
function ( $type ) use ( $translate ) {
|
||||
return Helper::sanitize_schema_title( $type, $translate );
|
||||
},
|
||||
$types
|
||||
);
|
||||
}
|
||||
return implode( ', ', $types );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schema by shortcode id.
|
||||
*
|
||||
* @param string $id Shortcode unique id.
|
||||
* @param bool $from_db If set to true, the schema will be retrieved from the database.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_schema_by_shortcode_id( $id, $from_db = false ) {
|
||||
/**
|
||||
* Keep Schema data in memory after querying by shortcode ID, to avoid
|
||||
* unnecessary queries.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static $cached_schema_shortcodes = [];
|
||||
|
||||
if ( ! $from_db && isset( $cached_schema_shortcodes[ $id ] ) ) {
|
||||
return $cached_schema_shortcodes[ $id ];
|
||||
}
|
||||
|
||||
// First, check for meta_key matches for a "shortcut" to the schema.
|
||||
$shortcut = false;
|
||||
if ( strpos( self::table()->table, 'post' ) !== false ) {
|
||||
// Only check for shortcuts if we're querying for a post.
|
||||
$shortcut = self::table()
|
||||
->select( 'meta_value' )
|
||||
->where( 'meta_key', 'rank_math_shortcode_schema_' . $id )
|
||||
->one();
|
||||
}
|
||||
|
||||
if ( ! empty( $shortcut ) ) {
|
||||
$data = self::table()
|
||||
->select( 'post_id' )
|
||||
->select( 'meta_value' )
|
||||
->where( 'meta_id', $shortcut->meta_value )
|
||||
->one();
|
||||
|
||||
if ( ! empty( $data ) ) {
|
||||
$schema = [
|
||||
'post_id' => $data->post_id,
|
||||
'schema' => maybe_unserialize( $data->meta_value ),
|
||||
];
|
||||
|
||||
// Cache the schema for future use.
|
||||
$cached_schema_shortcodes[ $id ] = $schema;
|
||||
|
||||
return $schema;
|
||||
}
|
||||
}
|
||||
|
||||
$data = self::table()
|
||||
->select( 'post_id' )
|
||||
->select( 'meta_value' )
|
||||
->whereLike( 'meta_value', $id, '%:"' )
|
||||
->one();
|
||||
|
||||
if ( ! empty( $data ) ) {
|
||||
$schema = [
|
||||
'post_id' => $data->post_id,
|
||||
'schema' => maybe_unserialize( $data->meta_value ),
|
||||
];
|
||||
|
||||
// Cache the schema for future use.
|
||||
$cached_schema_shortcodes[ $id ] = $schema;
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schema type for template.
|
||||
*
|
||||
* @param int $post_id Post id.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_template_type( $post_id ) {
|
||||
$data = self::table()
|
||||
->select( 'meta_value' )
|
||||
->where( 'post_id', $post_id )
|
||||
->whereLike( 'meta_key', 'rank_math_schema', '' )
|
||||
->one();
|
||||
|
||||
if ( empty( $data ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$schema = maybe_unserialize( $data->meta_value );
|
||||
|
||||
return [
|
||||
'type' => isset( $schema['@type'] ) ? $schema['@type'] : '',
|
||||
'schema' => $schema,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Schema data using Post ID.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function delete_schema_data( $post_id ) {
|
||||
return self::table()->where( 'post_id', $post_id )->whereLike( 'meta_key', 'rank_math_schema_' )->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpublish job posting when expired.
|
||||
*
|
||||
* @param JsonLD $jsonld JsonLD Instance.
|
||||
* @param array $schemas Array of JSON-LD entity.
|
||||
*/
|
||||
public static function unpublish_jobposting_post( $jsonld, $schemas ) {
|
||||
if ( ! is_singular() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$job_postings = array_map(
|
||||
function( $schema ) {
|
||||
return isset( $schema['@type'] ) && 'JobPosting' === $schema['@type'] ? $schema : false;
|
||||
},
|
||||
$schemas
|
||||
);
|
||||
|
||||
if ( empty( $job_postings ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $job_postings as $job_posting ) {
|
||||
if (
|
||||
empty( $job_posting['metadata']['unpublish'] ) ||
|
||||
'on' !== $job_posting['metadata']['unpublish'] ||
|
||||
empty( $job_posting['validThrough'] ) ||
|
||||
date_create( 'now' )->getTimestamp() < strtotime( $job_posting['validThrough'] )
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
wp_update_post(
|
||||
[
|
||||
'ID' => $jsonld->post_id,
|
||||
'post_status' => 'draft',
|
||||
]
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,318 @@
|
||||
<?php
|
||||
/**
|
||||
* The frontend code of the Schema module.
|
||||
*
|
||||
* @since 1.4.3
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Traits\Hooker;
|
||||
use RankMath\Helpers\Str;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Frontend class.
|
||||
*/
|
||||
class Frontend {
|
||||
|
||||
use Hooker;
|
||||
|
||||
/**
|
||||
* Hold post object.
|
||||
*
|
||||
* @var WP_Post
|
||||
*/
|
||||
public $post = null;
|
||||
|
||||
/**
|
||||
* Hold post ID.
|
||||
*
|
||||
* @var ID
|
||||
*/
|
||||
public $post_id = 0;
|
||||
|
||||
/**
|
||||
* The Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( Helper::is_divi_frontend_editor() ) {
|
||||
return;
|
||||
}
|
||||
$this->action( 'rank_math/json_ld', 'add_schema', 10, 2 );
|
||||
$this->action( 'rank_math/json_ld', 'connect_schema_entities', 99, 2 );
|
||||
$this->filter( 'rank_math/snippet/rich_snippet_event_entity', 'validate_event_schema', 11, 2 );
|
||||
$this->filter( 'rank_math/snippet/rich_snippet_article_entity', 'add_name_property', 11, 2 );
|
||||
$this->action( 'rank_math/schema/validated_data', 'remove_person_entity', 999 );
|
||||
|
||||
new Opengraph();
|
||||
}
|
||||
|
||||
/**
|
||||
* Output schema data for a post.
|
||||
*
|
||||
* @param array $data Array of json-ld data.
|
||||
* @param JsonLD $jsonld Instance of jsonld.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_schema( $data, $jsonld ) {
|
||||
if ( ! is_singular() || post_password_required() ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
global $post;
|
||||
$schemas = array_filter(
|
||||
DB::get_schemas( $post->ID ),
|
||||
function( $schema ) {
|
||||
return ! in_array( $schema['@type'], [ 'WooCommerceProduct', 'EDDProduct' ], true );
|
||||
}
|
||||
);
|
||||
|
||||
// Check & Unpublish the JobPosting post.
|
||||
DB::unpublish_jobposting_post( $jsonld, $schemas );
|
||||
|
||||
$schemas = $jsonld->replace_variables( $schemas, [], $data );
|
||||
$schemas = $jsonld->filter( $schemas, $jsonld, $data );
|
||||
|
||||
return array_merge( $data, $schemas );
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect different schema entities using isPartOf & publisher properties.
|
||||
*
|
||||
* @param array $schemas Array of json-ld data.
|
||||
* @param JsonLD $jsonld Instance of jsonld.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function connect_schema_entities( $schemas, $jsonld ) {
|
||||
if ( empty( $schemas ) ) {
|
||||
return $schemas;
|
||||
}
|
||||
|
||||
$jsonld->parts['canonical'] = ! empty( $jsonld->parts['canonical'] ) ? $jsonld->parts['canonical'] : \RankMath\Paper\Paper::get()->get_canonical();
|
||||
|
||||
$schema_types = [];
|
||||
foreach ( $schemas as $id => $schema ) {
|
||||
if ( ! Str::starts_with( 'schema-', $id ) && 'richSnippet' !== $id ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$schema_types[] = $schema['@type'];
|
||||
$this->connect_properties( $schema, $id, $jsonld, $schemas );
|
||||
$this->add_main_entity_of_page( $schema, $jsonld );
|
||||
$schemas[ $id ] = $schema;
|
||||
}
|
||||
|
||||
return $this->change_webpage_entity( $schemas, $schema_types );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add name property to the Article schema.
|
||||
*
|
||||
* @since 1.0.61
|
||||
*
|
||||
* @param array $schema Snippet Data.
|
||||
* @return array
|
||||
*/
|
||||
public function add_name_property( $schema ) {
|
||||
if ( empty( $schema['headline'] ) ) {
|
||||
return $schema;
|
||||
}
|
||||
|
||||
$schema['name'] = $schema['headline'];
|
||||
if ( ! isset( $schema['articleSection'] ) ) {
|
||||
global $post;
|
||||
$schema['articleSection'] = Helper::replace_vars( '%primary_taxonomy_terms%', $post );
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add timezone to startDate field.
|
||||
*
|
||||
* @param array $schema Event schema Data.
|
||||
* @return array
|
||||
*/
|
||||
public function validate_event_schema( $schema ) {
|
||||
if ( ! empty( $schema['startDate'] ) ) {
|
||||
$start_date = date_i18n( 'Y-m-d H:i:sP', strtotime( $schema['startDate'] ) );
|
||||
$schema['startDate'] = str_replace( ' ', 'T', $start_date );
|
||||
}
|
||||
|
||||
if ( ! empty( $schema['endDate'] ) ) {
|
||||
$end_date = date_i18n( 'Y-m-d H:i:sP', strtotime( $schema['endDate'] ) );
|
||||
$schema['endDate'] = str_replace( ' ', 'T', $end_date );
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect schema properties.
|
||||
*
|
||||
* @param array $schema Schema Entity.
|
||||
* @param string $id Schema Entity ID.
|
||||
* @param JsonLD $jsonld JsonLD Instance.
|
||||
* @param array $schemas Array of json-ld data.
|
||||
*/
|
||||
private function connect_properties( &$schema, $id, $jsonld, $schemas ) {
|
||||
if ( isset( $schema['isCustom'] ) ) {
|
||||
unset( $schema['isCustom'] );
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove empty ImageObject.
|
||||
if ( isset( $schema['image'] ) && empty( $schema['image']['url'] ) && ! is_array( $schema['image'] ) ) {
|
||||
unset( $schema['image'] );
|
||||
}
|
||||
|
||||
$jsonld->parts['canonical'] = ! empty( $jsonld->parts['canonical'] ) ? $jsonld->parts['canonical'] : \RankMath\Paper\Paper::get()->get_canonical();
|
||||
$schema['@id'] = $jsonld->parts['canonical'] . '#' . $id;
|
||||
|
||||
$types = array_map( 'strtolower', (array) $schema['@type'] );
|
||||
foreach ( $types as $type ) {
|
||||
$is_event = Str::contains( 'event', $type );
|
||||
// Add publisher entity @id in the organizer property of Event schema.
|
||||
if ( $is_event ) {
|
||||
$jsonld->add_prop( 'publisher', $schema, 'organizer', $schemas );
|
||||
}
|
||||
|
||||
$props = [
|
||||
'is_part_of' => [
|
||||
'key' => 'webpage',
|
||||
'value' => ! in_array( $type, [ 'jobposting', 'musicgroup', 'person', 'product', 'productgroup', 'restaurant', 'service' ], true ) && ! $is_event,
|
||||
],
|
||||
'publisher' => [
|
||||
'key' => 'publisher',
|
||||
'value' => ! in_array( $type, [ 'jobposting', 'musicgroup', 'person', 'product', 'productgroup', 'restaurant', 'service' ], true ) && ! $is_event,
|
||||
],
|
||||
'thumbnail' => [
|
||||
'key' => 'image',
|
||||
'value' => ! in_array( $type, [ 'videoobject' ], true ) || isset( $schema['image'] ),
|
||||
],
|
||||
'language' => [
|
||||
'key' => 'inLanguage',
|
||||
'value' => ! in_array( $type, [ 'person', 'service', 'restaurant', 'product', 'productgroup', 'musicgroup', 'musicalbum', 'jobposting' ], true ),
|
||||
],
|
||||
];
|
||||
|
||||
if ( isset( $schema['image'] ) && 'product' === $type && is_array( $schema['image'] ) ) {
|
||||
$props['thumbnail']['value'] = false;
|
||||
}
|
||||
|
||||
foreach ( $props as $prop => $data ) {
|
||||
if ( ! $data['value'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$jsonld->add_prop( $prop, $schema, $data['key'], $schemas );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add mainEntityOfPage property to Primary schema entity.
|
||||
*
|
||||
* @param array $schema Schema Entity.
|
||||
* @param JsonLD $jsonld JsonLD Instance.
|
||||
*/
|
||||
private function add_main_entity_of_page( &$schema, $jsonld ) {
|
||||
if ( ! isset( $schema['isPrimary'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! empty( $schema['isPrimary'] ) ) {
|
||||
$schema['mainEntityOfPage'] = [ '@id' => $jsonld->parts['canonical'] . '#webpage' ];
|
||||
}
|
||||
|
||||
unset( $schema['isPrimary'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove Person entity if it is not referenced in any other entities.
|
||||
*
|
||||
* @param array $data Array of json-ld data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function remove_person_entity( $data ) {
|
||||
if ( empty( $data['ProfilePage'] ) || empty( $data['ProfilePage']['@id'] ) || ! is_singular() ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( function_exists( 'bp_is_user' ) && bp_is_user() ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$temp_data = $data;
|
||||
$id = $temp_data['ProfilePage']['@id'];
|
||||
$ids = [];
|
||||
|
||||
unset( $temp_data['ProfilePage'] );
|
||||
array_walk_recursive(
|
||||
$temp_data,
|
||||
function( $value, $key ) use ( &$ids, $id ) {
|
||||
if ( '@id' === $key && $value === $id ) {
|
||||
$ids[] = $value;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if ( empty( $ids ) ) {
|
||||
unset( $data['ProfilePage'] );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change WebPage entity type depending on the schemas on the page.
|
||||
*
|
||||
* @param array $schemas Schema data.
|
||||
* @param array $types Schema types.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function change_webpage_entity( $schemas, $types ) {
|
||||
if ( in_array( 'Product', $types, true ) ) {
|
||||
$schemas['WebPage']['@type'] = 'ItemPage';
|
||||
}
|
||||
|
||||
if ( isset( $schemas['howto'] ) && ! empty( $schemas['WebPage'] ) ) {
|
||||
$schemas['howto']['mainEntityOfPage'] = [ '@id' => $schemas['WebPage']['@id'] ];
|
||||
}
|
||||
|
||||
$faq_data = array_map(
|
||||
function( $schema ) {
|
||||
return isset( $schema['@type'] ) && 'FAQPage' === $schema['@type'];
|
||||
},
|
||||
$schemas
|
||||
);
|
||||
|
||||
$faq_key = is_array( $faq_data ) && ! empty( $faq_data ) ? key( array_filter( $faq_data ) ) : '';
|
||||
if ( ! $faq_key ) {
|
||||
return $schemas;
|
||||
}
|
||||
|
||||
if ( in_array( $faq_key, array_keys( $schemas ), true ) ) {
|
||||
$schemas['WebPage']['@type'] =
|
||||
! empty( $types )
|
||||
? array_merge( (array) $schemas['WebPage']['@type'], [ 'FAQPage' ] )
|
||||
: 'FAQPage';
|
||||
|
||||
$schemas['WebPage']['mainEntity'] = $schemas[ $faq_key ]['mainEntity'];
|
||||
|
||||
unset( $schemas[ $faq_key ] );
|
||||
}
|
||||
|
||||
return $schemas;
|
||||
}
|
||||
}
|
@@ -0,0 +1,794 @@
|
||||
<?php
|
||||
/**
|
||||
* Output the Schema.org markup in JSON-LD format.
|
||||
*
|
||||
* @since 0.9.0
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Helpers\Url;
|
||||
use RankMath\Helpers\Str;
|
||||
use RankMath\Helpers\Arr;
|
||||
use RankMath\Paper\Paper;
|
||||
use RankMath\Traits\Hooker;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* JsonLD class.
|
||||
*/
|
||||
class JsonLD {
|
||||
|
||||
use Hooker;
|
||||
|
||||
/**
|
||||
* Hold post object.
|
||||
*
|
||||
* @var WP_Post
|
||||
*/
|
||||
public $post = null;
|
||||
|
||||
/**
|
||||
* Hold post ID.
|
||||
*
|
||||
* @var ID
|
||||
*/
|
||||
public $post_id = 0;
|
||||
|
||||
/**
|
||||
* Hold post parts.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $parts = [];
|
||||
|
||||
/**
|
||||
* The Constructor.
|
||||
*/
|
||||
public function setup() {
|
||||
$this->action( 'rank_math/head', 'json_ld', 90 );
|
||||
$this->action( 'rank_math/json_ld', 'add_context_data' );
|
||||
$this->action( 'rank_math/json_ld/preview', 'generate_preview' );
|
||||
new Block_Parser();
|
||||
new Frontend();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Schema Preview. Used in the Code Validation in Pro.
|
||||
*/
|
||||
public function generate_preview() {
|
||||
global $post;
|
||||
|
||||
if ( is_singular() ) {
|
||||
$this->post = $post;
|
||||
$this->post_id = $post->ID;
|
||||
$this->get_parts();
|
||||
}
|
||||
|
||||
$data = $this->do_filter( 'json_ld', [], $this );
|
||||
unset( $data['BreadcrumbList'] );
|
||||
|
||||
// Preview schema.
|
||||
$schema = \json_decode( file_get_contents( 'php://input' ), true );
|
||||
$schema_id = $schema['schemaID'];
|
||||
if ( isset( $data[ $schema_id ] ) ) {
|
||||
$current_data = $data[ $schema_id ];
|
||||
unset( $data[ $schema_id ] );
|
||||
} else {
|
||||
$current_data = array_pop( $data );
|
||||
}
|
||||
unset( $schema['schemaID'] );
|
||||
|
||||
$schema = $this->replace_variables( $schema );
|
||||
$schema = $this->filter( $schema, $this, $data );
|
||||
$schema = wp_parse_args( $schema['schema'], $current_data );
|
||||
if ( ! empty( $schema['@type'] ) && in_array( $schema['@type'], [ 'WooCommerceProduct', 'EDDProduct' ], true ) ) {
|
||||
$schema['@type'] = 'Product';
|
||||
}
|
||||
|
||||
// Merge.
|
||||
$data = array_merge( $data, [ $schema_id => $schema ] );
|
||||
|
||||
/**
|
||||
* Filter to change the Code validation data..
|
||||
*
|
||||
* @param array $unsigned An array of data to output in JSON-LD.
|
||||
*/
|
||||
$data = $this->do_filter( 'schema/preview/validate', $this->do_filter( 'schema/validated_data', $this->validate_schema( $data ) ) );
|
||||
|
||||
echo wp_json_encode( array_values( $data ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get Old schema data. This code is used in the Schema_Converter to convert old schema data.
|
||||
*
|
||||
* @param int $post_id Post id for conversion.
|
||||
* @param mixed $class Class instance of snippet.
|
||||
* @return array
|
||||
*/
|
||||
public function get_old_schema( $post_id, $class ) {
|
||||
global $post;
|
||||
$post = get_post( $post_id ); // phpcs:ignore
|
||||
$this->post = $post;
|
||||
$this->post_id = $post_id;
|
||||
setup_postdata( $post );
|
||||
$this->get_parts();
|
||||
|
||||
/**
|
||||
* Collect data to output in JSON-LD.
|
||||
*
|
||||
* @param array $unsigned An array of data to output in json-ld.
|
||||
* @param JsonLD $unsigned JsonLD instance.
|
||||
*/
|
||||
return $class->process( [], $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the ld+json tag with the generated schema data.
|
||||
*/
|
||||
public function json_ld() {
|
||||
global $post;
|
||||
|
||||
if ( is_singular() ) {
|
||||
$this->post = $post;
|
||||
$this->post_id = $post->ID;
|
||||
$this->get_parts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to collect data to output in JSON-LD.
|
||||
*
|
||||
* @param array $unsigned An array of data to output in JSON-LD.
|
||||
* @param JsonLD $unsigned JsonLD instance.
|
||||
*/
|
||||
$data = $this->do_filter( 'json_ld', [], $this );
|
||||
$data = $this->do_filter( 'schema/validated_data', $this->validate_schema( $data ) );
|
||||
if ( is_array( $data ) && ! empty( $data ) ) {
|
||||
|
||||
$class = defined( 'RANK_MATH_PRO_FILE' ) ? 'schema-pro' : 'schema';
|
||||
$json = [
|
||||
'@context' => 'https://schema.org',
|
||||
'@graph' => array_values( $data ),
|
||||
];
|
||||
|
||||
$options = defined( 'RANKMATH_DEBUG' ) && RANKMATH_DEBUG ? JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES : JSON_UNESCAPED_SLASHES;
|
||||
|
||||
echo '<script type="application/ld+json" class="rank-math-' . esc_attr( $class ) . '">' . wp_json_encode( wp_kses_post_deep( $json ), $options ) . '</script>' . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate schema data. Removes invalid and empty values from the schema data.
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function validate_schema( $data ) {
|
||||
if ( ! is_array( $data ) || empty( $data ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ( $data as $id => $value ) {
|
||||
if ( is_array( $value ) ) {
|
||||
// Remove aline @type.
|
||||
if ( isset( $value['@type'] ) && 1 === count( $value ) ) {
|
||||
unset( $data[ $id ] );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove empty review.
|
||||
if ( 'review' === $id && isset( $value['@type'] ) ) {
|
||||
if ( ! isset( $value['reviewRating'] ) || ! isset( $value['reviewRating']['ratingValue'] ) ) {
|
||||
unset( $data[ $id ] );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Recursive.
|
||||
$data[ $id ] = $this->validate_schema( $value );
|
||||
}
|
||||
|
||||
// Remove empty values.
|
||||
// Remove need of array_filter as this will go recursive.
|
||||
if ( '' === $value ) {
|
||||
unset( $data[ $id ] );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Default Schema Data.
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_schema( $data ) {
|
||||
global $post;
|
||||
|
||||
$schema = DB::get_schemas( $post->ID );
|
||||
|
||||
return array_merge( $data, $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Default Schema Data.
|
||||
*
|
||||
* @param array $data Array of json-ld data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_context_data( $data ) {
|
||||
$is_product_archive = $this->is_product_archive_page();
|
||||
$can_add_global = $this->can_add_global_entities( $data, $is_product_archive );
|
||||
$snippets = [
|
||||
'\\RankMath\\Schema\\Publisher' => ! isset( $data['publisher'] ) && $can_add_global,
|
||||
'\\RankMath\\Schema\\Website' => $can_add_global,
|
||||
'\\RankMath\\Schema\\PrimaryImage' => is_singular() && ! post_password_required() && $can_add_global,
|
||||
'\\RankMath\\Schema\\Breadcrumbs' => $this->can_add_breadcrumb(),
|
||||
'\\RankMath\\Schema\\Webpage' => $can_add_global,
|
||||
'\\RankMath\\Schema\\Author' => is_author() || ( is_singular() && $can_add_global ),
|
||||
'\\RankMath\\Schema\\Products_Page' => $is_product_archive,
|
||||
'\\RankMath\\Schema\\Singular' => ! post_password_required() && is_singular(),
|
||||
];
|
||||
|
||||
foreach ( $snippets as $class => $can_run ) {
|
||||
if ( $can_run ) {
|
||||
$class = new $class();
|
||||
$data = $class->process( $data, $this );
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to replace variables used in Schema fields.
|
||||
*
|
||||
* @param array $schemas Schema to replace.
|
||||
* @param object $object Current Object.
|
||||
* @param array $data Array of json-ld data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function replace_variables( $schemas, $object = [], $data = [] ) {
|
||||
$new_schemas = [];
|
||||
$object = empty( $object ) ? get_queried_object() : $object;
|
||||
foreach ( $schemas as $key => $schema ) {
|
||||
if ( 'metadata' === $key ) {
|
||||
$new_schemas['isPrimary'] = ! empty( $schema['isPrimary'] );
|
||||
|
||||
if ( ! empty( $schema['type'] ) && 'custom' === $schema['type'] ) {
|
||||
$new_schemas['isCustom'] = true;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->replace_author( $schema, $data );
|
||||
if ( is_array( $schema ) ) {
|
||||
$new_schemas[ $key ] = $this->replace_variables( $schema, $object, $data );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Need this conditions to convert date to valid ISO 8601 format.
|
||||
if ( in_array( $key, [ 'datePublished', 'uploadDate' ], true ) && '%date(Y-m-dTH:i:sP)%' === $schema ) {
|
||||
$schema = '%date(Y-m-d\TH:i:sP)%';
|
||||
}
|
||||
if ( 'dateModified' === $key && '%modified(Y-m-dTH:i:sP)%' === $schema ) {
|
||||
$schema = '%modified(Y-m-d\TH:i:sP)%';
|
||||
}
|
||||
|
||||
$new_schemas[ $key ] = is_string( $schema ) && Str::contains( '%', $schema ) && ! filter_var( $schema, FILTER_VALIDATE_URL )
|
||||
? Helper::replace_vars( $schema, $object ) : $schema;
|
||||
if ( '' === $new_schemas[ $key ] ) {
|
||||
unset( $new_schemas[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $new_schemas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to replace %author% with Author entity @id.
|
||||
*
|
||||
* @param array $schema Schema to replace.
|
||||
* @param array $data Array of json-ld data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function replace_author( &$schema, $data ) {
|
||||
if ( empty( $data['ProfilePage'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( empty( $schema['author'] ) || ! isset( $schema['author']['name'] ) || ! in_array( $schema['author']['name'], [ '%name%', '%post_author%' ], true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$schema['author'] = [
|
||||
'@id' => $data['ProfilePage']['@id'],
|
||||
'name' => get_the_author(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter schema data before adding it to ld+json.
|
||||
*
|
||||
* @param array $schemas Schema to replace.
|
||||
* @param JsonLD $jsonld Instance of jsonld.
|
||||
* @param array $data Array of json-ld data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter( $schemas, $jsonld, $data ) {
|
||||
$new_schemas = [];
|
||||
|
||||
foreach ( $schemas as $key => $schema ) {
|
||||
$type = is_array( $schema['@type'] ) ? $schema['@type'][0] : $schema['@type'];
|
||||
$type = strtolower( $type );
|
||||
$type = in_array( $type, [ 'musicgroup', 'musicalbum' ], true )
|
||||
? 'music'
|
||||
: ( in_array( $type, [ 'blogposting', 'newsarticle' ], true ) ? 'article' : $type );
|
||||
$type = Str::contains( 'event', $type ) ? 'event' : $type;
|
||||
$hook = 'snippet/rich_snippet_' . $type;
|
||||
|
||||
/**
|
||||
* Short-circuit if 3rd party is interested generating his own data.
|
||||
*/
|
||||
$pre = $this->do_filter( $hook, false, $jsonld->parts, $data );
|
||||
if ( false !== $pre ) {
|
||||
$new_schemas[ $key ] = $this->do_filter( $hook . '_entity', $pre );
|
||||
$new_schemas[ $key ] = $this->do_filter( 'snippet/rich_snippet_entity', $new_schemas[ $key ] );
|
||||
continue;
|
||||
}
|
||||
|
||||
$new_schemas[ $key ] = $this->do_filter( $hook . '_entity', $schema );
|
||||
$new_schemas[ $key ] = $this->do_filter( 'snippet/rich_snippet_entity', $new_schemas[ $key ] );
|
||||
}
|
||||
|
||||
return $new_schemas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to add global schema entities.
|
||||
*
|
||||
* @param array $data Array of json-ld data.
|
||||
* @param bool $is_product_archive Whether the current page is a Product archive.
|
||||
* @return bool
|
||||
*/
|
||||
public function can_add_global_entities( $data = [], $is_product_archive = false ) {
|
||||
if ( ! $is_product_archive && ( is_category() || is_tag() || is_tax() ) ) {
|
||||
$queried_object = get_queried_object();
|
||||
return ! Helper::get_settings( 'titles.remove_' . $queried_object->taxonomy . '_snippet_data' ) && ! $this->do_filter( 'snippet/remove_taxonomy_data', false, $queried_object->taxonomy );
|
||||
}
|
||||
|
||||
if ( is_front_page() || ! is_singular() || ! Helper::can_use_default_schema( $this->post_id ) || ! empty( $data ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$schemas = DB::get_schemas( $this->post_id );
|
||||
if ( ! empty( $schemas ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow developer to remove global schema entities.
|
||||
*
|
||||
* @param bool $can_add
|
||||
* @param JsonLD $unsigned JsonLD instance.
|
||||
*/
|
||||
return $this->do_filter( 'schema/add_global_entities', Helper::get_default_schema_type( $this->post_id, true ), $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Can add breadcrumb schema.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function can_add_breadcrumb() {
|
||||
/**
|
||||
* Allow developer to disable the breadcrumb JSON-LD output.
|
||||
*
|
||||
* @param bool $unsigned Default: true
|
||||
*/
|
||||
return ! is_front_page() && Helper::is_breadcrumbs_enabled() && $this->do_filter( 'json_ld/breadcrumbs_enabled', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current page is a WooCommerce archive page.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_product_archive_page() {
|
||||
return Helper::is_woocommerce_active() && ( ( is_tax() && in_array( get_query_var( 'taxonomy' ), get_object_taxonomies( 'product' ), true ) ) || is_shop() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add property to entity.
|
||||
*
|
||||
* @param string $prop Name of the property to add into entity.
|
||||
* @param array $entity Array of json-ld entity.
|
||||
* @param string $key Property key to add into entity.
|
||||
* @param array $data Array of json-ld data.
|
||||
*/
|
||||
public function add_prop( $prop, &$entity, $key = '', $data = [] ) {
|
||||
if ( empty( $prop ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hash = [
|
||||
'email' => [ 'titles.email', 'email' ],
|
||||
'phone' => [ 'titles.phone', 'telephone' ],
|
||||
];
|
||||
|
||||
if ( isset( $hash[ $prop ] ) && $value = Helper::get_settings( $hash[ $prop ][0] ) ) { // phpcs:ignore
|
||||
$entity[ $hash[ $prop ][1] ] = $value;
|
||||
return;
|
||||
}
|
||||
|
||||
$perform = "add_prop_{$prop}";
|
||||
if ( method_exists( $this, $perform ) ) {
|
||||
$this->$perform( $entity, $key, $data );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add logo property to the entity.
|
||||
*
|
||||
* @param array $entity Array of JSON-LD entity.
|
||||
*/
|
||||
private function add_prop_image( &$entity ) {
|
||||
$logo = Helper::get_settings( 'titles.knowledgegraph_logo' );
|
||||
if ( ! $logo ) {
|
||||
$logo_id = \get_option( 'site_logo' );
|
||||
$logo = $logo_id ? wp_get_attachment_image_url( $logo_id ) : '';
|
||||
}
|
||||
|
||||
if ( ! $logo ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entity['logo'] = [
|
||||
'@type' => 'ImageObject',
|
||||
'@id' => home_url( '/#logo' ),
|
||||
'url' => $logo,
|
||||
'contentUrl' => $logo,
|
||||
'caption' => $this->get_website_name(),
|
||||
];
|
||||
$this->add_prop_language( $entity['logo'] );
|
||||
|
||||
$attachment = wp_get_attachment_metadata( Helper::get_settings( 'titles.knowledgegraph_logo_id' ), true );
|
||||
if ( ! $attachment ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entity['logo']['width'] = $attachment['width'];
|
||||
$entity['logo']['height'] = $attachment['height'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Language property to the entity.
|
||||
*
|
||||
* @param array $entity Array of JSON-LD entity.
|
||||
*/
|
||||
private function add_prop_language( &$entity ) {
|
||||
$entity['inLanguage'] = $this->do_filter( 'schema/language', get_bloginfo( 'language' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Image property to entity.
|
||||
*
|
||||
* @param array $entity Array of json-ld entity.
|
||||
* @param string $key Entity Key.
|
||||
* @param array $data Schema Data.
|
||||
*/
|
||||
private function add_prop_thumbnail( &$entity, $key, $data ) {
|
||||
if ( ! empty( $data['primaryImage'] ) ) {
|
||||
$entity[ $key ] = [ '@id' => $data['primaryImage']['@id'] ];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add isPartOf property to entity.
|
||||
*
|
||||
* @param array $entity Array of json-ld entity.
|
||||
* @param string $key Entity Key.
|
||||
*/
|
||||
private function add_prop_is_part_of( &$entity, $key ) {
|
||||
$hash = [
|
||||
'website' => home_url( '/#website' ),
|
||||
'webpage' => Paper::get()->get_canonical() . '#webpage',
|
||||
];
|
||||
|
||||
if ( ! empty( $hash[ $key ] ) ) {
|
||||
$entity['isPartOf'] = [ '@id' => $hash[ $key ] ];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add publisher property to entity
|
||||
*
|
||||
* @param array $entity Entity.
|
||||
* @param string $key Entity Key.
|
||||
* @param array $data Schema Data.
|
||||
*/
|
||||
public function add_prop_publisher( &$entity, $key, $data ) {
|
||||
if ( empty( $data['publisher'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entity[ $key ] = [ '@id' => $data['publisher']['@id'] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add url property to entity.
|
||||
*
|
||||
* @param array $entity Array of JSON-LD entity.
|
||||
*/
|
||||
private function add_prop_url( &$entity ) {
|
||||
if ( $url = Helper::get_settings( 'titles.url' ) ) { // phpcs:ignore
|
||||
$entity['url'] = ! Url::is_relative( $url ) ? $url : 'http://' . $url;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add address property to entity.
|
||||
*
|
||||
* @param array $entity Array of JSON-LD entity.
|
||||
*/
|
||||
private function add_prop_address( &$entity ) {
|
||||
if ( $address = Helper::get_settings( 'titles.local_address' ) ) { // phpcs:ignore
|
||||
$entity['address'] = [ '@type' => 'PostalAddress' ] + $address;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add aggregateratings to entity.
|
||||
*
|
||||
* @param string $schema Schema to get data for.
|
||||
* @param array $entity Array of JSON-LD entity to attach data to.
|
||||
*/
|
||||
public function add_ratings( $schema, &$entity ) {
|
||||
$rating = Helper::get_post_meta( "snippet_{$schema}_rating" );
|
||||
|
||||
// Early Bail!
|
||||
if ( ! $rating ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entity['review'] = [
|
||||
'author' => [
|
||||
'@type' => 'Person',
|
||||
'name' => get_the_author_meta( 'display_name' ),
|
||||
],
|
||||
'datePublished' => get_post_time( 'Y-m-d\TH:i:sP', false ),
|
||||
'dateModified' => get_post_modified_time( 'Y-m-d\TH:i:sP', false ),
|
||||
'reviewRating' => [
|
||||
'@type' => 'Rating',
|
||||
'ratingValue' => $rating,
|
||||
'bestRating' => Helper::get_post_meta( "snippet_{$schema}_rating_max" ) ? Helper::get_post_meta( "snippet_{$schema}_rating_max" ) : 5,
|
||||
'worstRating' => Helper::get_post_meta( "snippet_{$schema}_rating_min" ) ? Helper::get_post_meta( "snippet_{$schema}_rating_min" ) : 1,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get website name with a fallback to bloginfo( 'name' ).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_website_name() {
|
||||
return Helper::get_settings( 'titles.website_name', $this->get_organization_name() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get website name with a fallback to bloginfo( 'name' ).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_organization_name() {
|
||||
$name = Helper::get_settings( 'titles.knowledgegraph_name' );
|
||||
return $name ? $name : get_bloginfo( 'name' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set publisher/provider data for JSON-LD.
|
||||
*
|
||||
* @param array $entity Array of JSON-LD entity.
|
||||
* @param array $organization Organization data.
|
||||
* @param string $type Type data set to. Default: 'publisher'.
|
||||
*/
|
||||
public function set_publisher( &$entity, $organization, $type = 'publisher' ) {
|
||||
$keys = [ '@context', '@type', 'url', 'name', 'logo', 'image', 'contactPoint', 'sameAs' ];
|
||||
foreach ( $keys as $key ) {
|
||||
if ( ! isset( $organization[ $key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$entity[ $type ][ $key ] = 'logo' !== $key ? $organization[ $key ] : [
|
||||
'@type' => 'ImageObject',
|
||||
'url' => $organization[ $key ],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set address for JSON-LD.
|
||||
*
|
||||
* @param string $schema Schema to get data for.
|
||||
* @param array $entity Array of JSON-LD entity to attach data to.
|
||||
*/
|
||||
public function set_address( $schema, &$entity ) {
|
||||
$address = Helper::get_post_meta( "snippet_{$schema}_address" );
|
||||
|
||||
// Early Bail!
|
||||
if ( ! is_array( $address ) || empty( $address ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entity['address'] = [ '@type' => 'PostalAddress' ];
|
||||
foreach ( $address as $key => $value ) {
|
||||
$entity['address'][ $key ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set data to entity.
|
||||
*
|
||||
* Loop through post meta value grab data and attache it to the entity.
|
||||
*
|
||||
* @param array $hash Key to get data and Value to save as.
|
||||
* @param array $entity Array of JSON-LD entity to attach data to.
|
||||
*/
|
||||
public function set_data( $hash, &$entity ) {
|
||||
foreach ( $hash as $metakey => $dest ) {
|
||||
$entity[ $dest ] = Helper::get_post_meta( $metakey, $this->post_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get post title.
|
||||
*
|
||||
* Retrieves the title in this order.
|
||||
* 1. Custom post meta set in rich snippet
|
||||
* 2. Headline template set in Titles & Meta
|
||||
*
|
||||
* @param int $post_id Post ID to get title for.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_post_title( $post_id = 0 ) {
|
||||
$title = Helper::get_post_meta( 'snippet_name', $post_id );
|
||||
|
||||
if ( ! $title && ! empty( $this->post ) ) {
|
||||
$title = Helper::replace_vars( Helper::get_settings( "titles.pt_{$this->post->post_type}_default_snippet_name", '%seo_title%' ), $this->post );
|
||||
}
|
||||
|
||||
$title = $title ? $title : Paper::get()->get_title();
|
||||
|
||||
return Str::truncate( $title );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get post url.
|
||||
*
|
||||
* @param int $post_id Post ID to get URL for.
|
||||
* @return string
|
||||
*/
|
||||
public function get_post_url( $post_id = 0 ) {
|
||||
$url = Helper::get_post_meta( 'snippet_url', $post_id );
|
||||
|
||||
return $url ? $url : ( 0 === $post_id ? Paper::get()->get_canonical() : get_the_permalink( $post_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get product description.
|
||||
*
|
||||
* @param object $product Product Object.
|
||||
* @return string
|
||||
*/
|
||||
public function get_product_desc( $product = [] ) {
|
||||
if ( empty( $product ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $description = Helper::get_post_meta( 'description', $product->get_id() ) ) { //phpcs:ignore
|
||||
return $description;
|
||||
}
|
||||
|
||||
$product_object = get_post( $product->get_id() );
|
||||
$description = Paper::get_from_options( 'pt_product_description', $product_object, '%excerpt%' );
|
||||
|
||||
if ( ! $description ) {
|
||||
$description = $product->get_short_description() ? $product->get_short_description() : $product->get_description();
|
||||
}
|
||||
|
||||
$description = $this->do_filter( 'product_description/apply_shortcode', false ) ? do_shortcode( $description ) : Helper::strip_shortcodes( $description );
|
||||
return wp_strip_all_tags( $description, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get product title.
|
||||
*
|
||||
* @param object $product Product Object.
|
||||
* @return string
|
||||
*/
|
||||
public function get_product_title( $product = [] ) {
|
||||
if ( empty( $product ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( $title = Helper::get_post_meta( 'title', $product->get_id() ) ) { //phpcs:ignore
|
||||
return $title;
|
||||
}
|
||||
|
||||
$product_object = get_post( $product->get_id() );
|
||||
|
||||
$title = Paper::get_from_options( 'pt_product_title', $product_object, '%title% %sep% %sitename%' );
|
||||
return $title ? $title : $product->get_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get post parts.
|
||||
*/
|
||||
private function get_parts() {
|
||||
$parts = [
|
||||
'title' => $this->get_post_title(),
|
||||
'url' => $this->get_post_url(),
|
||||
'canonical' => Paper::get()->get_canonical(),
|
||||
'modified' => mysql2date( DATE_W3C, $this->post->post_modified, false ),
|
||||
'published' => mysql2date( DATE_W3C, $this->post->post_date, false ),
|
||||
'excerpt' => Helper::replace_vars( '%excerpt%', $this->post ),
|
||||
];
|
||||
|
||||
// Description.
|
||||
$desc = Helper::get_post_meta( 'snippet_desc' );
|
||||
|
||||
if ( ! $desc ) {
|
||||
$desc = Helper::replace_vars( Helper::get_settings( "titles.pt_{$this->post->post_type}_default_snippet_desc" ), $this->post );
|
||||
}
|
||||
$parts['desc'] = $desc ? $desc : ( Helper::get_post_meta( 'description' ) ? Helper::get_post_meta( 'description' ) : $parts['excerpt'] );
|
||||
|
||||
// Author.
|
||||
$author = Helper::get_post_meta( 'snippet_author' );
|
||||
$parts['author'] = $author ? $author : get_the_author_meta( 'display_name', $this->post->post_author );
|
||||
|
||||
// Modified date cannot be before publish date.
|
||||
if ( strtotime( $this->post->post_modified ) < strtotime( $this->post->post_date ) ) {
|
||||
$parts['modified'] = $parts['published'];
|
||||
}
|
||||
|
||||
$this->parts = $parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get global social profile URLs, to use in the `sameAs` property.
|
||||
*
|
||||
* @link https://developers.google.com/webmasters/structured-data/customize/social-profiles
|
||||
*/
|
||||
public function get_social_profiles() {
|
||||
$profiles = [ Helper::get_settings( 'titles.social_url_facebook' ) ];
|
||||
|
||||
$twitter = Helper::get_settings( 'titles.twitter_author_names' );
|
||||
if ( $twitter ) {
|
||||
$profiles[] = "https://twitter.com/$twitter";
|
||||
}
|
||||
|
||||
$addional_profiles = Helper::get_settings( 'titles.social_additional_profiles' );
|
||||
if ( ! empty( $addional_profiles ) ) {
|
||||
$profiles = array_merge( $profiles, Arr::from_string( $addional_profiles, "\n" ) );
|
||||
}
|
||||
|
||||
return array_values( array_filter( $profiles ) );
|
||||
}
|
||||
}
|
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
/**
|
||||
* Output Opengraph tags for specific schema types.
|
||||
*
|
||||
* @since 1.0.56
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Traits\Hooker;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Opengraph class.
|
||||
*/
|
||||
class Opengraph {
|
||||
|
||||
use Hooker;
|
||||
|
||||
/**
|
||||
* The Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->action( 'rank_math/opengraph/facebook', 'add_schema_tags', 90 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the schema opengraph tags.
|
||||
*
|
||||
* @param OpenGraph $opengraph The current opengraph network object.
|
||||
*/
|
||||
public function add_schema_tags( $opengraph ) {
|
||||
if ( ! is_singular() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$schemas = $this->get_schemas();
|
||||
if ( empty( $schemas ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hash = [
|
||||
'VideoObject' => 'video',
|
||||
'Product' => 'product',
|
||||
'Article' => 'article',
|
||||
'NewsArticle' => 'article',
|
||||
'BlogPosting' => 'article',
|
||||
];
|
||||
|
||||
foreach ( $schemas as $schema ) {
|
||||
$method = $hash[ $schema['@type'] ];
|
||||
$this->$method( $schema, $opengraph );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get schema data.
|
||||
*/
|
||||
private function get_schemas() {
|
||||
global $post;
|
||||
$schemas = array_filter(
|
||||
DB::get_schemas( $post->ID ),
|
||||
function( $schema ) {
|
||||
return ! empty( $schema['@type'] ) && in_array( $schema['@type'], [ 'Article', 'NewsArticle', 'BlogPosting', 'Product', 'VideoObject' ], true );
|
||||
}
|
||||
);
|
||||
|
||||
if ( ! empty( $schemas ) ) {
|
||||
return $schemas;
|
||||
}
|
||||
|
||||
$default_schema = Helper::get_default_schema_type( $post->ID, true, false );
|
||||
if ( ! in_array( $default_schema, [ 'Article', 'BlogPosting', 'NewsArticle' ], true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
'@type' => $default_schema,
|
||||
'datePublished' => '%date(Y-m-d\TH:i:sP)%',
|
||||
'dateModified' => '%modified(Y-m-d\TH:i:sP)%',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Output Video Schema tags.
|
||||
*
|
||||
* @param array $schema Schema Data.
|
||||
* @param OpenGraph $opengraph The current opengraph network object.
|
||||
*/
|
||||
private function video( $schema, $opengraph ) {
|
||||
$video_url = ! empty( $schema['contentUrl'] ) ? $schema['contentUrl'] : ( ! empty( $schema['embedUrl'] ) ? $schema['embedUrl'] : '' );
|
||||
if ( ! $video_url ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$opengraph->tag( 'og:video', $video_url );
|
||||
if ( ! empty( $schema['duration'] ) ) {
|
||||
global $post;
|
||||
$opengraph->tag( 'video:duration', Helper::duration_to_seconds( Helper::replace_vars( $schema['duration'], $post ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output Product Schema tags.
|
||||
*
|
||||
* @param array $schema Schema Data.
|
||||
* @param OpenGraph $opengraph The current opengraph network object.
|
||||
*/
|
||||
public function product( $schema, $opengraph ) {
|
||||
if ( isset( $schema['brand'], $schema['brand']['name'] ) ) {
|
||||
$opengraph->tag( 'product:brand', $schema['brand']['name'] );
|
||||
}
|
||||
|
||||
$tags = [
|
||||
'product:price:amount' => ! empty( $schema['offers']['price'] ) ? $schema['offers']['price'] : '',
|
||||
'product:price:currency' => ! empty( $schema['offers']['priceCurrency'] ) ? $schema['offers']['priceCurrency'] : '',
|
||||
'product:availability' => ! empty( $schema['offers']['availability'] ) && 'instock' === $schema['offers']['availability'] ? 'instock' : '',
|
||||
];
|
||||
|
||||
foreach ( $tags as $tag => $value ) {
|
||||
$opengraph->tag( $tag, $value );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output Article Schema tags.
|
||||
*
|
||||
* @param array $schema Schema Data.
|
||||
* @param OpenGraph $opengraph The current opengraph network object.
|
||||
*/
|
||||
private function article( $schema, $opengraph ) {
|
||||
if ( empty( $schema['datePublished'] ) || empty( $schema['dateModified'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $post;
|
||||
$pub = '%date(Y-m-dTH:i:sP)%' === $schema['datePublished'] ? '%date(Y-m-d\TH:i:sP)%' : $schema['datePublished'];
|
||||
$mod = '%modified(Y-m-dTH:i:sP)%' === $schema['dateModified'] ? '%modified(Y-m-d\TH:i:sP)%' : $schema['dateModified'];
|
||||
$pub = Helper::replace_vars( $pub, $post );
|
||||
$mod = Helper::replace_vars( $mod, $post );
|
||||
$opengraph->tag( 'article:published_time', $pub );
|
||||
if ( $mod !== $pub ) {
|
||||
$opengraph->tag( 'article:modified_time', $mod );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* The Schema Module
|
||||
*
|
||||
* @since 0.9.0
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Traits\Hooker;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Schema class.
|
||||
*/
|
||||
class Schema {
|
||||
|
||||
use Hooker;
|
||||
|
||||
/**
|
||||
* The Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
if ( is_admin() ) {
|
||||
new Admin();
|
||||
}
|
||||
$this->action( 'wp', 'integrations' );
|
||||
$this->filter( 'rank_math/elementor/dark_styles', 'add_dark_style' );
|
||||
|
||||
new Blocks();
|
||||
new Snippet_Shortcode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add dark style
|
||||
*
|
||||
* @param array $styles The dark mode styles.
|
||||
*/
|
||||
public function add_dark_style( $styles = [] ) {
|
||||
$styles['rank-math-schema-dark'] = rank_math()->plugin_url() . 'includes/modules/schema/assets/css/schema-dark.css';
|
||||
|
||||
return $styles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize integrations.
|
||||
*/
|
||||
public function integrations() {
|
||||
$type = get_query_var( 'sitemap' );
|
||||
if ( ! empty( $type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
( new JsonLD() )->setup();
|
||||
}
|
||||
}
|
@@ -0,0 +1,516 @@
|
||||
<?php
|
||||
/**
|
||||
* The Schema Shortcode
|
||||
*
|
||||
* @since 1.0.24
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Traits\Hooker;
|
||||
use RankMath\Traits\Shortcode;
|
||||
use RankMath\Helpers\Str;
|
||||
use RankMath\Helpers\Param;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Snippet_Shortcode class.
|
||||
*/
|
||||
class Snippet_Shortcode {
|
||||
|
||||
use Hooker, Shortcode;
|
||||
|
||||
/**
|
||||
* Post object.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
private $post;
|
||||
|
||||
/**
|
||||
* Schema data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $schema;
|
||||
|
||||
/**
|
||||
* The Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->add_shortcode( 'rank_math_rich_snippet', 'rich_snippet' );
|
||||
$this->add_shortcode( 'rank_math_review_snippet', 'rich_snippet' );
|
||||
|
||||
if ( ! is_admin() ) {
|
||||
$this->filter( 'the_content', 'output_schema_in_content', 11 );
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'register_block_type' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
register_block_type(
|
||||
'rank-math/rich-snippet',
|
||||
[
|
||||
'render_callback' => [ $this, 'rich_snippet' ],
|
||||
'attributes' => [
|
||||
'id' => [
|
||||
'default' => '',
|
||||
'type' => 'string',
|
||||
],
|
||||
'post_id' => [
|
||||
'default' => '',
|
||||
'type' => 'integer',
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema shortcode.
|
||||
*
|
||||
* @param array $atts Optional. Shortcode arguments - currently only 'show'
|
||||
* parameter, which is a comma-separated list of elements to show.
|
||||
*
|
||||
* @return string Shortcode output.
|
||||
*/
|
||||
public function rich_snippet( $atts ) {
|
||||
|
||||
$atts = shortcode_atts(
|
||||
[
|
||||
'id' => false,
|
||||
'post_id' => Param::get( 'post_id' ) ? Param::get( 'post_id' ) : get_the_ID(),
|
||||
'className' => '',
|
||||
],
|
||||
$atts,
|
||||
'rank_math_rich_snippet'
|
||||
);
|
||||
|
||||
if ( 'edit' === Param::get( 'context' ) ) {
|
||||
rank_math()->variables->setup();
|
||||
}
|
||||
|
||||
$data = $this->get_schema_data( $atts['id'], $atts['post_id'] );
|
||||
if ( empty( $data ) || empty( $data['schema'] ) ) {
|
||||
return esc_html__( 'No schema found.', 'rank-math' );
|
||||
}
|
||||
|
||||
$post = get_post( $data['post_id'] );
|
||||
$schemas = ! empty( $atts['id'] ) ? [ $data['schema'] ] : $data['schema'];
|
||||
|
||||
$html = '';
|
||||
|
||||
foreach ( $schemas as $schema ) {
|
||||
|
||||
$schema = $this->replace_variables( $schema, $post );
|
||||
$schema = $this->do_filter( 'schema/shortcode/filter_attributes', $schema, $atts );
|
||||
|
||||
/**
|
||||
* Change the Schema HTML output.
|
||||
*
|
||||
* @param string $unsigned HTML output.
|
||||
* @param array $schema Schema data.
|
||||
* @param WP_Post $post The post instance.
|
||||
* @param Snippet_Shortcode $this Snippet_Shortcode instance.
|
||||
*/
|
||||
$html .= $this->do_filter( 'snippet/html', $this->get_snippet_content( $schema, $post, $atts ), $schema, $post, $this );
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Snippet content.
|
||||
*
|
||||
* @param array $schema Schema to replace.
|
||||
* @param WP_Post $post Post schema attached to.
|
||||
* @param array $atts Optional. Shortcode arguments - currently only 'show'
|
||||
* parameter, which is a comma-separated list of elements to show.
|
||||
*
|
||||
* @return string Shortcode output.
|
||||
*/
|
||||
public function get_snippet_content( $schema, $post, $atts ) {
|
||||
wp_enqueue_style( 'rank-math-review-snippet', rank_math()->assets() . 'css/rank-math-snippet.css', null, rank_math()->version );
|
||||
|
||||
$type = \strtolower( $schema['@type'] );
|
||||
$type = preg_replace( '/[^a-z0-9_-]+/i', '', $type );
|
||||
$this->post = $post;
|
||||
$this->schema = $schema;
|
||||
|
||||
if ( in_array( $type, [ 'article', 'blogposting', 'newsarticle' ], true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Str::ends_with( 'event', $type ) ) {
|
||||
$type = 'event';
|
||||
}
|
||||
|
||||
if ( 'resturant' === $type ) {
|
||||
$type = 'restaurant';
|
||||
}
|
||||
|
||||
$class = ! empty( $atts['className'] ) ? $atts['className'] : '';
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div id="rank-math-rich-snippet-wrapper" class="<?php echo esc_attr( $class ); ?>">
|
||||
|
||||
<?php
|
||||
$type = sanitize_file_name( $type );
|
||||
$file = rank_math()->plugin_dir() . "includes/modules/schema/shortcode/$type.php";
|
||||
if ( file_exists( $file ) ) {
|
||||
include $file;
|
||||
}
|
||||
|
||||
$this->do_action( 'snippet/after_schema_content', $this );
|
||||
?>
|
||||
|
||||
</div>
|
||||
<?php
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field value.
|
||||
*
|
||||
* @param string $field_id Field id.
|
||||
* @param mixed $default Default value.
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_field_value( $field_id, $default = null ) {
|
||||
$array = $this->schema;
|
||||
if ( isset( $array[ $field_id ] ) ) {
|
||||
if ( isset( $array[ $field_id ]['@type'] ) ) {
|
||||
unset( $array[ $field_id ]['@type'] );
|
||||
}
|
||||
|
||||
return $array[ $field_id ];
|
||||
}
|
||||
|
||||
foreach ( explode( '.', $field_id ) as $segment ) {
|
||||
if ( ! is_array( $array ) || ! array_key_exists( $segment, $array ) ) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$array = $array[ $segment ];
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field.
|
||||
*
|
||||
* @param string $title Field title.
|
||||
* @param string $field_id Field id to get value.
|
||||
* @param string $convert_date Convert date value to proper format.
|
||||
* @param mixed $default Default value.
|
||||
*/
|
||||
public function get_field( $title, $field_id, $convert_date = false, $default = null ) {
|
||||
$value = $this->get_field_value( $field_id, $default );
|
||||
if ( empty( $value ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $convert_date ) {
|
||||
$value = Helper::convert_date( $value );
|
||||
}
|
||||
|
||||
$this->output_field( $title, $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Opening hours data.
|
||||
*
|
||||
* @param string $field_id Field id to get value.
|
||||
*/
|
||||
public function get_opening_hours( $field_id ) {
|
||||
$opening_hours = $this->get_field_value( $field_id );
|
||||
if ( empty( $opening_hours ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( count( array_filter( array_keys( $opening_hours ), 'is_string' ) ) > 0 ) {
|
||||
$this->get_opening_hour( $opening_hours );
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<div>';
|
||||
echo '<strong>' . esc_html__( 'Opening Hours', 'rank-math' ) . '</strong><br />';
|
||||
foreach ( $opening_hours as $opening_hour ) {
|
||||
$this->get_opening_hour( $opening_hour );
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Opening hours.
|
||||
*
|
||||
* @param array $opening_hour Opening hours data.
|
||||
*/
|
||||
public function get_opening_hour( $opening_hour ) {
|
||||
$labels = [
|
||||
'dayOfWeek' => esc_html__( 'Days', 'rank-math' ),
|
||||
'opens' => esc_html__( 'Opening Time', 'rank-math' ),
|
||||
'closes' => esc_html__( 'Closing Time', 'rank-math' ),
|
||||
];
|
||||
foreach ( $labels as $key => $label ) {
|
||||
if ( empty( $opening_hour[ $key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->output_field( $label, $opening_hour[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field.
|
||||
*
|
||||
* @param string $title Field title.
|
||||
* @param mixed $value Field value.
|
||||
*/
|
||||
public function output_field( $title, $value ) {
|
||||
?>
|
||||
<p>
|
||||
<strong><?php echo esc_html( $title ); // phpcs:ignore ?>: </strong>
|
||||
<?php echo is_array( $value ) ? implode( ', ', $value ) : wp_kses_post( $value ); // phpcs:ignore ?>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title.
|
||||
*/
|
||||
public function get_title() {
|
||||
if ( ! isset( $this->schema['name'] ) && ! isset( $this->schema['title'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$title = isset( $this->schema['title'] ) ? $this->schema['title'] : $this->schema['name'];
|
||||
$title = $title && '' !== $title ? $title : Helper::replace_vars( '%title%', $this->post );
|
||||
?>
|
||||
<h5 class="rank-math-title"><?php echo esc_html( $title ); // phpcs:ignore ?></h5>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Get description.
|
||||
*
|
||||
* @param string $description Schema description field.
|
||||
*/
|
||||
public function get_description( $description = 'description' ) {
|
||||
$excerpt = Helper::replace_vars( '%excerpt%', $this->post );
|
||||
if ( $description && '' !== $description ) {
|
||||
$description = $this->get_field_value( $description );
|
||||
}
|
||||
$description = $description && '' !== $description ? $description : ( $excerpt ? $excerpt : Helper::get_post_meta( 'description', $this->post->ID ) );
|
||||
?>
|
||||
<p><?php echo wp_kses_post( do_shortcode( $description ) ); ?></p>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image.
|
||||
*/
|
||||
public function get_image() {
|
||||
if ( ! isset( $this->schema['image'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$image = Helper::get_thumbnail_with_fallback( $this->post->ID, 'medium' );
|
||||
if ( empty( $image ) ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<div class="rank-math-review-image">
|
||||
<img src="<?php echo esc_url( $image[0] ); ?>" />
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Display nicely formatted reviews.
|
||||
*
|
||||
* @param string $field_id Field id to get value.
|
||||
*/
|
||||
public function show_ratings( $field_id = 'review.reviewRating.ratingValue' ) {
|
||||
$rating = (float) $this->get_field_value( $field_id );
|
||||
if ( empty( $rating ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$best_rating = (int) $this->get_field_value( 'review.reviewRating.bestRating', 5 );
|
||||
?>
|
||||
<div class="rank-math-total-wrapper">
|
||||
|
||||
<strong><?php echo $this->do_filter( 'review/text', esc_html__( 'Editor\'s Rating:', 'rank-math' ) ); // phpcs:ignore ?></strong><br />
|
||||
|
||||
<span class="rank-math-total"><?php echo $rating; // phpcs:ignore ?></span>
|
||||
|
||||
<div class="rank-math-review-star">
|
||||
|
||||
<div class="rank-math-review-result-wrapper">
|
||||
|
||||
<?php echo \str_repeat( '<i class="rank-math-star"></i>', $best_rating ); // phpcs:ignore ?>
|
||||
|
||||
<div class="rank-math-review-result" style="width:<?php echo ( $rating * ( 100 / $best_rating ) ); // phpcs:ignore ?>%;">
|
||||
<?php echo \str_repeat( '<i class="rank-math-star"></i>', $best_rating ); // phpcs:ignore ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Add schema data in the content.
|
||||
*
|
||||
* @param string $content Post content.
|
||||
* @return string
|
||||
*
|
||||
* @since 1.0.12
|
||||
*/
|
||||
public function output_schema_in_content( $content ) {
|
||||
if ( ! is_singular() ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$schemas = $this->get_schemas();
|
||||
if ( empty( $schemas ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
foreach ( $schemas as $schema ) {
|
||||
$location = $this->get_content_location( $schema );
|
||||
if ( false === $location || 'custom' === $location ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$review = do_shortcode( '[rank_math_rich_snippet id="' . $schema['metadata']['shortcode'] . '"]' );
|
||||
if ( in_array( $location, [ 'top', 'both' ], true ) ) {
|
||||
$content = $review . $content;
|
||||
}
|
||||
|
||||
if ( in_array( $location, [ 'bottom', 'both' ], true ) && $this->can_add_multi_page() ) {
|
||||
$content .= $review;
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schema data by shortcode/post ID.
|
||||
*
|
||||
* @param string $shortcode_id Schema shortcode ID.
|
||||
* @param string $post_id Post ID.
|
||||
* @return array
|
||||
*/
|
||||
private function get_schema_data( $shortcode_id, $post_id = false ) {
|
||||
if ( ! empty( $shortcode_id ) && is_string( $shortcode_id ) ) {
|
||||
return DB::get_schema_by_shortcode_id( $shortcode_id );
|
||||
}
|
||||
|
||||
if ( ! $post_id ) {
|
||||
$post_id = Param::get( 'post_id' ) ? Param::get( 'post_id' ) : get_the_ID();
|
||||
}
|
||||
|
||||
$data = DB::get_schemas( $post_id );
|
||||
return empty( $data ) ? false : [
|
||||
'post_id' => $post_id,
|
||||
'schema' => $data,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to replace variables used in Schema fields.
|
||||
*
|
||||
* @param array $schemas Schema to replace.
|
||||
* @param WP_Post $post Post schema attached to.
|
||||
* @return array
|
||||
*/
|
||||
private function replace_variables( $schemas, $post ) {
|
||||
if ( ! is_array( $schemas ) && ! is_object( $schemas ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$new_schemas = [];
|
||||
foreach ( $schemas as $key => $schema ) {
|
||||
if ( 'metadata' === $key ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( is_array( $schema ) ) {
|
||||
$new_schemas[ $key ] = $this->replace_variables( $schema, $post );
|
||||
continue;
|
||||
}
|
||||
|
||||
$new_schemas[ $key ] = Str::contains( '%', $schema ) ? Helper::replace_seo_fields( $schema, $post ) : $schema;
|
||||
}
|
||||
|
||||
return $new_schemas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we can inject the review in the content.
|
||||
*
|
||||
* @param array $schema Schema Data.
|
||||
*
|
||||
* @return boolean|string
|
||||
*/
|
||||
private function get_content_location( $schema ) {
|
||||
$location = ! empty( $schema['metadata']['shortcode'] ) && isset( $schema['metadata']['reviewLocation'] ) ? $schema['metadata']['reviewLocation'] : false;
|
||||
return $this->do_filter( 'snippet/review/location', $location );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schema data to show in the content.
|
||||
*
|
||||
* @return boolean|array
|
||||
*
|
||||
* @since 1.0.59
|
||||
*/
|
||||
private function get_schemas() {
|
||||
/**
|
||||
* Filter: Allow disabling the review display.
|
||||
*
|
||||
* @param bool $return True to disable.
|
||||
*/
|
||||
if ( ! is_main_query() || ! in_the_loop() || $this->do_filter( 'snippet/review/hide_data', false ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$schemas = $this->get_schema_data( false );
|
||||
if ( empty( $schemas ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return array_filter(
|
||||
$schemas['schema'],
|
||||
function( $schema ) {
|
||||
return ! empty( $schema['metadata']['reviewLocation'] );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we can add content if multipage.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function can_add_multi_page() {
|
||||
global $multipage, $numpages, $page;
|
||||
|
||||
return ( ! $multipage || $page === $numpages );
|
||||
}
|
||||
}
|
@@ -0,0 +1 @@
|
||||
<?php // Silence is golden.
|
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* The Schema Interface
|
||||
*
|
||||
* @since 1.0.13
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Schema interface.
|
||||
*/
|
||||
interface Snippet {
|
||||
|
||||
/**
|
||||
* Process schema data
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param JsonLD $jsonld Instance of JsonLD.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function process( $data, $jsonld );
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* Shortcode - Book
|
||||
*
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
$this->get_title();
|
||||
$this->get_image();
|
||||
?>
|
||||
<div class="rank-math-review-data">
|
||||
|
||||
<?php $this->get_description(); ?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'URL', 'rank-math' ),
|
||||
'url'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Author', 'rank-math' ),
|
||||
'author.name'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
if ( ! empty( $schema['hasPart'] ) ) {
|
||||
$hash = [
|
||||
'edition' => __( 'Edition', 'rank-math' ),
|
||||
'name' => __( 'Name', 'rank-math' ),
|
||||
'url' => __( 'Url', 'rank-math' ),
|
||||
'author' => __( 'Author', 'rank-math' ),
|
||||
'isbn' => __( 'ISBN', 'rank-math' ),
|
||||
'datePublished' => __( 'Date Published', 'rank-math' ),
|
||||
'bookFormat' => __( 'Format', 'rank-math' ),
|
||||
];
|
||||
foreach ( $schema['hasPart'] as $edition ) {
|
||||
$this->schema = $edition;
|
||||
foreach ( $hash as $id => $label ) {
|
||||
$this->get_field( $label, $id );
|
||||
}
|
||||
}
|
||||
$this->schema = $schema;
|
||||
}
|
||||
?>
|
||||
|
||||
<?php $this->show_ratings(); ?>
|
||||
|
||||
</div>
|
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
/**
|
||||
* Shortcode - Course
|
||||
*
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
$this->get_title();
|
||||
$this->get_image();
|
||||
?>
|
||||
<div class="rank-math-review-data">
|
||||
|
||||
<?php $this->get_description(); ?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Course Provider', 'rank-math' ),
|
||||
'provider.@type'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Course Provider Name', 'rank-math' ),
|
||||
'provider.name'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Course Provider URL', 'rank-math' ),
|
||||
'provider.sameAs'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Course Mode', 'rank-math' ),
|
||||
'hasCourseInstance.courseMode'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Course Workload', 'rank-math' ),
|
||||
'hasCourseInstance.courseWorkload',
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Start Date', 'rank-math' ),
|
||||
'hasCourseInstance.courseSchedule.startDate',
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'End Date', 'rank-math' ),
|
||||
'hasCourseInstance.courseSchedule.endDate',
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Duration', 'rank-math' ),
|
||||
'hasCourseInstance.courseSchedule.duration',
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Repeat Count', 'rank-math' ),
|
||||
'hasCourseInstance.courseSchedule.repeatCount',
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Repeat Frequency', 'rank-math' ),
|
||||
'hasCourseInstance.courseSchedule.repeatFrequency',
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Course Type', 'rank-math' ),
|
||||
'offers.category'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Course Currency', 'rank-math' ),
|
||||
'offers.priceCurrency'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Course Price', 'rank-math' ),
|
||||
'offers.price'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php $this->show_ratings(); ?>
|
||||
|
||||
</div>
|
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
/**
|
||||
* Shortcode - Event
|
||||
*
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
$this->get_title();
|
||||
$this->get_image();
|
||||
|
||||
$value = $this->get_field_value( 'eventAttendanceMode' );
|
||||
$is_online = 'Online' === $value;
|
||||
$is_offline = 'Offline' === $value;
|
||||
|
||||
if ( 'MixedEventAttendanceMode' === $value ) {
|
||||
$is_online = true;
|
||||
$is_offline = true;
|
||||
$value = esc_html__( 'Online + Offline', 'rank-math' );
|
||||
}
|
||||
?>
|
||||
<div class="rank-math-review-data">
|
||||
|
||||
<?php $this->get_description(); ?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Event Type', 'rank-math' ),
|
||||
'@type'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->output_field(
|
||||
esc_html__( 'Event Attendance Mode', 'rank-math' ),
|
||||
$value
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Event Status', 'rank-math' ),
|
||||
'eventStatus'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
if ( $is_offline ) {
|
||||
$this->get_field(
|
||||
esc_html__( 'Venue Name', 'rank-math' ),
|
||||
'location.name'
|
||||
);
|
||||
|
||||
$this->get_field(
|
||||
esc_html__( 'Venue URL', 'rank-math' ),
|
||||
'location.url'
|
||||
);
|
||||
|
||||
$this->get_field(
|
||||
esc_html__( 'Address', 'rank-math' ),
|
||||
'location.address'
|
||||
);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
if ( $is_online ) {
|
||||
$this->get_field(
|
||||
esc_html__( 'Online Event URL', 'rank-math' ),
|
||||
'VirtualLocation.url'
|
||||
);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Performer', 'rank-math' ),
|
||||
'performer.@type'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Performer Name', 'rank-math' ),
|
||||
'performer.name'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Performer URL', 'rank-math' ),
|
||||
'performer.sameAs'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Start Date', 'rank-math' ),
|
||||
'startDate',
|
||||
true
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'End Date', 'rank-math' ),
|
||||
'endDate',
|
||||
true
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Ticket URL', 'rank-math' ),
|
||||
'offers.url'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Entry Price', 'rank-math' ),
|
||||
'offers.price'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Currency', 'rank-math' ),
|
||||
'offers.priceCurrency'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Availability', 'rank-math' ),
|
||||
'offers.availability'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Availability Starts', 'rank-math' ),
|
||||
'startDate'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Stock Inventory', 'rank-math' ),
|
||||
'offers.inventoryLevel'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php $this->show_ratings(); ?>
|
||||
|
||||
</div>
|
@@ -0,0 +1 @@
|
||||
<?php // Silence is golden.
|
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/**
|
||||
* Shortcode - Job Posting
|
||||
*
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
$this->get_title();
|
||||
$this->get_image();
|
||||
?>
|
||||
<div class="rank-math-review-data">
|
||||
|
||||
<?php $this->get_description(); ?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Salary', 'rank-math' ),
|
||||
'baseSalary.value.value'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Salary Currency', 'rank-math' ),
|
||||
'baseSalary.currency'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Payroll', 'rank-math' ),
|
||||
'baseSalary.value.unitText'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Date Posted', 'rank-math' ),
|
||||
'datePosted'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Expiry Posted', 'rank-math' ),
|
||||
'validThrough'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Unpublish when expired', 'rank-math' ),
|
||||
'unpublish'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Employment Type ', 'rank-math' ),
|
||||
'employmentType'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Hiring Organization ', 'rank-math' ),
|
||||
'hiringOrganization.name'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Organization URL', 'rank-math' ),
|
||||
'hiringOrganization.sameAs'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Organization Logo', 'rank-math' ),
|
||||
'hiringOrganization.logo'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Location', 'rank-math' ),
|
||||
'jobLocation.address'
|
||||
);
|
||||
?>
|
||||
|
||||
</div>
|
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* Shortcode - Music
|
||||
*
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
$this->get_title();
|
||||
$this->get_image();
|
||||
?>
|
||||
<div class="rank-math-review-data">
|
||||
|
||||
<?php $this->get_description(); ?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'URL', 'rank-math' ),
|
||||
'url'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Type', 'rank-math' ),
|
||||
'@type'
|
||||
);
|
||||
?>
|
||||
|
||||
</div>
|
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* Shortcode - Person
|
||||
*
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
$this->get_title();
|
||||
$this->get_image();
|
||||
?>
|
||||
<div class="rank-math-review-data">
|
||||
|
||||
<?php $this->get_description(); ?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Email', 'rank-math' ),
|
||||
'email'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Address', 'rank-math' ),
|
||||
'address'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Gender', 'rank-math' ),
|
||||
'gender'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Job Title', 'rank-math' ),
|
||||
'jobTitle'
|
||||
);
|
||||
?>
|
||||
|
||||
</div>
|
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* Shortcode - Product
|
||||
*
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
$this->get_title();
|
||||
$this->get_image();
|
||||
?>
|
||||
<div class="rank-math-review-data">
|
||||
|
||||
<?php $this->get_description(); ?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Product SKU', 'rank-math' ),
|
||||
'sku'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Product Brand', 'rank-math' ),
|
||||
'brand.name'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Product Currency', 'rank-math' ),
|
||||
'offers.priceCurrency'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Product Price', 'rank-math' ),
|
||||
'offers.price'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Price Valid Until', 'rank-math' ),
|
||||
'offers.priceValidUntil'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Product In-Stock', 'rank-math' ),
|
||||
'offers.availability'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php $this->show_ratings(); ?>
|
||||
|
||||
</div>
|
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
/**
|
||||
* Shortcode - Recipe
|
||||
*
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
$this->get_title();
|
||||
$this->get_image();
|
||||
?>
|
||||
<div class="rank-math-review-data">
|
||||
|
||||
<?php $this->get_description(); ?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Type', 'rank-math' ),
|
||||
'recipeCategory'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Cuisine', 'rank-math' ),
|
||||
'recipeCuisine'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Keywords', 'rank-math' ),
|
||||
'keywords'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Recipe Yield', 'rank-math' ),
|
||||
'recipeYield'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Calories', 'rank-math' ),
|
||||
'nutrition.calories'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Preparation Time', 'rank-math' ),
|
||||
'prepTime'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Cooking Time', 'rank-math' ),
|
||||
'cookTime'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Total Time', 'rank-math' ),
|
||||
'totalTime'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Recipe Video Name', 'rank-math' ),
|
||||
'video.name'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Recipe Video Description', 'rank-math' ),
|
||||
'video.description'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Recipe Video Thumbnail', 'rank-math' ),
|
||||
'video.thumbnailUrl'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
global $wp_embed;
|
||||
if ( ! empty( $this->schema['video'] ) ) {
|
||||
if ( ! empty( $this->schema['video']['embedUrl'] ) ) {
|
||||
echo do_shortcode( $wp_embed->autoembed( $this->schema['video']['embedUrl'] ) );
|
||||
} elseif ( ! empty( $this->schema['video']['contentUrl'] ) ) {
|
||||
echo do_shortcode( $wp_embed->autoembed( $this->schema['video']['contentUrl'] ) );
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
$ingredient = $this->get_field_value( 'recipeIngredient' );
|
||||
$this->output_field(
|
||||
esc_html__( 'Recipe Ingredients', 'rank-math' ),
|
||||
'<ul><li>' . join( '</li><li>', $ingredient ) . '</li></ul>'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$instructions = $this->get_field_value( 'recipeInstructions' );
|
||||
if ( is_string( $instructions ) ) {
|
||||
$this->get_field(
|
||||
esc_html__( 'Recipe Instructions', 'rank-math' ),
|
||||
'recipeInstructions'
|
||||
);
|
||||
} else {
|
||||
// HowTo Array.
|
||||
if ( isset( $instructions[0]['@type'] ) && 'HowtoStep' === $instructions[0]['@type'] ) {
|
||||
$instructions = wp_list_pluck( $instructions, 'text' );
|
||||
$this->output_field(
|
||||
esc_html__( 'Recipe Instructions', 'rank-math' ),
|
||||
'<ul><li>' . join( '</li><li>', $instructions ) . '</li></ul>'
|
||||
);
|
||||
}
|
||||
|
||||
// Single HowToSection data.
|
||||
if ( ! empty( $instructions['itemListElement'] ) ) {
|
||||
$this->output_field(
|
||||
esc_html__( 'Recipe Instructions', 'rank-math' ),
|
||||
''
|
||||
);
|
||||
|
||||
$this->output_field(
|
||||
$instructions['name'],
|
||||
'<ul><li>' . join( '</li><li>', wp_list_pluck( $instructions['itemListElement'], 'text' ) ) . '</li></ul>'
|
||||
);
|
||||
}
|
||||
|
||||
// Multiple HowToSection data.
|
||||
if ( isset( $instructions[0]['@type'] ) && 'HowToSection' === $instructions[0]['@type'] ) {
|
||||
$this->output_field(
|
||||
esc_html__( 'Recipe Instructions', 'rank-math' ),
|
||||
''
|
||||
);
|
||||
|
||||
foreach ( $instructions as $section ) {
|
||||
if ( empty( $section['itemListElement'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->output_field(
|
||||
$section['name'],
|
||||
'<ul><li>' . join( '</li><li>', wp_list_pluck( $section['itemListElement'], 'text' ) ) . '</li></ul>'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<?php $this->show_ratings(); ?>
|
||||
|
||||
</div>
|
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* Shortcode - Restaurant
|
||||
*
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
$this->get_title();
|
||||
$this->get_image();
|
||||
?>
|
||||
<div class="rank-math-review-data">
|
||||
|
||||
<?php $this->get_description(); ?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Address', 'rank-math' ),
|
||||
'address'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Geo Coordinates', 'rank-math' ),
|
||||
'geo'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Phone Number', 'rank-math' ),
|
||||
'telephone'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Price Range', 'rank-math' ),
|
||||
'priceRange'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php $this->get_opening_hours( 'openingHoursSpecification' ); ?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Serves Cuisine', 'rank-math' ),
|
||||
'servesCuisine'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Menu URL', 'rank-math' ),
|
||||
'hasMenu'
|
||||
);
|
||||
?>
|
||||
|
||||
</div>
|
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* Shortcode - Service
|
||||
*
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
$this->get_title();
|
||||
$this->get_image();
|
||||
?>
|
||||
<div class="rank-math-review-data">
|
||||
|
||||
<?php $this->get_description(); ?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Service Type', 'rank-math' ),
|
||||
'serviceType'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Price', 'rank-math' ),
|
||||
'offers.price'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Currency', 'rank-math' ),
|
||||
'offers.priceCurrency'
|
||||
);
|
||||
?>
|
||||
|
||||
</div>
|
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* Shortcode - Software Application
|
||||
*
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
$this->get_title();
|
||||
$this->get_image();
|
||||
?>
|
||||
<div class="rank-math-review-data">
|
||||
|
||||
<?php $this->get_description(); ?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Price', 'rank-math' ),
|
||||
'offers.price'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Price Currency', 'rank-math' ),
|
||||
'offers.priceCurrency'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Operating System', 'rank-math' ),
|
||||
'operatingSystem'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php
|
||||
$this->get_field(
|
||||
esc_html__( 'Application Category', 'rank-math' ),
|
||||
'applicationCategory'
|
||||
);
|
||||
?>
|
||||
|
||||
<?php $this->show_ratings(); ?>
|
||||
|
||||
</div>
|
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* Shortcode - Video
|
||||
*
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
global $wp_embed;
|
||||
$this->get_title();
|
||||
?>
|
||||
<div class="rank-math-review-data">
|
||||
|
||||
<?php $this->get_description(); ?>
|
||||
|
||||
<?php
|
||||
if ( ! empty( $this->schema['embedUrl'] ) ) {
|
||||
echo do_shortcode( $wp_embed->autoembed( $this->schema['embedUrl'] ) );
|
||||
} elseif ( ! empty( $this->schema['contentUrl'] ) ) {
|
||||
echo do_shortcode( $wp_embed->autoembed( $this->schema['contentUrl'] ) );
|
||||
}
|
||||
?>
|
||||
|
||||
</div>
|
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* The Article Class.
|
||||
*
|
||||
* @since 1.0.13
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Traits\Hooker;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Article class.
|
||||
*/
|
||||
class Article implements Snippet {
|
||||
|
||||
use Hooker;
|
||||
|
||||
/**
|
||||
* Article rich snippet.
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param JsonLD $jsonld JsonLD Instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function process( $data, $jsonld ) {
|
||||
$entity = [
|
||||
'@type' => Helper::get_default_schema_type( $jsonld->post->ID ),
|
||||
'headline' => $jsonld->parts['title'],
|
||||
'keywords' => Helper::replace_vars( '%keywords%', $jsonld->post ),
|
||||
'datePublished' => $jsonld->parts['published'],
|
||||
'dateModified' => $jsonld->parts['modified'],
|
||||
'isPrimary' => true,
|
||||
'articleSection' => Helper::replace_vars( '%primary_taxonomy_terms%', $jsonld->post ),
|
||||
'author' => ! empty( $data['ProfilePage'] ) ?
|
||||
[
|
||||
'@id' => $data['ProfilePage']['@id'],
|
||||
'name' => $jsonld->parts['author'],
|
||||
] :
|
||||
[
|
||||
'@type' => 'Person',
|
||||
'name' => $jsonld->parts['author'],
|
||||
],
|
||||
];
|
||||
|
||||
$jsonld->add_prop( 'publisher', $entity, 'publisher', $data );
|
||||
if ( ! empty( $jsonld->parts['desc'] ) ) {
|
||||
$entity['description'] = $jsonld->parts['desc'];
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
/**
|
||||
* The Author Class.
|
||||
*
|
||||
* @since 1.0.13
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\User;
|
||||
use RankMath\Paper\Paper;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Author class.
|
||||
*/
|
||||
class Author implements Snippet {
|
||||
|
||||
/**
|
||||
* Add Author entity in JSON-LD data.
|
||||
*
|
||||
* @link https://schema.org/Person
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param JsonLD $jsonld JsonLD Instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function process( $data, $jsonld ) {
|
||||
$is_archive_disabled = Helper::get_settings( 'titles.disable_author_archives' );
|
||||
$author_id = is_singular() ? $jsonld->post->post_author : get_the_author_meta( 'ID' );
|
||||
$author_name = get_the_author();
|
||||
$author_url = get_author_posts_url( $author_id );
|
||||
$data['ProfilePage'] = [
|
||||
'@type' => 'Person',
|
||||
'@id' => ! $is_archive_disabled ? $author_url : $jsonld->parts['url'] . '#author',
|
||||
'name' => $author_name,
|
||||
'description' => wp_strip_all_tags( stripslashes( $this->get_description( $author_id ) ), true ),
|
||||
'url' => $is_archive_disabled ? '' : $author_url,
|
||||
];
|
||||
|
||||
$this->add_image( $data['ProfilePage'], $author_id, $jsonld );
|
||||
$this->add_same_as( $data['ProfilePage'], $author_id );
|
||||
$this->add_works_for( $data['ProfilePage'], $data );
|
||||
|
||||
if ( is_author() && ! empty( $data['WebPage'] ) ) {
|
||||
$data['ProfilePage']['mainEntityOfPage'] = [
|
||||
'@id' => $data['WebPage']['@id'],
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add sameAs property to the Person entity.
|
||||
*
|
||||
* @param array $entity Author schema data.
|
||||
* @param int $author_id Author ID.
|
||||
*/
|
||||
private function add_same_as( &$entity, $author_id ) {
|
||||
$same_as = [
|
||||
get_the_author_meta( 'user_url', $author_id ),
|
||||
get_user_meta( $author_id, 'facebook', true ),
|
||||
];
|
||||
|
||||
if ( $twitter = get_user_meta( $author_id, 'twitter', true ) ) { // phpcs:ignore
|
||||
$same_as[] = 'https://twitter.com/' . $twitter;
|
||||
}
|
||||
|
||||
$addional_urls = get_user_meta( $author_id, 'additional_profile_urls', true );
|
||||
if ( $addional_urls ) {
|
||||
$same_as = array_merge( $same_as, explode( ' ', $addional_urls ) );
|
||||
}
|
||||
|
||||
$same_as = array_filter( $same_as );
|
||||
if ( empty( $same_as ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entity['sameAs'] = array_values( $same_as );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add image property to the Person entity.
|
||||
*
|
||||
* @param array $entity Author schema data.
|
||||
* @param int $author_id Author ID.
|
||||
* @param JsonLD $jsonld JsonLD Instance.
|
||||
*/
|
||||
private function add_image( &$entity, $author_id, $jsonld ) {
|
||||
$entity['image'] = [
|
||||
'@type' => 'ImageObject',
|
||||
'@id' => get_avatar_url( $author_id ),
|
||||
'url' => get_avatar_url( $author_id ),
|
||||
'caption' => get_the_author(),
|
||||
];
|
||||
|
||||
$jsonld->add_prop( 'language', $entity['image'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add worksFor property referencing it to the publisher entity.
|
||||
*
|
||||
* @param array $entity Author schema data.
|
||||
* @param array $data Schema Data.
|
||||
*/
|
||||
private function add_works_for( &$entity, $data ) {
|
||||
if (
|
||||
empty( $data['publisher'] ) ||
|
||||
in_array( 'Person', (array) $data['publisher']['@type'], true )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entity['worksFor'] = [ '@id' => $data['publisher']['@id'] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get author description.
|
||||
*
|
||||
* @param int $author_id Author ID.
|
||||
*/
|
||||
private function get_description( $author_id ) {
|
||||
$description = User::get_meta( 'description', $author_id );
|
||||
return $description ? $description : Paper::get_from_options( 'author_archive_description' );
|
||||
}
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/**
|
||||
* The Breadcrumbs Class.
|
||||
*
|
||||
* @since 1.0.13
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Frontend\Breadcrumbs as BreadcrumbTrail;
|
||||
use RankMath\Paper\Paper;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Breadcrumbs class.
|
||||
*/
|
||||
class Breadcrumbs implements Snippet {
|
||||
|
||||
/**
|
||||
* Generate breadcrumbs JSON-LD.
|
||||
*
|
||||
* @link https://schema.org/BreadcrumbList
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param JsonLD $jsonld JsonLD Instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function process( $data, $jsonld ) {
|
||||
$crumbs = BreadcrumbTrail::get() ? BreadcrumbTrail::get()->get_crumbs() : false;
|
||||
if ( empty( $crumbs ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$entity = [
|
||||
'@type' => 'BreadcrumbList',
|
||||
'@id' => Paper::get()->get_canonical() . '#breadcrumb',
|
||||
'itemListElement' => [],
|
||||
];
|
||||
|
||||
$position = 1;
|
||||
foreach ( $crumbs as $crumb ) {
|
||||
if ( ! empty( $crumb['hide_in_schema'] ) || empty( $crumb[1] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$entity['itemListElement'][] = [
|
||||
'@type' => 'ListItem',
|
||||
'position' => $position,
|
||||
'item' => [
|
||||
'@id' => $crumb[1],
|
||||
'name' => $crumb[0],
|
||||
],
|
||||
];
|
||||
|
||||
$position++;
|
||||
}
|
||||
|
||||
$entity = apply_filters( 'rank_math/snippet/breadcrumb', $entity );
|
||||
if ( empty( $entity['itemListElement'] ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$data['BreadcrumbList'] = $entity;
|
||||
return $data;
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* The Primary Image Class.
|
||||
*
|
||||
* @since 1.0.43
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* PrimaryImage class.
|
||||
*/
|
||||
class PrimaryImage implements Snippet {
|
||||
|
||||
/**
|
||||
* Add primaryImage entity in JSON-LD data.
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param JsonLD $jsonld JsonLD Instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function process( $data, $jsonld ) {
|
||||
$image = Helper::get_thumbnail_with_fallback( get_the_ID(), 'full' );
|
||||
if ( empty( $image ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$data['primaryImage'] = [
|
||||
'@type' => 'ImageObject',
|
||||
'@id' => $image[0],
|
||||
'url' => $image[0],
|
||||
'width' => $image[1],
|
||||
'height' => $image[2],
|
||||
'caption' => isset( $image['caption'] ) ? $image['caption'] : '',
|
||||
];
|
||||
|
||||
$jsonld->add_prop( 'language', $data['primaryImage'] );
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/**
|
||||
* The Easy Digital Downloads Product Class.
|
||||
*
|
||||
* @since 1.0.13
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use EDD_Download;
|
||||
use RankMath\Helper;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Product_Edd class.
|
||||
*/
|
||||
class Product_Edd {
|
||||
|
||||
/**
|
||||
* Set product data for rich snippet.
|
||||
*
|
||||
* @param array $entity Array of JSON-LD entity.
|
||||
* @param JsonLD $jsonld JsonLD Instance.
|
||||
*/
|
||||
public function set_product( &$entity, $jsonld ) {
|
||||
$product_id = get_the_ID();
|
||||
$permalink = get_permalink();
|
||||
$product = new EDD_Download( $product_id );
|
||||
|
||||
$entity['url'] = $permalink;
|
||||
$entity['name'] = $jsonld->post->post_title;
|
||||
$entity['description'] = Helper::replace_vars( '%seo_description%' );
|
||||
$entity['category'] = Product::get_category( $product_id, 'download_category' );
|
||||
$entity['mainEntityOfPage'] = [ '@id' => $jsonld->parts['canonical'] . '#webpage' ];
|
||||
|
||||
// SKU.
|
||||
if ( $product->get_sku() ) {
|
||||
$entity['sku'] = $product->get_sku();
|
||||
}
|
||||
|
||||
// Offers.
|
||||
$seller = Product::get_seller( $jsonld );
|
||||
$variations = $this->has_variations( $product );
|
||||
if ( false !== $variations ) {
|
||||
$entity['offers'] = [];
|
||||
foreach ( $variations as $variation ) {
|
||||
$offer = [
|
||||
'@type' => 'Offer',
|
||||
'description' => $variation['name'],
|
||||
'price' => $variation['amount'],
|
||||
'priceCurrency' => edd_get_currency(),
|
||||
'priceValidUntil' => date( 'Y-12-31', time() + YEAR_IN_SECONDS ),
|
||||
'url' => $permalink,
|
||||
'seller' => $seller,
|
||||
];
|
||||
|
||||
// Set Price Specification.
|
||||
$this->set_price_specification( $variation['amount'], $offer );
|
||||
$entity['offers'][] = $offer;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Single offer.
|
||||
$entity['offers'] = [
|
||||
'@type' => 'Offer',
|
||||
'price' => $product->get_price() ? $product->get_price() : '0',
|
||||
'priceCurrency' => edd_get_currency(),
|
||||
'priceValidUntil' => date( 'Y-12-31', time() + YEAR_IN_SECONDS ),
|
||||
'seller' => $seller,
|
||||
'url' => $permalink,
|
||||
];
|
||||
|
||||
// Set Price Specification.
|
||||
$this->set_price_specification( $product->get_price(), $entity['offers'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set price specification.
|
||||
*
|
||||
* @param object $price Product price.
|
||||
* @param array $entity Array of offer entity.
|
||||
*/
|
||||
private function set_price_specification( $price, &$entity ) {
|
||||
if ( ! edd_use_taxes() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entity['priceSpecification'] = [
|
||||
'price' => $price ? $price : '0',
|
||||
'priceCurrency' => edd_get_currency(),
|
||||
'valueAddedTaxIncluded' => edd_prices_include_tax() ? 'true' : 'false',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* If product is variable, set variations.
|
||||
*
|
||||
* @param EDD_Download $product Current product.
|
||||
*
|
||||
* @return array|boolean
|
||||
*/
|
||||
private function has_variations( $product ) {
|
||||
if ( ! $product->has_variable_prices() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$variations = $product->get_prices();
|
||||
return ! empty( $variations ) ? $variations : false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,373 @@
|
||||
<?php
|
||||
/**
|
||||
* The WooCommerce Product Class.
|
||||
*
|
||||
* @since 1.0.13
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Product_WooCommerce class.
|
||||
*/
|
||||
class Product_WooCommerce {
|
||||
|
||||
/**
|
||||
* Attribute assigner.
|
||||
*
|
||||
* @var WC_Attributes
|
||||
*/
|
||||
private $attributes;
|
||||
|
||||
/**
|
||||
* Set product data for rich snippet.
|
||||
*
|
||||
* @param array $entity Array of JSON-LD entity.
|
||||
* @param JsonLD $jsonld JsonLD Instance.
|
||||
*/
|
||||
public function set_product( &$entity, $jsonld ) {
|
||||
$product = wc_get_product( get_the_ID() );
|
||||
$this->attributes = new WC_Attributes( $product );
|
||||
|
||||
if ( Helper::is_module_active( 'woocommerce' ) ) {
|
||||
$brand = \RankMath\WooCommerce\Woocommerce::get_brands( $product->get_id() );
|
||||
|
||||
// Brand.
|
||||
if ( ! empty( $brand ) ) {
|
||||
$entity['brand'] = [
|
||||
'@type' => 'Brand',
|
||||
'name' => $brand,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$entity['name'] = $jsonld->get_product_title( $product );
|
||||
$entity['description'] = $jsonld->get_product_desc( $product );
|
||||
$entity['sku'] = $product->get_sku() ? $product->get_sku() : '';
|
||||
$entity['category'] = Product::get_category( $product->get_id(), 'product_cat' );
|
||||
$entity['mainEntityOfPage'] = [ '@id' => $jsonld->parts['canonical'] . '#webpage' ];
|
||||
|
||||
$this->set_weight( $product, $entity );
|
||||
$this->set_dimensions( $product, $entity );
|
||||
$this->set_images( $product, $entity );
|
||||
$this->set_ratings( $product, $entity );
|
||||
$this->set_offers( $product, $entity, Product::get_seller( $jsonld ) );
|
||||
|
||||
// GTIN numbers need product attributes.
|
||||
$this->attributes->assign_property( $entity, 'gtin8' );
|
||||
$this->attributes->assign_property( $entity, 'gtin12' );
|
||||
$this->attributes->assign_property( $entity, 'gtin13' );
|
||||
$this->attributes->assign_property( $entity, 'gtin14' );
|
||||
|
||||
// Color.
|
||||
$this->attributes->assign_property( $entity, 'color' );
|
||||
|
||||
// Remaining Attributes.
|
||||
$this->attributes->assign_remaining( $entity );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set product weight.
|
||||
*
|
||||
* @param object $product Product instance.
|
||||
* @param array $entity Array of JSON-LD entity.
|
||||
*/
|
||||
private function set_weight( $product, &$entity ) {
|
||||
if ( ! $product->has_weight() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hash = [
|
||||
'lbs' => 'LBR',
|
||||
'kg' => 'KGM',
|
||||
'g' => 'GRM',
|
||||
'oz' => 'ONZ',
|
||||
];
|
||||
$unit = get_option( 'woocommerce_weight_unit' );
|
||||
|
||||
$entity['weight'] = [
|
||||
'@type' => 'QuantitativeValue',
|
||||
'unitCode' => isset( $hash[ $unit ] ) ? $hash[ $unit ] : 'LBR',
|
||||
'value' => $product->get_weight(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set product dimension.
|
||||
*
|
||||
* @param object $product Product instance.
|
||||
* @param array $entity Array of JSON-LD entity.
|
||||
*/
|
||||
private function set_dimensions( $product, &$entity ) {
|
||||
if ( ! $product->has_dimensions() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hash = [
|
||||
'in' => 'INH',
|
||||
'm' => 'MTR',
|
||||
'cm' => 'CMT',
|
||||
'mm' => 'MMT',
|
||||
'yd' => 'YRD',
|
||||
];
|
||||
$unit = get_option( 'woocommerce_dimension_unit' );
|
||||
$code = isset( $hash[ $unit ] ) ? $hash[ $unit ] : '';
|
||||
|
||||
$entity['height'] = [
|
||||
'@type' => 'QuantitativeValue',
|
||||
'unitCode' => $code,
|
||||
'value' => $product->get_height(),
|
||||
];
|
||||
|
||||
$entity['width'] = [
|
||||
'@type' => 'QuantitativeValue',
|
||||
'unitCode' => $code,
|
||||
'value' => $product->get_width(),
|
||||
];
|
||||
|
||||
$entity['depth'] = [
|
||||
'@type' => 'QuantitativeValue',
|
||||
'unitCode' => $code,
|
||||
'value' => $product->get_length(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set product images.
|
||||
*
|
||||
* @param object $product Product instance.
|
||||
* @param array $entity Array of JSON-LD entity.
|
||||
*/
|
||||
private function set_images( $product, &$entity ) {
|
||||
if ( ! $product->get_image_id() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$image = wp_get_attachment_image_src( $product->get_image_id(), 'single-post-thumbnail' );
|
||||
if ( ! empty( $image ) ) {
|
||||
$entity['image'][] = [
|
||||
'@type' => 'ImageObject',
|
||||
'url' => $image[0],
|
||||
'height' => $image[2],
|
||||
'width' => $image[1],
|
||||
];
|
||||
}
|
||||
|
||||
$gallery = $product->get_gallery_image_ids();
|
||||
foreach ( $gallery as $image_id ) {
|
||||
$image = wp_get_attachment_image_src( $image_id, 'single-post-thumbnail' );
|
||||
if ( empty( $image ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$entity['image'][] = [
|
||||
'@type' => 'ImageObject',
|
||||
'url' => $image[0],
|
||||
'height' => $image[2],
|
||||
'width' => $image[1],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set product ratings.
|
||||
*
|
||||
* @param object $product Product instance.
|
||||
* @param array $entity Array of JSON-LD entity.
|
||||
*/
|
||||
private function set_ratings( $product, &$entity ) {
|
||||
if ( $product->get_rating_count() < 1 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Aggregate Rating.
|
||||
$entity['aggregateRating'] = [
|
||||
'@type' => 'AggregateRating',
|
||||
'ratingValue' => $product->get_average_rating(),
|
||||
'bestRating' => '5',
|
||||
'ratingCount' => $product->get_rating_count(),
|
||||
'reviewCount' => $product->get_review_count(),
|
||||
];
|
||||
|
||||
// Reviews.
|
||||
$comments = get_comments(
|
||||
[
|
||||
'post_type' => 'product',
|
||||
'post_id' => get_the_ID(),
|
||||
'status' => 'approve',
|
||||
'parent' => 0,
|
||||
]
|
||||
);
|
||||
$permalink = $product->get_permalink();
|
||||
|
||||
foreach ( $comments as $comment ) {
|
||||
$rating = intval( get_comment_meta( $comment->comment_ID, 'rating', true ) );
|
||||
if ( ! $rating ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$entity['review'][] = [
|
||||
'@type' => 'Review',
|
||||
'@id' => $permalink . '#li-comment-' . $comment->comment_ID,
|
||||
'description' => $comment->comment_content,
|
||||
'datePublished' => $comment->comment_date,
|
||||
'reviewRating' => [
|
||||
'@type' => 'Rating',
|
||||
'ratingValue' => $rating,
|
||||
'bestRating' => '5',
|
||||
'worstRating' => '1',
|
||||
],
|
||||
'author' => [
|
||||
'@type' => 'Person',
|
||||
'name' => $comment->comment_author,
|
||||
'url' => $comment->comment_author_url,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set product offers.
|
||||
*
|
||||
* @param object $product Product instance.
|
||||
* @param array $entity Array of JSON-LD entity.
|
||||
* @param array $seller Seller info.
|
||||
*/
|
||||
private function set_offers( $product, &$entity, $seller ) {
|
||||
if ( '' === $product->get_price() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( true === $this->set_offers_variable( $product, $entity, $seller ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$offer = [
|
||||
'@type' => 'Offer',
|
||||
'price' => $product->get_price() ? wc_format_decimal( $product->get_price(), wc_get_price_decimals() ) : '0',
|
||||
'priceCurrency' => get_woocommerce_currency(),
|
||||
'priceValidUntil' => $product->is_on_sale() && ! empty( $product->get_date_on_sale_to() ) ? date_i18n( 'Y-m-d', strtotime( $product->get_date_on_sale_to() ) ) : date( 'Y-12-31', time() + YEAR_IN_SECONDS ),
|
||||
'availability' => $product->is_in_stock() ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock',
|
||||
'itemCondition' => 'NewCondition',
|
||||
'url' => $product->get_permalink(),
|
||||
'seller' => $seller,
|
||||
];
|
||||
|
||||
// Set Price Specification.
|
||||
$this->set_price_specification( $product->get_price(), $offer );
|
||||
|
||||
$this->attributes->assign_property( $offer, 'itemCondition' );
|
||||
$entity['offers'] = $offer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set product variable offers.
|
||||
*
|
||||
* @param object $product Product instance.
|
||||
* @param array $entity Array of JSON-LD entity.
|
||||
* @param array $seller Seller info.
|
||||
*/
|
||||
private function set_offers_variable( $product, &$entity, $seller ) {
|
||||
if ( ! $product->is_type( 'variable' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $this->set_single_variable_offer( $product, $entity, $seller ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$permalink = $product->get_permalink();
|
||||
$lowest = wc_format_decimal( $product->get_variation_price( 'min', false ), wc_get_price_decimals() );
|
||||
$highest = wc_format_decimal( $product->get_variation_price( 'max', false ), wc_get_price_decimals() );
|
||||
|
||||
if ( $lowest === $highest ) {
|
||||
$offer = [
|
||||
'@type' => 'Offer',
|
||||
'price' => $lowest,
|
||||
'priceValidUntil' => date( 'Y-12-31', time() + YEAR_IN_SECONDS ),
|
||||
];
|
||||
|
||||
// Set Price Specification.
|
||||
$this->set_price_specification( $lowest, $offer );
|
||||
} else {
|
||||
$offer = [
|
||||
'@type' => 'AggregateOffer',
|
||||
'lowPrice' => $lowest,
|
||||
'highPrice' => $highest,
|
||||
'offerCount' => count( $product->get_children() ),
|
||||
];
|
||||
}
|
||||
|
||||
$offer += [
|
||||
'priceCurrency' => get_woocommerce_currency(),
|
||||
'availability' => 'http://schema.org/' . ( $product->is_in_stock() ? 'InStock' : 'OutOfStock' ),
|
||||
'seller' => $seller,
|
||||
'url' => $permalink,
|
||||
];
|
||||
|
||||
$entity['offers'] = $offer;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Single Variable Product offer.
|
||||
*
|
||||
* Credit @leewillis77: https://github.com/leewillis77/wc-structured-data-option-4
|
||||
*
|
||||
* @param object $product Product instance.
|
||||
* @param array $entity Array of JSON-LD entity.
|
||||
* @param array $seller Seller info.
|
||||
*/
|
||||
private function set_single_variable_offer( $product, &$entity, $seller ) {
|
||||
$data_store = \WC_Data_Store::load( 'product' );
|
||||
$variation_id = $data_store->find_matching_product_variation( $product, wp_unslash( $_GET ) );
|
||||
$variation = $variation_id ? wc_get_product( $variation_id ) : false;
|
||||
if ( empty( $variation ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$price_valid_until = date( 'Y-12-31', current_time( 'timestamp', true ) + YEAR_IN_SECONDS );
|
||||
if ( $variation->is_on_sale() && $variation->get_date_on_sale_to() ) {
|
||||
$price_valid_until = date( 'Y-m-d', $variation->get_date_on_sale_to()->getTimestamp() );
|
||||
}
|
||||
|
||||
$entity['offers'] = [
|
||||
'@type' => 'Offer',
|
||||
'url' => $variation->get_permalink(),
|
||||
'sku' => $variation->get_sku(),
|
||||
'price' => wc_format_decimal( $variation->get_price(), wc_get_price_decimals() ),
|
||||
'priceCurrency' => get_woocommerce_currency(),
|
||||
'priceValidUntil' => $price_valid_until,
|
||||
'seller' => $seller,
|
||||
'availability' => 'http://schema.org/' . ( $variation->is_in_stock() ? 'InStock' : 'OutOfStock' ),
|
||||
];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set price specification.
|
||||
*
|
||||
* @param object $price Product price.
|
||||
* @param array $entity Array of offer entity.
|
||||
*/
|
||||
private function set_price_specification( $price, &$entity ) {
|
||||
if ( ! wc_tax_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entity['priceSpecification'] = [
|
||||
'price' => $price ? $price : '0',
|
||||
'priceCurrency' => get_woocommerce_currency(),
|
||||
'valueAddedTaxIncluded' => wc_prices_include_tax() ? 'true' : 'false',
|
||||
];
|
||||
}
|
||||
}
|
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
/**
|
||||
* The Product Class.
|
||||
*
|
||||
* @since 1.0.13
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Schema\Product_Edd;
|
||||
use RankMath\Schema\Product_WooCommerce;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Product class.
|
||||
*/
|
||||
class Product implements Snippet {
|
||||
|
||||
/**
|
||||
* Hold JsonLD Instance.
|
||||
*
|
||||
* @var JsonLD
|
||||
*/
|
||||
private $json = '';
|
||||
|
||||
/**
|
||||
* Product rich snippet.
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param JsonLD $jsonld JsonLD Instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function process( $data, $jsonld ) {
|
||||
$entity = [
|
||||
'@type' => 'Product',
|
||||
];
|
||||
if ( Helper::is_woocommerce_active() && is_product() ) {
|
||||
remove_action( 'wp_footer', [ WC()->structured_data, 'output_structured_data' ], 10 );
|
||||
remove_action( 'woocommerce_email_order_details', [ WC()->structured_data, 'output_email_structured_data' ], 30 );
|
||||
$product = new Product_WooCommerce();
|
||||
$product->set_product( $entity, $jsonld );
|
||||
}
|
||||
|
||||
if ( Helper::is_edd_active() && is_singular( 'download' ) ) {
|
||||
remove_filter( 'wp_footer', [ \EDD()->structured_data, 'output_structured_data' ] );
|
||||
remove_action( 'edd_purchase_link_top', 'edd_purchase_link_single_pricing_schema', 10 );
|
||||
remove_action( 'loop_start', 'edd_microdata_wrapper_open', 10 );
|
||||
$product = new Product_Edd();
|
||||
$product->set_product( $entity, $jsonld );
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get seller.
|
||||
*
|
||||
* @param JsonLD $jsonld JsonLD Instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_seller( $jsonld ) {
|
||||
$site_url = home_url();
|
||||
$type = Helper::get_settings( 'titles.knowledgegraph_type' );
|
||||
$seller = [
|
||||
'@type' => 'person' === $type ? 'Person' : 'Organization',
|
||||
'@id' => trailingslashit( $site_url ),
|
||||
'name' => $jsonld->get_website_name(),
|
||||
'url' => $site_url,
|
||||
];
|
||||
|
||||
if ( 'company' === $type ) {
|
||||
$seller['logo'] = Helper::get_settings( 'titles.knowledgegraph_logo' );
|
||||
}
|
||||
|
||||
return $seller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set product categories.
|
||||
*
|
||||
* @param int $product_id Product ID.
|
||||
* @param string $taxonomy Taxonomy.
|
||||
*/
|
||||
public static function get_category( $product_id, $taxonomy ) {
|
||||
$categories = get_the_terms( $product_id, $taxonomy );
|
||||
if ( is_wp_error( $categories ) || empty( $categories ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 0 === $categories[0]->parent ) {
|
||||
return $categories[0]->name;
|
||||
}
|
||||
|
||||
$ancestors = array_reverse( get_ancestors( $categories[0]->term_id, $taxonomy ) );
|
||||
foreach ( $ancestors as $parent ) {
|
||||
$term = get_term( $parent, $taxonomy );
|
||||
$category[] = $term->name;
|
||||
}
|
||||
$category[] = $categories[0]->name;
|
||||
|
||||
return join( ' > ', $category );
|
||||
}
|
||||
}
|
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* The Products Page Class.
|
||||
*
|
||||
* @since 1.0.13
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Products_Page class.
|
||||
*/
|
||||
class Products_Page implements Snippet {
|
||||
|
||||
/**
|
||||
* Sets the Schema structured data on the Products Archive page.
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param JsonLD $jsonld JsonLD Instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function process( $data, $jsonld ) {
|
||||
if ( ! $this->can_add_snippet_taxonomy() || ! $this->can_add_snippet_shop() ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$data['ProductsPage'] = [
|
||||
'@context' => 'https://schema.org/',
|
||||
'@graph' => [],
|
||||
];
|
||||
|
||||
while ( have_posts() ) {
|
||||
the_post();
|
||||
|
||||
$post_id = get_the_ID();
|
||||
$product = wc_get_product( $post_id );
|
||||
$url = $jsonld->get_post_url( $post_id );
|
||||
|
||||
$part = [
|
||||
'@type' => 'Product',
|
||||
'name' => $jsonld->get_product_title( $product ),
|
||||
'url' => $url,
|
||||
'@id' => $url,
|
||||
'description' => $jsonld->get_product_desc( $product ),
|
||||
];
|
||||
|
||||
$data['ProductsPage']['@graph'][] = $part;
|
||||
}
|
||||
|
||||
wp_reset_postdata();
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if structured data can be added on the current Product taxonomy.
|
||||
*
|
||||
* @return boolean|string
|
||||
*/
|
||||
private function can_add_snippet_taxonomy() {
|
||||
$queried_object = get_queried_object();
|
||||
|
||||
/**
|
||||
* Allow developer to remove snippet data.
|
||||
*
|
||||
* @param bool $unsigned Default: false
|
||||
* @param string $unsigned Taxonomy Name
|
||||
*/
|
||||
if (
|
||||
! is_shop() &&
|
||||
(
|
||||
true === Helper::get_settings( 'titles.remove_' . $queried_object->taxonomy . '_snippet_data' ) ||
|
||||
true === apply_filters( 'rank_math/snippet/remove_taxonomy_data', false, $queried_object->taxonomy )
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if structured data can be added on the Shop page.
|
||||
*
|
||||
* @return boolean|string
|
||||
*/
|
||||
private function can_add_snippet_shop() {
|
||||
/**
|
||||
* Allow developer to remove snippet data from Shop page.
|
||||
*
|
||||
* @param bool $unsigned Default: false
|
||||
*/
|
||||
if (
|
||||
is_shop() &&
|
||||
(
|
||||
true === Helper::get_settings( 'general.remove_shop_snippet_data' ) ||
|
||||
true === apply_filters( 'rank_math/snippet/remove_shop_data', false )
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* The Publisher Class.
|
||||
*
|
||||
* @since 1.0.43
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Publisher class.
|
||||
*/
|
||||
class Publisher implements Snippet {
|
||||
|
||||
/**
|
||||
* Generate Organization JSON-LD.
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param JsonLD $jsonld JsonLD Instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function process( $data, $jsonld ) {
|
||||
$type = Helper::get_settings( 'titles.knowledgegraph_type' );
|
||||
$id = 'company' === $type ? 'organization' : 'person';
|
||||
$data['publisher'] = [
|
||||
'@type' => $this->get_publisher_type( $type ),
|
||||
'@id' => home_url( "/#{$id}" ),
|
||||
'name' => $jsonld->get_organization_name(),
|
||||
];
|
||||
|
||||
$social_profiles = $jsonld->get_social_profiles();
|
||||
if ( ! empty( $social_profiles ) ) {
|
||||
$data['publisher']['sameAs'] = $social_profiles;
|
||||
}
|
||||
|
||||
$jsonld->add_prop( 'image', $data['publisher'] );
|
||||
if ( empty( $data['publisher']['logo'] ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( 'person' === $type ) {
|
||||
$data['publisher']['image'] = $data['publisher']['logo'];
|
||||
}
|
||||
|
||||
if ( ! is_singular() ) {
|
||||
unset( $data['publisher']['logo'] );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Publisher Type.
|
||||
*
|
||||
* @param string $type Knowledgegraph type.
|
||||
*
|
||||
* @return string|array
|
||||
*/
|
||||
private function get_publisher_type( $type ) {
|
||||
if ( 'company' === $type ) {
|
||||
return 'Organization';
|
||||
}
|
||||
|
||||
if ( ! is_singular() ) {
|
||||
return 'Person';
|
||||
}
|
||||
|
||||
return [ 'Person', 'Organization' ];
|
||||
}
|
||||
}
|
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
/**
|
||||
* The Singular Class.
|
||||
*
|
||||
* @since 1.0.13
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Traits\Hooker;
|
||||
use RankMath\Schema\DB;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Singular class.
|
||||
*/
|
||||
class Singular implements Snippet {
|
||||
|
||||
use Hooker;
|
||||
|
||||
/**
|
||||
* Generate rich snippet.
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param JsonLD $jsonld JsonLD Instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function process( $data, $jsonld ) {
|
||||
$schema = $this->can_add_schema( $jsonld );
|
||||
if ( false === $schema ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$hook = 'snippet/rich_snippet_' . $schema;
|
||||
/**
|
||||
* Short-circuit if 3rd party is interested generating his own data.
|
||||
*/
|
||||
$pre = $this->do_filter( $hook, false, $jsonld->parts, $data );
|
||||
if ( false !== $pre ) {
|
||||
$data['richSnippet'] = $this->do_filter( $hook . '_entity', $pre );
|
||||
return $data;
|
||||
}
|
||||
|
||||
$object = $this->get_schema_class( $schema );
|
||||
if ( false === $object ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$entity = $object->process( $data, $jsonld );
|
||||
|
||||
$data['richSnippet'] = $this->do_filter( $hook . '_entity', $entity );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Schema type.
|
||||
*
|
||||
* @param JsonLD $jsonld JsonLD Instance.
|
||||
*
|
||||
* @return boolean|string
|
||||
*/
|
||||
private function can_add_schema( $jsonld ) {
|
||||
if ( empty( $jsonld->post_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$schemas = DB::get_schemas( $jsonld->post_id );
|
||||
if ( ! empty( $schemas ) ) {
|
||||
$has_product = array_filter(
|
||||
$schemas,
|
||||
function( $schema ) {
|
||||
return ! empty( $schema['@type'] ) && in_array( $schema['@type'], [ 'WooCommerceProduct', 'EDDProduct' ], true );
|
||||
}
|
||||
);
|
||||
return ! empty( $has_product ) ? 'product' : false;
|
||||
}
|
||||
|
||||
if ( metadata_exists( 'post', $jsonld->post_id, 'rank_math_rich_snippet' ) ) {
|
||||
return Helper::get_post_meta( 'rich_snippet' );
|
||||
}
|
||||
|
||||
if ( ! Helper::can_use_default_schema( $jsonld->post_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->get_default_schema( $jsonld );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Default Rich Snippet type from Settings.
|
||||
*
|
||||
* @param JsonLD $jsonld JsonLD Instance.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_default_schema( $jsonld ) {
|
||||
$schema = Helper::get_default_schema_type( $jsonld->post_id, true );
|
||||
if ( ! $schema ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( in_array( $schema, [ 'BlogPosting', 'NewsArticle', 'Article' ], true ) ) {
|
||||
return 'article';
|
||||
}
|
||||
|
||||
if (
|
||||
( Helper::is_woocommerce_active() && is_singular( 'product' ) ) ||
|
||||
( Helper::is_edd_active() && is_singular( 'download' ) )
|
||||
) {
|
||||
return 'product';
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get appropriate Schema Class.
|
||||
*
|
||||
* @param string $schema Schema type.
|
||||
* @return bool|Class
|
||||
*/
|
||||
private function get_schema_class( $schema ) {
|
||||
$data = [
|
||||
'article' => '\\RankMath\\Schema\\Article',
|
||||
'product' => '\\RankMath\\Schema\\Product',
|
||||
];
|
||||
|
||||
if ( isset( $data[ $schema ] ) && class_exists( $data[ $schema ] ) ) {
|
||||
return new $data[ $schema ]();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* Helper class for handling WooComerce product attributes for rich snippets.
|
||||
*
|
||||
* @since 0.9.0
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* WC_Attributes class.
|
||||
*/
|
||||
class WC_Attributes {
|
||||
|
||||
/**
|
||||
* Hold product attributes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_attributes;
|
||||
|
||||
/**
|
||||
* Hold product object.
|
||||
*
|
||||
* @var WC_Product
|
||||
*/
|
||||
private $_product;
|
||||
|
||||
/**
|
||||
* The Constructor.
|
||||
*
|
||||
* @param WC_Product $product The Product.
|
||||
*/
|
||||
public function __construct( $product ) {
|
||||
$this->_product = $product;
|
||||
$this->_attributes = $product->get_attributes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find attribute for property.
|
||||
*
|
||||
* @param array $entity Entity to attach data to.
|
||||
* @param string $needle Assign this property.
|
||||
*/
|
||||
public function assign_property( &$entity, $needle ) {
|
||||
foreach ( $this->_attributes as $key => $attrib ) {
|
||||
if ( stristr( $key, $needle ) ) {
|
||||
$entity[ $needle ] = $this->_product->get_attribute( $key );
|
||||
unset( $this->_attributes[ $key ] );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map remaining attributes as PropertyValue.
|
||||
*
|
||||
* @param array $entity Entity to attach data to.
|
||||
*/
|
||||
public function assign_remaining( &$entity ) {
|
||||
foreach ( $this->_attributes as $key => $attrib ) {
|
||||
if ( $attrib['is_visible'] && ! $attrib['is_variation'] ) {
|
||||
$entity['additionalProperty'][] = [
|
||||
'@type' => 'PropertyValue',
|
||||
'name' => $key,
|
||||
'value' => $this->_product->get_attribute( $key ),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* The Webpage Class.
|
||||
*
|
||||
* @since 1.0.13
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Paper\Paper;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Webpage class.
|
||||
*/
|
||||
class Webpage implements Snippet {
|
||||
|
||||
/**
|
||||
* Generate WebPage JSON-LD.
|
||||
*
|
||||
* @link https://schema.org/WebPage
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param JsonLD $jsonld JsonLD Instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function process( $data, $jsonld ) {
|
||||
$entity = [
|
||||
'@type' => $this->get_type(),
|
||||
'@id' => Paper::get()->get_canonical() . '#webpage',
|
||||
'url' => Paper::get()->get_canonical(),
|
||||
'name' => Paper::get()->get_title(),
|
||||
];
|
||||
|
||||
if ( is_singular() ) {
|
||||
$entity['datePublished'] = $jsonld->parts['published'];
|
||||
$entity['dateModified'] = $jsonld->parts['modified'];
|
||||
}
|
||||
|
||||
if ( is_front_page() ) {
|
||||
$jsonld->add_prop( 'publisher', $entity, 'about', $data );
|
||||
}
|
||||
|
||||
$jsonld->add_prop( 'is_part_of', $entity, 'website' );
|
||||
$jsonld->add_prop( 'thumbnail', $entity, 'primaryImageOfPage', $data );
|
||||
$jsonld->add_prop( 'language', $entity );
|
||||
|
||||
if ( isset( $data['BreadcrumbList'] ) ) {
|
||||
$entity['breadcrumb'] = [ '@id' => $data['BreadcrumbList']['@id'] ];
|
||||
}
|
||||
|
||||
$data['WebPage'] = $entity;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get WebPage type depending on the current page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_type() {
|
||||
$about_page = Helper::get_settings( 'titles.local_seo_about_page' );
|
||||
$contact_page = Helper::get_settings( 'titles.local_seo_contact_page' );
|
||||
$hash = [
|
||||
'SearchResultsPage' => is_search(),
|
||||
'ProfilePage' => is_author(),
|
||||
'CollectionPage' => is_home() || is_archive(),
|
||||
'AboutPage' => $about_page && is_page( $about_page ),
|
||||
'ContactPage' => $contact_page && is_page( $contact_page ),
|
||||
];
|
||||
|
||||
return ! empty( array_filter( $hash ) ) ? key( array_filter( $hash ) ) : 'WebPage';
|
||||
}
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/**
|
||||
* The Website Class.
|
||||
*
|
||||
* @since 1.0.13
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Website class.
|
||||
*/
|
||||
class Website implements Snippet {
|
||||
|
||||
/**
|
||||
* Generate WebSite JSON-LD.
|
||||
*
|
||||
* @link https://schema.org/WebSite
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param JsonLD $jsonld JsonLD Instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function process( $data, $jsonld ) {
|
||||
$data['WebSite'] = [
|
||||
'@type' => 'WebSite',
|
||||
'@id' => home_url( '/#website' ),
|
||||
'url' => get_home_url(),
|
||||
'name' => $jsonld->get_website_name(),
|
||||
];
|
||||
|
||||
$alternate_name = Helper::get_settings( 'titles.website_alternate_name' );
|
||||
if ( $alternate_name ) {
|
||||
$data['WebSite']['alternateName'] = $alternate_name;
|
||||
}
|
||||
$jsonld->add_prop( 'publisher', $data['WebSite'], 'publisher', $data );
|
||||
$jsonld->add_prop( 'language', $data['WebSite'] );
|
||||
|
||||
/**
|
||||
* Disable the JSON-LD output for the Sitelinks Searchbox.
|
||||
*
|
||||
* @param boolean Display or not the JSON-LD for the Sitelinks Searchbox.
|
||||
*/
|
||||
if ( apply_filters( 'rank_math/json_ld/disable_search', ! is_front_page() || is_paged() ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the search URL in the JSON-LD.
|
||||
*
|
||||
* @param string $search_url The search URL with `{search_term_string}` placeholder.
|
||||
*/
|
||||
$search_url = apply_filters( 'rank_math/json_ld/search_url', home_url( '/?s={search_term_string}' ) );
|
||||
|
||||
$data['WebSite']['potentialAction'] = [
|
||||
'@type' => 'SearchAction',
|
||||
'target' => $search_url,
|
||||
'query-input' => 'required name=search_term_string',
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* Metabox - Schema Tab
|
||||
*
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
*/
|
||||
|
||||
use RankMath\Helper;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
if ( ! Helper::has_cap( 'onpage_snippet' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cmb->add_field(
|
||||
[
|
||||
'id' => 'rank_math_schema_generator',
|
||||
'type' => 'raw',
|
||||
'content' => '<div id="rank-math-schema-generator"></div>',
|
||||
'save_field' => false,
|
||||
]
|
||||
);
|
||||
|
||||
$cmb->add_field(
|
||||
[
|
||||
'id' => 'rank-math-schemas',
|
||||
'type' => 'textarea',
|
||||
'classes' => 'hidden',
|
||||
'save_field' => false,
|
||||
]
|
||||
);
|
||||
|
||||
$cmb->add_field(
|
||||
[
|
||||
'id' => 'rank-math-schemas-delete',
|
||||
'type' => 'textarea',
|
||||
'default' => '[]',
|
||||
'classes' => 'hidden',
|
||||
'save_field' => false,
|
||||
]
|
||||
);
|
Reference in New Issue
Block a user