Commit inicial - WordPress Análisis de Precios Unitarios

- WordPress core y plugins
- Tema Twenty Twenty-Four configurado
- Plugin allow-unfiltered-html.php simplificado
- .gitignore configurado para excluir wp-config.php y uploads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-11-03 21:04:30 -06:00
commit a22573bf0b
24068 changed files with 4993111 additions and 0 deletions

View File

@@ -0,0 +1,204 @@
<?php
/**
* The AIOSEO Block Converter imports editor blocks (TOC) from AIOSEO to Rank Math.
*
* @since 1.0.104
* @package RankMath
* @subpackage RankMath\Status
* @author Rank Math <support@rankmath.com>
*/
namespace RankMath\Tools;
use RankMath\Helper;
defined( 'ABSPATH' ) || exit;
/**
* AIOSEO_Blocks class.
*/
class AIOSEO_Blocks extends \WP_Background_Process {
/**
* TOC Converter.
*
* @var AIOSEO_TOC_Converter
*/
private $toc_converter;
/**
* Action.
*
* @var string
*/
protected $action = 'convert_aioseo_blocks';
/**
* Main instance.
*
* Ensure only one instance is loaded or can be loaded.
*
* @return AIOSEO_Blocks
*/
public static function get() {
static $instance;
if ( is_null( $instance ) && ! ( $instance instanceof AIOSEO_Blocks ) ) {
$instance = new AIOSEO_Blocks();
}
return $instance;
}
/**
* Start creating batches.
*
* @param array $posts Posts to process.
*/
public function start( $posts ) {
$chunks = array_chunk( $posts, 10 );
foreach ( $chunks as $chunk ) {
$this->push_to_queue( $chunk );
}
$this->save()->dispatch();
}
/**
* Complete.
*
* Override if applicable, but ensure that the below actions are
* performed, or, call parent::complete().
*/
protected function complete() {
$posts = get_option( 'rank_math_aioseo_block_posts' );
delete_option( 'rank_math_aioseo_block_posts' );
Helper::add_notification(
// Translators: placeholder is the number of modified posts.
sprintf( _n( 'Blocks successfully converted in %d post.', 'Blocks successfully converted in %d posts.', $posts['count'], 'rank-math' ), $posts['count'] ),
[
'type' => 'success',
'id' => 'rank_math_aioseo_block_posts',
'classes' => 'rank-math-notice',
]
);
parent::complete();
}
/**
* Task to perform.
*
* @param string $posts Posts to process.
*/
public function wizard( $posts ) {
$this->task( $posts );
}
/**
* Task to perform.
*
* @param array $posts Posts to process.
*
* @return bool
*/
protected function task( $posts ) {
try {
remove_filter( 'pre_kses', 'wp_pre_kses_block_attributes', 10 );
$this->toc_converter = new AIOSEO_TOC_Converter();
foreach ( $posts as $post_id ) {
$post = get_post( $post_id );
$this->convert( $post );
}
return false;
} catch ( \Exception $error ) {
return true;
}
}
/**
* Convert post.
*
* @param WP_Post $post Post object.
*/
public function convert( $post ) {
$dirty = false;
$blocks = $this->parse_blocks( $post->post_content );
$content = '';
if ( isset( $blocks['aioseo/table-of-contents'] ) && ! empty( $blocks['aioseo/table-of-contents'] ) ) {
$dirty = true;
$content = $this->toc_converter->replace( $post->post_content, $blocks['aioseo/table-of-contents'] );
}
if ( $dirty ) {
$post->post_content = $content;
wp_update_post( $post );
}
}
/**
* Find posts with AIOSEO blocks.
*
* @return array
*/
public function find_posts() {
$posts = get_option( 'rank_math_aioseo_block_posts' );
if ( false !== $posts ) {
return $posts;
}
// TOC Posts.
$args = [
's' => 'wp:aioseo/table-of-contents ',
'post_status' => 'any',
'numberposts' => -1,
'fields' => 'ids',
'no_found_rows' => true,
'post_type' => 'any',
];
$toc_posts = get_posts( $args );
$posts_data = [
'posts' => $toc_posts,
'count' => count( $toc_posts ),
];
update_option( 'rank_math_aioseo_block_posts', $posts_data, false );
return $posts_data;
}
/**
* Parse blocks to get data.
*
* @param string $content Post content to parse.
*
* @return array
*/
private function parse_blocks( $content ) {
$parsed_blocks = parse_blocks( $content );
$blocks = [];
foreach ( $parsed_blocks as $block ) {
if ( empty( $block['blockName'] ) ) {
continue;
}
$name = strtolower( $block['blockName'] );
if ( ! isset( $blocks[ $name ] ) || ! is_array( $blocks[ $name ] ) ) {
$blocks[ $name ] = [];
}
if ( ! isset( $block['innerContent'] ) ) {
$block['innerContent'] = [];
}
if ( 'aioseo/table-of-contents' === $name ) {
$block = $this->toc_converter->convert( $block );
$blocks[ $name ][] = \serialize_block( $block );
}
}
return $blocks;
}
}

View File

@@ -0,0 +1,114 @@
<?php
/**
* The AIOSEO TOC Block Converter.
*
* @since 1.0.104
* @package RankMath
* @subpackage RankMath\Status
* @author Rank Math <support@rankmath.com>
*/
namespace RankMath\Tools;
defined( 'ABSPATH' ) || exit;
/**
* AIOSEO_TOC_Converter class.
*/
class AIOSEO_TOC_Converter {
/**
* Convert TOC blocks to Rank Math.
*
* @param array $block Block to convert.
*
* @return array
*/
public function convert( $block ) {
$attributes = $block['attrs'];
$headings = [];
$this->get_headings( $attributes['headings'], $headings );
$new_block = [
'blockName' => 'rank-math/toc-block',
'attrs' => [
'title' => '',
'headings' => $headings,
'listStyle' => $attributes['listStyle'] ?? 'ul',
'titleWrapper' => 'h2',
'excludeHeadings' => [],
],
];
$new_block['innerContent'][] = $this->get_html( $block['innerHTML'] );
return $new_block;
}
/**
* Replace block in content.
*
* @param string $post_content Post content.
* @param array $blocks Blocks.
*
* @return string
*/
public function replace( $post_content, $blocks ) {
preg_match_all( '/<!-- wp:aioseo\/table-of-contents.*-->.*<!-- \/wp:aioseo\/table-of-contents -->/iUs', $post_content, $matches );
foreach ( $matches[0] as $index => $match ) {
$post_content = \str_replace( $match, $blocks[ $index ], $post_content );
}
return $post_content;
}
/**
* Get headings from the content.
*
* @param array $data Block data.
* @param array $headings Headings.
*
* @return void
*/
public function get_headings( $data, &$headings ) {
foreach ( $data as $heading ) {
$headings[] = [
'key' => $heading['blockClientId'],
'link' => '#' . $heading['anchor'],
'content' => ! empty( $heading['editedContent'] ) ? $heading['editedContent'] : $heading['content'],
'level' => ! empty( $heading['editedLevel'] ) ? $heading['editedLevel'] : $heading['level'],
'disable' => ! empty( $heading['hidden'] ),
];
if ( ! empty( $heading['headings'] ) ) {
$this->get_headings( $heading['headings'], $headings );
}
}
}
/**
* Get TOC title.
*
* @param string $html Block HTML.
*
* @return string
*/
public function get_toc_title( $html ) {
preg_match( '#<h2.*?>(.*?)</h2>#i', $html, $found );
return ! empty( $found[1] ) ? $found[1] : '';
}
/**
* Generate HTML.
*
* @param string $html Block html.
*
* @return string
*/
private function get_html( $html ) {
$html = str_replace( 'wp-block-aioseo-table-of-contents', 'wp-block-rank-math-toc-block', $html );
$html = str_replace( '<div class="wp-block-rank-math-toc-block"><ul>', '<div class="wp-block-rank-math-toc-block"><nav><ul>', $html );
$html = str_replace( '</ul></div>', '</nav></ul></div>', $html );
return $html;
}
}

