Commit realizado el 12:13:52 08-04-2024

This commit is contained in:
Pagina Web Monito
2024-04-08 12:13:55 -04:00
commit 0c33094de9
7815 changed files with 1365694 additions and 0 deletions

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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'] );
}
}

View File

@@ -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 ) );
}
}

View File

@@ -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}

View File

@@ -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}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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 ]

View File

@@ -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>
)
}

View File

@@ -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,
}
)

View File

@@ -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>
)
}

View File

@@ -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>
)
} ) }
</>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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;
}
}

View File

@@ -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' ),
],
]
);