View File

@@ -0,0 +1,423 @@
<?php
/**
* The Database_Tools is responsible for the Database Tools inside Status & Tools.
*
* @package RankMath
* @subpackage RankMath\Database_Tools
*/
namespace RankMath\Tools;
use RankMath\Helper;
use RankMath\Helpers\Str;
use RankMath\Helpers\Arr;
use RankMath\Helpers\Schedule;
use RankMath\Installer;
use RankMath\Traits\Hooker;
use RankMath\Helpers\DB as DB_Helper;
use RankMath\Helpers\Sitepress;
defined( 'ABSPATH' ) || exit;
/**
* Database_Tools class.
*/
class Database_Tools {
use Hooker;
/**
* Constructor.
*/
public function __construct() {
if ( Helper::is_heartbeat() || ! Helper::is_advanced_mode() ) {
return;
}
Yoast_Blocks::get();
AIOSEO_Blocks::get();
Update_Score::get();
$this->hooks();
}
/**
* Register version control hooks.
*/
public function hooks() {
if ( Helper::is_rest() && Str::contains( 'toolsAction', add_query_arg( [] ) ) ) {
foreach ( $this->get_tools() as $id => $tool ) {
if ( ! method_exists( $this, $id ) ) {
continue;
}
add_filter( 'rank_math/tools/' . $id, [ $this, $id ] );
}
}
}
/**
* Get localized JSON data to be used on the Database Tools view of the Status & Tools page.
*/
public static function get_json_data() {
return [
'tools' => self::get_tools(),
];
}
/**
* Function to clear all the transients from the database.
*/
public function clear_transients() {
global $wpdb;
$transients = DB_Helper::get_col(
"SELECT `option_name` AS `name`
FROM $wpdb->options
WHERE `option_name` LIKE '%\\_transient\\_rank_math%'
ORDER BY `option_name`"
);
if ( empty( $transients ) ) {
return [
'status' => 'error',
'message' => __( 'No Rank Math transients found.', 'rank-math' ),
];
}
$count = 0;
foreach ( $transients as $transient ) {
delete_option( $transient );
++$count;
}
// Translators: placeholder is the number of transients deleted.
return sprintf( _n( '%d Rank Math transient cleared.', '%d Rank Math transients cleared.', $count, 'rank-math' ), $count );
}
/**
* Function to reset the SEO Analyzer.
*/
public function clear_seo_analysis() {
$stored = get_option( 'rank_math_seo_analysis_results' );
if ( empty( $stored ) ) {
return [
'status' => 'error',
'message' => __( 'SEO Analyzer data has already been cleared.', 'rank-math' ),
];
}
delete_option( 'rank_math_seo_analysis_results' );
delete_option( 'rank_math_seo_analysis_date' );
return __( 'SEO Analyzer data successfully deleted.', 'rank-math' );
}
/**
* Function to delete all the Internal Links data.
*/
public function delete_links() {
global $wpdb;
$exists = DB_Helper::get_var( "SELECT EXISTS ( SELECT 1 FROM {$wpdb->prefix}rank_math_internal_links )" );
if ( empty( $exists ) ) {
return [
'status' => 'error',
'message' => __( 'No Internal Links data found.', 'rank-math' ),
];
}
DB_Helper::query( "TRUNCATE TABLE {$wpdb->prefix}rank_math_internal_links" );
DB_Helper::query( "TRUNCATE TABLE {$wpdb->prefix}rank_math_internal_meta" );
return __( 'Internal Links successfully deleted.', 'rank-math' );
}
/**
* Function to delete all the 404 log items.
*/
public function delete_log() {
global $wpdb;
$exists = DB_Helper::get_var( "SELECT EXISTS ( SELECT 1 FROM {$wpdb->prefix}rank_math_404_logs )" );
if ( empty( $exists ) ) {
return [
'status' => 'error',
'message' => __( 'No 404 log data found.', 'rank-math' ),
];
}
DB_Helper::query( "TRUNCATE TABLE {$wpdb->prefix}rank_math_404_logs;" );
return __( '404 Log successfully deleted.', 'rank-math' );
}
/**
* Function to delete all Redirections data.
*/
public function delete_redirections() {
global $wpdb;
$exists = DB_Helper::get_var( "SELECT EXISTS ( SELECT 1 FROM {$wpdb->prefix}rank_math_redirections )" );
if ( empty( $exists ) ) {
return [
'status' => 'error',
'message' => __( 'No Redirections found.', 'rank-math' ),
];
}
DB_Helper::query( "TRUNCATE TABLE {$wpdb->prefix}rank_math_redirections;" );
DB_Helper::query( "TRUNCATE TABLE {$wpdb->prefix}rank_math_redirections_cache;" );
return __( 'Redirection rules successfully deleted.', 'rank-math' );
}
/**
* Re-create Database Tables.
*
* @return string
*/
public function recreate_tables() {
// Base.
Installer::create_tables( get_option( 'rank_math_modules', [] ) );
// ActionScheduler.
$this->maybe_recreate_actionscheduler_tables();
// Analytics module.
if ( Helper::is_module_active( 'analytics' ) ) {
Schedule::async_action(
'rank_math/analytics/workflow/create_tables',
[],
'rank-math'
);
}
return __( 'Table re-creation started. It might take a couple of minutes.', 'rank-math' );
}
/**
* Recreate ActionScheduler tables if missing.
*/
public function maybe_recreate_actionscheduler_tables() {
global $wpdb;
if ( Helper::is_woocommerce_active() ) {
return;
}
if (
! class_exists( 'ActionScheduler_HybridStore' )
|| ! class_exists( 'ActionScheduler_StoreSchema' )
|| ! class_exists( 'ActionScheduler_LoggerSchema' )
) {
return;
}
$table_list = [
'actionscheduler_actions',
'actionscheduler_logs',
'actionscheduler_groups',
'actionscheduler_claims',
];
$found_tables = DB_Helper::get_col( "SHOW TABLES LIKE '{$wpdb->prefix}actionscheduler%'" );
foreach ( $table_list as $table_name ) {
if ( ! in_array( $wpdb->prefix . $table_name, $found_tables, true ) ) {
$this->recreate_actionscheduler_tables();
return;
}
}
}
/**
* Force the data store schema updates.
*/
public function recreate_actionscheduler_tables() {
$store = new \ActionScheduler_HybridStore();
add_action( 'action_scheduler/created_table', [ $store, 'set_autoincrement' ], 10, 2 );
$store_schema = new \ActionScheduler_StoreSchema();
$logger_schema = new \ActionScheduler_LoggerSchema();
$store_schema->register_tables( true );
$logger_schema->register_tables( true );
remove_action( 'action_scheduler/created_table', [ $store, 'set_autoincrement' ], 10 );
}
/**
* Function to convert Yoast blocks in posts to Rank Math blocks (FAQ & HowTo).
*
* @return string
*/
public function yoast_blocks() {
$posts = Yoast_Blocks::get()->find_posts();
if ( empty( $posts['posts'] ) ) {
return [
'status' => 'error',
'message' => __( 'No posts found to convert.', 'rank-math' ),
];
}
Yoast_Blocks::get()->start( $posts['posts'] );
return __( 'Conversion started. A success message will be shown here once the process completes. You can close this page.', 'rank-math' );
}
/**
* Function to convert AIOSEO blocks in posts to Rank Math blocks (TOC).
*
* @return string
*/
public function aioseo_blocks() {
$posts = AIOSEO_Blocks::get()->find_posts();
if ( empty( $posts['posts'] ) ) {
return [
'status' => 'error',
'message' => __( 'No posts found to convert.', 'rank-math' ),
];
}
AIOSEO_Blocks::get()->start( $posts['posts'] );
return __( 'Conversion started. A success message will be shown here once the process completes. You can close this page.', 'rank-math' );
}
/**
* Get tools.
*
* @return array
*/
private static function get_tools() {
$tools = [];
if ( Helper::is_module_active( 'seo-analysis' ) ) {
$tools['clear_seo_analysis'] = [
'title' => __( 'Flush SEO Analyzer Data', 'rank-math' ),
'description' => __( "Need a clean slate or not able to run the SEO Analyzer tool? Flushing the analysis data might fix the issue. Flushing SEO Analyzer data is entirely safe and doesn't remove any critical data from your website.", 'rank-math' ),
'button_text' => __( 'Clear SEO Analyzer', 'rank-math' ),
];
}
$tools['clear_transients'] = [
'title' => __( 'Remove Rank Math Transients', 'rank-math' ),
'description' => __( 'If you see any issue while using Rank Math or one of its options - clearing the Rank Math transients fixes the problem in most cases. Deleting transients does not delete ANY data added using Rank Math.', 'rank-math' ),
'button_text' => __( 'Remove transients', 'rank-math' ),
];
if ( Helper::is_module_active( '404-monitor' ) ) {
$tools['delete_log'] = [
'title' => __( 'Clear 404 Log', 'rank-math' ),
'description' => __( 'Is the 404 error log getting out of hand? Use this option to clear ALL 404 logs generated by your website in the Rank Math 404 Monitor.', 'rank-math' ),
'confirm_text' => __( 'Are you sure you want to delete the 404 log? This action is irreversible.', 'rank-math' ),
'button_text' => __( 'Clear 404 Log', 'rank-math' ),
];
}
$tools['recreate_tables'] = [
'title' => __( 'Re-create Missing Database Tables', 'rank-math' ),
'description' => __( 'Check if required tables exist and create them if not.', 'rank-math' ),
'button_text' => __( 'Re-create Tables', 'rank-math' ),
];
if ( Helper::is_module_active( 'analytics' ) ) {
$tools['analytics_fix_collations'] = [
'title' => __( 'Fix Analytics table collations', 'rank-math' ),
'description' => __( 'In some cases, the Analytics database tables or columns don\'t match with each other, which can cause database errors. This tool can fix that issue.', 'rank-math' ),
'button_text' => __( 'Fix Collations', 'rank-math' ),
];
}
$block_posts = Yoast_Blocks::get()->find_posts();
if ( is_array( $block_posts ) && ! empty( $block_posts['count'] ) ) {
$tools['yoast_blocks'] = [
'title' => __( 'Yoast Block Converter', 'rank-math' ),
'description' => __( 'Convert FAQ, HowTo, & Table of Contents Blocks created using Yoast. Use this option to easily move your previous blocks into Rank Math.', 'rank-math' ),
'confirm_text' => __( 'Are you sure you want to convert Yoast blocks into Rank Math blocks? This action is irreversible.', 'rank-math' ),
'button_text' => __( 'Convert Blocks', 'rank-math' ),
];
}
$aio_block_posts = AIOSEO_Blocks::get()->find_posts();
if ( is_array( $aio_block_posts ) && ! empty( $aio_block_posts['count'] ) ) {
$tools['aioseo_blocks'] = [
'title' => __( 'AIOSEO Block Converter', 'rank-math' ),
'description' => __( 'Convert TOC block created using AIOSEO. Use this option to easily move your previous blocks into Rank Math.', 'rank-math' ),
'confirm_text' => __( 'Are you sure you want to convert AIOSEO blocks into Rank Math blocks? This action is irreversible.', 'rank-math' ),
'button_text' => __( 'Convert Blocks', 'rank-math' ),
];
}
if ( Helper::is_module_active( 'link-counter' ) ) {
$tools['delete_links'] = [
'title' => __( 'Delete Internal Links Data', 'rank-math' ),
'description' => __( 'In some instances, the internal links data might show an inflated number or no number at all. Deleting the internal links data might fix the issue.', 'rank-math' ),
'confirm_text' => __( 'Are you sure you want to delete Internal Links Data? This action is irreversible.', 'rank-math' ),
'button_text' => __( 'Delete Internal Links', 'rank-math' ),
];
}
if ( Helper::is_module_active( 'redirections' ) ) {
$tools['delete_redirections'] = [
'title' => __( 'Delete Redirections Rules', 'rank-math' ),
'description' => __( 'Getting a redirection loop or need a fresh start? Delete all the redirections using this tool. Note: This process is irreversible and will delete ALL your redirection rules.', 'rank-math' ),
'confirm_text' => __( 'Are you sure you want to delete all the Redirection Rules? This action is irreversible.', 'rank-math' ),
'button_text' => __( 'Delete Redirections', 'rank-math' ),
];
}
if ( ! empty( Update_Score::get()->find() ) ) {
$tools['update_seo_score'] = [
'title' => __( 'Update SEO Scores', 'rank-math' ),
'description' => __( 'This tool will calculate the SEO score for the posts/pages that have a Focus Keyword set. Note: This process may take some time and the browser tab must be kept open while it is running.', 'rank-math' ),
'button_text' => __( 'Recalculate Scores', 'rank-math' ),
];
}
if ( Helper::is_module_active( 'analytics' ) && Helper::has_cap( 'analytics' ) ) {
Arr::insert(
$tools,
[
'analytics_clear_caches' => [
'title' => __( 'Purge Analytics Cache', 'rank-math' ),
'description' => __( 'Clear analytics cache to re-calculate all the stats again.', 'rank-math' ),
'button_text' => __( 'Clear Cache', 'rank-math' ),
],
],
3
);
$description = __( 'Missing some posts/pages in the Analytics data? Clear the index and build a new one for more accurate stats.', 'rank-math' );
$sitepress = Sitepress::get()->is_active() ? Sitepress::get()->get_var() : false;
if ( Sitepress::get()->is_per_domain() && ! empty( $sitepress->get_setting( 'auto_adjust_ids', null ) ) ) {
$description .= '<br /><br /><i>' . sprintf(
/* translators: 1: settings URL, 2: settings text */
__( 'To properly rebuild Analytics posts in secondary languages, please disable the %1$s when using a different domain per language.', 'rank-math' ),
'<a href="' . esc_url( admin_url( 'admin.php?page=sitepress-multilingual-cms/menu/languages.php#lang-sec-8' ) ) . '">' . __( 'Make themes work multilingual option in WPML settings', 'rank-math' ) . '</a>'
) . '</i>';
}
Arr::insert(
$tools,
[
'analytics_reindex_posts' => [
'title' => __( 'Rebuild Index for Analytics', 'rank-math' ),
'description' => $description,
'button_text' => __( 'Rebuild Index', 'rank-math' ),
],
],
3
);
}
/**
* Filters the list of tools available on the Database Tools page.
*
* @param array $tools The tools.
*/
$tools = apply_filters( 'rank_math/database/tools', $tools );
return $tools;
}
}

View File

@@ -0,0 +1,252 @@
<?php
/**
* The tool to update SEO score on existing posts.
*
* @since 1.0.97
* @package RankMath
* @subpackage RankMath\Status
* @author Rank Math <support@rankmath.com>
*/
namespace RankMath\Tools;
use RankMath\Helper;
use RankMath\Helpers\Param;
use RankMath\Traits\Hooker;
use RankMath\Paper\Paper;
use RankMath\Admin\Metabox\Screen;
use RankMath\Helpers\DB as DB_Helper;
defined( 'ABSPATH' ) || exit;
/**
* Update_Score class.
*/
class Update_Score {
use Hooker;
/**
* Batch size.
*
* @var int
*/
private $batch_size;
/**
* Screen object.
*
* @var object
*/
public $screen;
/**
* Constructor.
*/
public function __construct() {
$this->batch_size = absint( apply_filters( 'rank_math/recalculate_scores_batch_size', 25 ) );
$this->filter( 'rank_math/tools/update_seo_score', 'update_seo_score' );
$this->screen = new Screen();
$this->screen->load_screen( 'post' );
if ( Param::get( 'page' ) === 'rank-math-status' ) {
$this->action( 'admin_enqueue_scripts', 'enqueue' );
}
}
/**
* Enqueue scripts & add JSON data needed to update the SEO score on existing posts.
*/
public function enqueue() {
$scripts = [
'lodash' => '',
'wp-data' => '',
'wp-core-data' => '',
'wp-compose' => '',
'wp-components' => '',
'wp-element' => '',
'wp-block-editor' => '',
'wp-wordcount' => '',
'rank-math-analyzer' => rank_math()->plugin_url() . 'assets/admin/js/analyzer.js',
];
foreach ( $scripts as $handle => $src ) {
wp_enqueue_script( $handle, $src, [], rank_math()->version, true );
}
global $post;
$temp_post = $post;
if ( is_null( $post ) ) {
$posts = get_posts(
[
'fields' => 'id',
'posts_per_page' => 1,
'post_type' => $this->get_post_types(),
]
);
$post = isset( $posts[0] ) ? $posts[0] : null; //phpcs:ignore -- Overriding $post is required to load the localized data for the post.
}
$this->screen->localize();
$post = $temp_post; //phpcs:ignore -- Overriding $post is required to load the localized data for the post.
Helper::add_json( 'totalPostsWithoutScore', $this->find( false ) );
Helper::add_json( 'totalPosts', $this->find( true ) );
Helper::add_json( 'batchSize', $this->batch_size );
}
/**
* Function to Update the SEO score.
*/
public function update_seo_score() {
$args = Param::post( 'args', [], FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
$offset = isset( $args['offset'] ) ? absint( $args['offset'] ) : 0;
// We get "paged" when running from the importer.
$paged = Param::post( 'paged', 0 );
if ( $paged ) {
$offset = ( $paged - 1 ) * $this->batch_size;
}
$update_all = ! isset( $args['update_all_scores'] ) || ! empty( $args['update_all_scores'] );
$query_args = [
'post_type' => $this->get_post_types(),
'posts_per_page' => $this->batch_size,
'offset' => $offset,
'orderby' => 'ID',
'order' => 'ASC',
'status' => 'any',
'meta_query' => [
'relation' => 'AND',
[
'key' => 'rank_math_focus_keyword',
'value' => '',
'compare' => '!=',
],
],
];
if ( ! $update_all ) {
$query_args['meta_query'][] = [
'relation' => 'OR',
[
'key' => 'rank_math_seo_score',
'compare' => 'NOT EXISTS',
],
[
'key' => 'rank_math_seo_score',
'value' => '',
'compare' => '=',
],
];
}
$posts = get_posts( $query_args );
if ( empty( $posts ) ) {
return 'complete'; // Don't translate this string.
}
add_filter(
'rank_math/replacements/non_cacheable',
function ( $non_cacheable ) {
$non_cacheable[] = 'excerpt';
$non_cacheable[] = 'excerpt_only';
$non_cacheable[] = 'seo_description';
$non_cacheable[] = 'keywords';
$non_cacheable[] = 'focuskw';
return $non_cacheable;
}
);
rank_math()->variables->setup();
$data = [];
foreach ( $posts as $post ) {
$post_id = $post->ID;
$post_type = $post->post_type;
$title = get_post_meta( $post_id, 'rank_math_title', true );
$title = $title ? $title : Paper::get_from_options( "pt_{$post_type}_title", $post, '%title% %sep% %sitename%' );
$keywords = array_map( 'trim', explode( ',', Helper::get_post_meta( 'focus_keyword', $post_id ) ) );
$keyword = $keywords[0];
$values = [
'title' => Helper::replace_vars( '%seo_title%', $post ),
'description' => Helper::replace_vars( '%seo_description%', $post ),
'keywords' => $keywords,
'keyword' => $keyword,
'content' => wpautop( $post->post_content ),
'url' => urldecode( get_the_permalink( $post_id ) ),
'hasContentAi' => ! empty( Helper::get_post_meta( 'contentai_score', $post_id ) ),
];
if ( has_post_thumbnail( $post_id ) ) {
$thumbnail_id = get_post_thumbnail_id( $post_id );
$values['thumbnail'] = get_the_post_thumbnail_url( $post_id );
$values['thumbnailAlt'] = get_post_meta( $thumbnail_id, '_wp_attachment_image_alt', true );
}
/**
* Filter the values sent to the analyzer to calculate the SEO score.
*
* @param array $values The values to be sent to the analyzer.
*/
$data[ $post_id ] = $this->do_filter( 'recalculate_score/data', $values, $post_id );
}
return $data;
}
/**
* Ensure only one instance is loaded or can be loaded.
*
* @return Update_Score
*/
public static function get() {
static $instance;
if ( is_null( $instance ) && ! ( $instance instanceof Update_Score ) ) {
$instance = new Update_Score();
}
return $instance;
}
/**
* Find posts with focus keyword but no SEO score.
*
* @param bool $update_all Whether to update all posts or only those without a score.
* @return int
*/
public function find( $update_all = true ) {
global $wpdb;
$post_types = $this->get_post_types();
$placeholder = implode( ', ', array_fill( 0, count( $post_types ), '%s' ) );
$query = "SELECT COUNT(ID) FROM {$wpdb->posts} as p
LEFT JOIN {$wpdb->postmeta} as pm ON p.ID = pm.post_id AND pm.meta_key = 'rank_math_focus_keyword'
WHERE p.post_type IN ({$placeholder}) AND p.post_status = 'publish' AND pm.meta_value != ''";
if ( ! $update_all ) {
$query .= " AND (SELECT COUNT(*) FROM {$wpdb->postmeta} as pm2 WHERE pm2.post_id = p.ID AND pm2.meta_key = 'rank_math_seo_score' AND pm2.meta_value != '') = 0";
}
$update_score_post_ids = DB_Helper::get_var( $wpdb->prepare( $query, $post_types ) );
return (int) $update_score_post_ids;
}
/**
* Get post types.
*
* @return array
*/
private function get_post_types() {
$post_types = get_post_types( [ 'public' => true ] );
if ( isset( $post_types['attachment'] ) ) {
unset( $post_types['attachment'] );
}
return $this->do_filter( 'tool/post_types', array_keys( $post_types ) );
}
}

View File

@@ -0,0 +1,305 @@
<?php
/**
* The Yoast Block Converter imports editor blocks (FAQ, HowTo, Local Business) from Yoast to Rank Math.
*
* @since 1.0.37
* @package RankMath
* @subpackage RankMath\Status
* @author Rank Math <support@rankmath.com>
*/
namespace RankMath\Tools;
use RankMath\Helper;
defined( 'ABSPATH' ) || exit;
/**
* Yoast_Blocks class.
*/
class Yoast_Blocks extends \WP_Background_Process {
/**
* FAQ Converter.
*
* @var Yoast_FAQ_Converter
*/
private $faq_converter;
/**
* FAQ Converter.
*
* @var Yoast_HowTo_Converter
*/
private $howto_converter;
/**
* TOC Converter.
*
* @var Yoast_TOC_Converter
*/
private $toc_converter;
/**
* Local Converter.
*
* @var Yoast_Local_Converter
*/
private $local_converter;
/**
* Action.
*
* @var string
*/
protected $action = 'convert_yoast_blocks';
/**
* Holds blocks for each post.
*
* @var array
*/
private static $yoast_blocks = [];
/**
* Main instance.
*
* Ensure only one instance is loaded or can be loaded.
*
* @return Yoast_Blocks
*/
public static function get() {
static $instance;
if ( is_null( $instance ) && ! ( $instance instanceof Yoast_Blocks ) ) {
$instance = new Yoast_Blocks();
}
return $instance;
}
/**
* Start creating batches.
*
* @param array $posts Posts to process.
*/
public function start( $posts ) {
$chunks = array_chunk( $posts, 10 );
foreach ( $chunks as $chunk ) {
$this->push_to_queue( $chunk );
}
$this->save()->dispatch();
}
/**
* Complete.
*
* Override if applicable, but ensure that the below actions are
* performed, or, call parent::complete().
*/
protected function complete() {
$posts = get_option( 'rank_math_yoast_block_posts' );
delete_option( 'rank_math_yoast_block_posts' );
Helper::add_notification(
// Translators: placeholder is the number of modified posts.
sprintf( _n( 'Blocks successfully converted in %d post.', 'Blocks successfully converted in %d posts.', $posts['count'], 'rank-math' ), $posts['count'] ),
[
'type' => 'success',
'id' => 'rank_math_yoast_block_posts',
'classes' => 'rank-math-notice',
]
);
parent::complete();
}
/**
* Task to perform.
*
* @param string $posts Posts to process.
*/
public function wizard( $posts ) {
$this->task( $posts );
}
/**
* Task to perform.
*
* @param array $posts Posts to process.
*
* @return bool
*/
protected function task( $posts ) {
try {
remove_filter( 'pre_kses', 'wp_pre_kses_block_attributes', 10 );
$this->faq_converter = new Yoast_FAQ_Converter();
$this->howto_converter = new Yoast_HowTo_Converter();
$this->local_converter = new Yoast_Local_Converter();
$this->toc_converter = new Yoast_TOC_Converter();
foreach ( $posts as $post_id ) {
self::$yoast_blocks = [];
$post = get_post( $post_id );
$this->convert( $post );
}
return false;
} catch ( \Exception $error ) {
return true;
}
}
/**
* Convert post.
*
* @param WP_Post $post Post object.
*/
public function convert( $post ) {
$dirty = false;
$blocks = $this->parse_blocks( $post->post_content );
$content = '';
if ( isset( $blocks['yoast/faq-block'] ) && ! empty( $blocks['yoast/faq-block'] ) ) {
$dirty = true;
$content = $this->faq_converter->replace( $post->post_content, $blocks['yoast/faq-block'] );
}
if ( isset( $blocks['yoast/how-to-block'] ) && ! empty( $blocks['yoast/how-to-block'] ) ) {
$dirty = true;
$content = $this->howto_converter->replace( $post->post_content, $blocks['yoast/how-to-block'] );
}
if ( isset( $blocks['yoast-seo/table-of-contents'] ) && ! empty( $blocks['yoast-seo/table-of-contents'] ) ) {
$dirty = true;
$content = $this->toc_converter->replace( $post->post_content, $blocks['yoast-seo/table-of-contents'] );
}
if ( ! empty( array_intersect( array_keys( $blocks ), $this->local_converter->yoast_blocks ) ) ) {
$dirty = true;
$content = $this->local_converter->replace( $post->post_content, $blocks );
}
if ( $dirty ) {
$post->post_content = $content;
wp_update_post( $post );
}
}
/**
* Find posts with Yoast blocks.
*
* @return array
*/
public function find_posts() {
$posts = get_option( 'rank_math_yoast_block_posts' );
if ( ! empty( $posts['posts'] ) ) {
return $posts;
}
// FAQs Posts.
$args = [
's' => 'wp:yoast/faq-block',
'post_status' => 'any',
'numberposts' => -1,
'fields' => 'ids',
'no_found_rows' => true,
'post_type' => 'any',
];
$faqs = get_posts( $args );
// HowTo Posts.
$args['s'] = 'wp:yoast/how-to-block';
$howto = get_posts( $args );
// TOC Posts.
$args['s'] = 'wp:yoast-seo/table-of-contents';
$toc = get_posts( $args );
// Local Business Posts.
$args['s'] = ':yoast-seo-local/';
$local_business = get_posts( $args );
$posts = array_merge( $faqs, $howto, $toc, $local_business );
$posts_data = [
'posts' => $posts,
'count' => count( $posts ),
];
update_option( 'rank_math_yoast_block_posts', $posts_data, false );
return $posts_data;
}
/**
* Parse blocks to get data.
*
* @param string $content Post content to parse.
*
* @return array
*/
private function parse_blocks( $content ) {
$parsed_blocks = $this->filter_parsed_blocks( parse_blocks( $content ) );
$blocks = [];
foreach ( $parsed_blocks as $block ) {
if ( empty( $block['blockName'] ) ) {
continue;
}
$name = strtolower( $block['blockName'] );
if ( ! isset( $blocks[ $name ] ) || ! is_array( $blocks[ $name ] ) ) {
$blocks[ $name ] = [];
}
if ( ! isset( $block['innerContent'] ) ) {
$block['innerContent'] = [];
}
if ( 'yoast/faq-block' === $name ) {
$block = $this->faq_converter->convert( $block );
$blocks[ $name ][] = \serialize_block( $block );
}
if ( 'yoast/how-to-block' === $name ) {
$block = $this->howto_converter->convert( $block );
$blocks[ $name ][] = \serialize_block( $block );
}
if ( 'yoast-seo/table-of-contents' === $name ) {
$block = $this->toc_converter->convert( $block );
$blocks[ $name ][] = \serialize_block( $block );
}
if ( in_array( $name, $this->local_converter->yoast_blocks, true ) ) {
$block = $this->local_converter->convert( $block );
$blocks[ $name ][] = \serialize_block( $block );
}
}
return $blocks;
}
/**
* Does a deep filter for blocks.
*
* @param array $blocks The blocks array.
*
* @return array
*/
private function filter_parsed_blocks( $blocks ) {
$block_names = [
'yoast-seo/table-of-contents',
'yoast/how-to-block',
'yoast/faq-block',
];
foreach ( $blocks as $block ) {
if ( in_array( $block['blockName'], $block_names, true ) ) {
self::$yoast_blocks[] = $block;
}
if ( ! empty( $block['innerBlocks'] ) ) {
$this->filter_parsed_blocks( $block['innerBlocks'] );
}
}
return self::$yoast_blocks;
}
}

View File

@@ -0,0 +1,118 @@
<?php
/**
* The Yoast FAQ Block Converter.
*
* @since 1.0.37
* @package RankMath
* @subpackage RankMath\Status
* @author Rank Math <support@rankmath.com>
*/
namespace RankMath\Tools;
defined( 'ABSPATH' ) || exit;
/**
* Yoast_FAQ_Converter class.
*/
class Yoast_FAQ_Converter {
/**
* Convert FAQ blocks to Rank Math.
*
* @param array $block Block to convert.
*
* @return array
*/
public function convert( $block ) {
$new_block = [
'blockName' => 'rank-math/faq-block',
'attrs' => [
'listStyle' => '',
'textAlign' => 'left',
'titleWrapper' => 'h3',
'listCssClasses' => '',
'titleCssClasses' => '',
'contentCssClasses' => '',
'questions' => array_map( [ $this, 'get_question' ], $block['attrs']['questions'] ),
'className' => isset( $block['attrs']['className'] ) ? $block['attrs']['className'] : '',
],
];
$new_block['innerContent'][] = $this->get_html( $new_block['attrs'] );
return $new_block;
}
/**
* Replace block in content.
*
* @param string $post_content Post content.
* @param array $blocks Blocks.
*
* @return string
*/
public function replace( $post_content, $blocks ) {
preg_match_all( '/<!-- wp:yoast\/faq-block.*-->.*<!-- \/wp:yoast\/faq-block -->/iUs', $post_content, $matches );
foreach ( $matches[0] as $index => $match ) {
$post_content = \str_replace( $match, $blocks[ $index ], $post_content );
}
return $post_content;
}
/**
* Format questions.
*
* @param array $question Question.
*
* @return array
*/
public function get_question( $question ) {
return [
'id' => uniqid( 'faq-question-' ),
'visible' => true,
'title' => $question['jsonQuestion'],
'content' => $question['jsonAnswer'],
];
}
/**
* Generate HTML.
*
* @param array $attributes Block attributes.
*
* @return string
*/
private function get_html( $attributes ) {
// HTML.
$out = [ '<div class="wp-block-rank-math-faq-block">' ];
// Questions.
foreach ( $attributes['questions'] as $question ) {
if ( empty( $question['title'] ) || empty( $question['content'] ) || empty( $question['visible'] ) ) {
continue;
}
$out[] = '<div class="rank-math-faq-item">';
$out[] = sprintf(
'<%1$s class="rank-math-question">%2$s</%1$s>',
$attributes['titleWrapper'],
$question['title']
);
$out[] = sprintf(
'<div class="rank-math-answer">%2$s</div>',
$attributes['titleWrapper'],
$question['content']
);
$out[] = '</div>';
}
$out[] = '</div>';
return join( '', $out );
}
}

View File

@@ -0,0 +1,129 @@
<?php
/**
* The Yoast HowTo Block Converter.
*
* @since 1.0.37
* @package RankMath
* @subpackage RankMath\Status
* @author Rank Math <support@rankmath.com>
*/
namespace RankMath\Tools;
defined( 'ABSPATH' ) || exit;
/**
* Yoast_HowTo_Converter class.
*/
class Yoast_HowTo_Converter {
/**
* Convert HowTo blocks to Rank Math.
*
* @param array $block Block to convert.
*
* @return array
*/
public function convert( $block ) {
$attrs = $block['attrs'];
$new_block = [
'blockName' => 'rank-math/howto-block',
'attrs' => [
'hasDuration' => isset( $attrs['hasDuration'] ) ? $attrs['hasDuration'] : '',
'days' => isset( $attrs['days'] ) ? $attrs['days'] : '',
'hours' => isset( $attrs['hours'] ) ? $attrs['hours'] : '',
'minutes' => isset( $attrs['minutes'] ) ? $attrs['minutes'] : '',
'description' => isset( $attrs['jsonDescription'] ) ? $attrs['jsonDescription'] : '',
'listStyle' => isset( $attrs['unorderedList'] ) && $attrs['unorderedList'] ? 'ol' : '',
'timeLabel' => isset( $attrs['durationText'] ) ? $attrs['durationText'] : '',
'textAlign' => 'left',
'titleWrapper' => 'h3',
'listCssClasses' => '',
'titleCssClasses' => '',
'contentCssClasses' => '',
'steps' => array_map( [ $this, 'get_step' ], $attrs['steps'] ),
'className' => isset( $attrs['className'] ) ? $attrs['className'] : '',
],
];
$new_block['innerContent'][] = $this->get_html( $new_block['attrs'] );
return $new_block;
}
/**
* Replace block in content.
*
* @param string $post_content Post content.
* @param array $blocks Blocks.
*
* @return string
*/
public function replace( $post_content, $blocks ) {
preg_match_all( '/<!-- wp:yoast\/how-to-block.*-->.*<!-- \/wp:yoast\/how-to-block -->/iUs', $post_content, $matches );
foreach ( $matches[0] as $index => $match ) {
$post_content = \str_replace( $match, $blocks[ $index ], $post_content );
}
return $post_content;
}
/**
* Format steps.
*
* @param array $step Steps.
*
* @return array
*/
public function get_step( $step ) {
return [
'id' => uniqid( 'howto-step-' ),
'visible' => true,
'title' => $step['jsonName'],
'content' => $step['jsonText'],
];
}
/**
* Generate HTML.
*
* @param array $attributes Block attributes.
*
* @return string
*/
private function get_html( $attributes ) {
// HTML.
$out = [ '<div class="wp-block-rank-math-howto-block">' ];
// Steps.
foreach ( $attributes['steps'] as $step ) {
if ( empty( $step['visible'] ) ) {
continue;
}
$out[] = '<div class="rank-math-howto-step">';
if ( ! empty( $step['title'] ) ) {
$out[] = sprintf(
'<%1$s class="rank-math-howto-title">%2$s</%1$s>',
$attributes['titleWrapper'],
$step['title']
);
}
if ( ! empty( $step['content'] ) ) {
$out[] = sprintf(
'<div class="rank-math-howto-content">%1$s</div>',
$step['content']
);
}
$out[] = '</div>';
}
$out[] = '</div>';
return join( '', $out );
}
}

View File

@@ -0,0 +1,142 @@
<?php
/**
* The Yoast Local Business Block Converter.
*
* @since 1.0.48
* @package RankMath
* @subpackage RankMath\Status
* @author Rank Math <support@rankmath.com>
*/
namespace RankMath\Tools;
use RankMath\Helper;
defined( 'ABSPATH' ) || exit;
/**
* Yoast_Local_Converter class.
*/
class Yoast_Local_Converter {
/**
* Yoast's Local Business Blocks.
*
* @var array
*/
public $yoast_blocks = [
'yoast-seo-local/store-locator',
'yoast-seo-local/address',
'yoast-seo-local/map',
'yoast-seo-local/opening-hours',
];
/**
* Convert Local Business blocks to Rank Math.
*
* @param array $block Block to convert.
*
* @return array
*/
public function convert( $block ) {
$block['attrs']['type'] = str_replace( 'yoast-seo-local/', '', $block['blockName'] );
$new_block = [
'blockName' => 'rank-math/local-business',
'attrs' => $this->get_attributes( $block['attrs'] ),
];
$new_block['innerContent'] = '';
return $new_block;
}
/**
* Replace block in content.
*
* @param string $post_content Post content.
* @param array $blocks Blocks.
*
* @return string
*/
public function replace( $post_content, $blocks ) {
foreach ( $blocks as $block_name => $block ) {
if ( ! in_array( $block_name, $this->yoast_blocks, true ) ) {
continue;
}
$block_name = str_replace( 'yoast-seo-local/', '', $block_name );
preg_match_all( "/<!-- wp:yoast-seo-local\/{$block_name}.*-->.*<!-- \/wp:yoast-seo-local\/{$block_name} -->/iUs", $post_content, $matches );
foreach ( $matches[0] as $index => $match ) {
$post_content = \str_replace( $match, $block[ $index ], $post_content );
}
}
return $post_content;
}
/**
* Get Block attributes.
*
* @param array $attrs Yoast Block Attributes.
*/
private function get_attributes( $attrs ) {
$default_opening_days = 'Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday';
if ( 'opening-hours' === $attrs['type'] ) {
$days = explode( ', ', $default_opening_days );
$default_days = [];
foreach ( $days as $day ) {
if ( isset( $attrs[ "show{$day}" ] ) ) {
$default_days[ $day ] = ! empty( $attrs[ "show{$day}" ] );
}
}
if ( ! empty( $default_days ) ) {
$default_opening_days = implode( ',', array_filter( array_keys( $default_days ) ) );
}
}
return [
'type' => isset( $attrs['type'] ) ? $attrs['type'] : 'address',
'locations' => 0,
'terms' => [],
'limit' => isset( $attrs['maxNumber'] ) ? $attrs['maxNumber'] : 0,
'show_company_name' => isset( $attrs['hideName'] ) ? ! $attrs['hideName'] : true,
'show_company_address' => isset( $attrs['hideCompanyAddress'] ) ? ! $attrs['hideCompanyAddress'] : true,
'show_on_one_line' => isset( $attrs['showOnOneLine'] ) ? ! $attrs['showOnOneLine'] : false,
'show_state' => isset( $attrs['showState'] ) ? $attrs['showState'] : true,
'show_country' => isset( $attrs['showCountry'] ) ? $attrs['showCountry'] : true,
'show_telephone' => isset( $attrs['showPhone'] ) ? $attrs['showPhone'] : true,
'show_secondary_number' => isset( $attrs['showPhone2nd'] ) ? $attrs['showPhone2nd'] : true,
'show_fax' => isset( $attrs['showFax'] ) ? $attrs['showFax'] : false,
'show_email' => isset( $attrs['showEmail'] ) ? $attrs['showEmail'] : true,
'show_url' => isset( $attrs['showURL'] ) ? $attrs['showURL'] : true,
'show_logo' => isset( $attrs['showLogo'] ) ? $attrs['showLogo'] : true,
'show_vat_id' => isset( $attrs['showVatId'] ) ? $attrs['showVatId'] : false,
'show_tax_id' => isset( $attrs['showTaxId'] ) ? $attrs['showTaxId'] : false,
'show_coc_id' => isset( $attrs['showCocId'] ) ? $attrs['showCocId'] : false,
'show_priceRange' => isset( $attrs['showPriceRange'] ) ? $attrs['showPriceRange'] : false,
'show_opening_hours' => isset( $attrs['showOpeningHours'] ) ? $attrs['showOpeningHours'] : false,
'show_days' => $default_opening_days,
'hide_closed_days' => isset( $attrs['hideClosedDays'] ) ? $attrs['hideClosedDays'] : false,
'show_opening_now_label' => isset( $attrs['showOpenLabel'] ) ? $attrs['showOpenLabel'] : false,
'opening_hours_note' => isset( $attrs['extraComment'] ) ? $attrs['extraComment'] : '',
'show_map' => isset( $attrs['showMap'] ) ? $attrs['showMap'] : false,
'map_type' => isset( $attrs['mapType'] ) ? $attrs['mapType'] : 'roadmap',
'map_width' => isset( $attrs['mapWidth'] ) ? $attrs['mapWidth'] : '500',
'map_height' => isset( $attrs['mapHeight'] ) ? $attrs['mapHeight'] : '300',
'zoom_level' => isset( $attrs['zoomLevel'] ) ? $attrs['zoomLevel'] : -1,
'allow_zoom' => true,
'allow_scrolling' => isset( $attrs['allowScrolling'] ) ? $attrs['allowScrolling'] : true,
'allow_dragging' => isset( $attrs['allowDragging'] ) ? $attrs['allowDragging'] : true,
'show_route_planner' => isset( $attrs['showRoute'] ) ? $attrs['showRoute'] : true,
'route_label' => Helper::get_settings( 'titles.route_label' ),
'show_category_filter' => isset( $attrs['showCategoryFilter'] ) ? $attrs['showCategoryFilter'] : false,
'show_marker_clustering' => isset( $attrs['markerClustering'] ) ? $attrs['markerClustering'] : true,
'show_infowindow' => isset( $attrs['defaultShowInfoWindow'] ) ? $attrs['defaultShowInfoWindow'] : true,
'show_radius' => isset( $attrs['showRadius'] ) ? $attrs['showRadius'] : true,
'show_nearest_location' => isset( $attrs['showNearest'] ) ? $attrs['showNearest'] : true,
'search_radius' => isset( $attrs['searchRadius'] ) ? $attrs['searchRadius'] : '10',
];
}
}

View File

@@ -0,0 +1,125 @@
<?php
/**
* The Yoast TOC Block Converter.
*
* @since 1.0.104
* @package RankMath
* @subpackage RankMath\Status
* @author Rank Math <support@rankmath.com>
*/
namespace RankMath\Tools;
use RankMath\Helpers\HTML;
defined( 'ABSPATH' ) || exit;
/**
* Yoast_TOC_Converter class.
*/
class Yoast_TOC_Converter {
/**
* Convert TOC blocks to Rank Math.
*
* @param array $block Block to convert.
*
* @return array
*/
public function convert( $block ) {
$exclude_headings = [];
if ( ! empty( $block['attrs']['maxHeadingLevel'] ) ) {
for ( $i = $block['attrs']['maxHeadingLevel'] + 1; $i <= 6; $i++ ) {
$exclude_headings[] = "h{$i}";
}
}
$new_block = [
'blockName' => 'rank-math/toc-block',
'attrs' => [
'title' => $this->get_toc_title( $block['innerHTML'] ),
'headings' => $this->get_headings( $block['innerHTML'] ),
'listStyle' => 'ul',
'titleWrapper' => 'h2',
'excludeHeadings' => $exclude_headings,
],
];
$new_block['innerContent'][] = $this->get_html( $block['innerHTML'] );
return $new_block;
}
/**
* Replace block in content.
*
* @param string $post_content Post content.
* @param array $blocks Blocks.
*
* @return string
*/
public function replace( $post_content, $blocks ) {
preg_match_all( '/<!-- wp:yoast-seo\/table-of-contents.*-->.*<!-- \/wp:yoast-seo\/table-of-contents -->/iUs', $post_content, $matches );
foreach ( $matches[0] as $index => $match ) {
$post_content = \str_replace( $match, $blocks[ $index ], $post_content );
}
return $post_content;
}
/**
* Extract headings from the block content.
*
* @param string $content Block content.
*
* @return array
*/
public function get_headings( $content ) {
preg_match_all( '|<a\s*href="([^"]+)"[^>]+>([^<]+)</a>|', $content, $matches );
if ( empty( $matches ) || empty( $matches[0] ) ) {
return [];
}
$headings = [];
foreach ( $matches[0] as $link ) {
$attrs = HTML::extract_attributes( $link );
$headings[] = [
'key' => uniqid( 'toc-' ),
'link' => $attrs['href'] ?? '',
'content' => wp_strip_all_tags( $link ),
'level' => $attrs['data-level'] ?? '',
'disable' => false,
];
}
return $headings;
}
/**
* Get TOC title.
*
* @param string $html Block HTML.
*
* @return string
*/
public function get_toc_title( $html ) {
preg_match( '#<h2.*?>(.*?)</h2>#i', $html, $found );
return ! empty( $found[1] ) ? $found[1] : '';
}
/**
* Generate HTML.
*
* @param string $html Block html.
*
* @return string
*/
private function get_html( $html ) {
$html = str_replace( 'wp-block-yoast-seo-table-of-contents yoast-table-of-contents', 'wp-block-rank-math-toc-block', $html );
$html = str_replace( '</h2><ul>', '</h2><nav><ul>', $html );
$html = str_replace( '</ul></div>', '</nav></ul></div>', $html );
$html = preg_replace( '/data-level="([^"]*)"/', '', $html );
return $html;
}
}