Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save mrfoxtalbot/ee2c8b35965c85ca122ee38e266699f1 to your computer and use it in GitHub Desktop.

Select an option

Save mrfoxtalbot/ee2c8b35965c85ca122ee38e266699f1 to your computer and use it in GitHub Desktop.
A plugin that replaces WP Bakery's visual composer column shortcodes with native Gutenbeg blocks (use at you own discretion since inner content might not be parsed as expected)- You can user the Code Snippets plugin to run this code.
<?php
/*
Plugin Name: VC Columns to Native Blocks
Description: Convierte [vc_row][vc_column]...[/vc_column][/vc_row] en bloques nativos de columnas, párrafos e imágenes.
Author: Álvaro Gómez
Version: 1.4.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class AG_VC_Columns_To_Native {
protected $last_report = null;
public function __construct() {
add_action( 'admin_menu', array( $this, 'register_tools_page' ) );
}
public function register_tools_page() {
add_management_page(
'Convert VC Columns',
'Convert VC Columns',
'manage_options',
'ag-vc-columns-to-native',
array( $this, 'render_tools_page' )
);
}
public function render_tools_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
if ( isset( $_POST['ag_vc_convert_nonce'] ) && wp_verify_nonce( $_POST['ag_vc_convert_nonce'], 'ag_vc_convert' ) ) {
$this->last_report = $this->run_conversion();
$this->print_report_notice();
}
echo '<div class="wrap">';
echo '<h1>Convert VC Columns to Native Blocks</h1>';
echo '<p>Este script buscará patrones [vc_row][vc_column]...[/vc_column][/vc_row] en el contenido y los convertirá a bloques nativos de columnas, párrafos e imágenes.</p>';
echo '<form method="post">';
wp_nonce_field( 'ag_vc_convert', 'ag_vc_convert_nonce' );
submit_button( 'Run conversion' );
echo '</form>';
if ( $this->last_report ) {
$this->print_detailed_report_table();
}
echo '</div>';
}
protected function print_report_notice() {
if ( ! $this->last_report ) {
return;
}
$r = $this->last_report;
echo '<div class="notice notice-success is-dismissible">';
echo '<p><strong>Conversion finished.</strong></p>';
echo '<p>';
echo 'Posts scanned: ' . intval( $r['scanned'] ) . '<br>';
echo 'Posts updated: ' . intval( $r['updated'] ) . '<br>';
echo 'VC row groups converted: ' . intval( $r['groups_converted'] ) . '<br>';
echo 'VC rows skipped (unparsed): ' . intval( $r['rows_skipped'] );
echo '</p>';
if ( ! empty( $r['updated_ids'] ) ) {
echo '<p>Updated post IDs: ' . esc_html( implode( ', ', $r['updated_ids'] ) ) . '</p>';
}
echo '</div>';
}
protected function print_detailed_report_table() {
$r = $this->last_report;
if ( empty( $r['details'] ) ) {
return;
}
echo '<h2>Details</h2>';
echo '<table class="widefat striped">';
echo '<thead><tr>';
echo '<th>Post ID</th>';
echo '<th>Title</th>';
echo '<th>Rows found</th>';
echo '<th>Rows converted</th>';
echo '<th>Rows skipped</th>';
echo '</tr></thead>';
echo '<tbody>';
foreach ( $r['details'] as $post_id => $info ) {
printf(
'<tr><td>%d</td><td>%s</td><td>%d</td><td>%d</td><td>%d</td></tr>',
$post_id,
esc_html( get_the_title( $post_id ) ),
intval( $info['rows_found'] ),
intval( $info['rows_converted'] ),
intval( $info['rows_skipped'] )
);
}
echo '</tbody>';
echo '</table>';
}
protected function run_conversion() {
global $wpdb;
$post_ids = $wpdb->get_col(
"SELECT ID FROM {$wpdb->posts}
WHERE post_type IN ('post','page')
AND post_status IN ('publish','draft','pending','future','private')
AND post_content LIKE '%[vc_row%'"
);
$report = array(
'scanned' => 0,
'updated' => 0,
'groups_converted' => 0,
'rows_skipped' => 0,
'updated_ids' => array(),
'details' => array(),
);
if ( empty( $post_ids ) ) {
return $report;
}
foreach ( $post_ids as $post_id ) {
$report['scanned']++;
$content = get_post_field( 'post_content', $post_id );
$original = $content;
$rows_found = 0;
$rows_converted = 0;
$rows_skipped = 0;
// Match each complete vc_row block.
$pattern = '/\[vc_row\b(.*?)\](.*?)\[\/vc_row\]/is';
if ( preg_match_all( $pattern, $content, $matches, PREG_SET_ORDER ) ) {
$rows_found = count( $matches );
foreach ( $matches as $match ) {
$full_match = $match[0];
$row_inner = $match[2];
$converted = $this->convert_row_to_columns_block( $row_inner );
if ( $converted === null ) {
$rows_skipped++;
continue;
}
$rows_converted++;
$content = str_replace( $full_match, $converted, $content );
}
}
if ( $content !== $original ) {
$updated_post = array(
'ID' => $post_id,
'post_content' => $content,
);
$result = wp_update_post( $updated_post, true );
if ( ! is_wp_error( $result ) ) {
$report['updated']++;
$report['groups_converted'] += $rows_converted;
$report['rows_skipped'] += $rows_skipped;
$report['updated_ids'][] = $post_id;
$report['details'][ $post_id ] = array(
'rows_found' => $rows_found,
'rows_converted' => $rows_converted,
'rows_skipped' => $rows_skipped,
);
}
}
}
return $report;
}
/**
* Convierte el contenido interno de un [vc_row] en un bloque <!-- wp:columns -->...
* Devuelve null si no puede parsear columnas.
*/
protected function convert_row_to_columns_block( $row_inner ) {
// Capturar todas las columnas de primer nivel dentro del row.
$pattern = '/\[vc_column\b([^]]*)\](.*?)\[\/vc_column\]/is';
if ( ! preg_match_all( $pattern, $row_inner, $cols, PREG_SET_ORDER ) ) {
return null;
}
$columns_html = array();
foreach ( $cols as $col_match ) {
$col_attrs = $col_match[1];
$col_inner = $col_match[2];
// 1) Convertir [vc_column_text] a párrafos (dentro, <img> a bloques image).
$col_inner = $this->convert_vc_column_text_to_paragraph( $col_inner );
// 2) Convertir <img> sueltos en la columna en bloques image (caso como el que comentas).
$col_inner = $this->convert_imgs_to_image_blocks( $col_inner );
$columns_html[] =
"<!-- wp:column -->\n" .
'<div class="wp-block-column">' . $col_inner . "</div>\n" .
"<!-- /wp:column -->";
}
if ( empty( $columns_html ) ) {
return null;
}
$columns_block =
"<!-- wp:columns -->\n" .
'<div class="wp-block-columns">' . "\n\n" .
implode( "\n\n", $columns_html ) . "\n\n" .
"</div>\n" .
"<!-- /wp:columns -->";
return $columns_block;
}
/**
* Reemplaza [vc_column_text]$content[/vc_column_text] por
* <!-- wp:paragraph -->$content<!-- /wp:paragraph --> preservando $content,
* y dentro de ese contenido convierte <img> en bloques de imagen.
*/
protected function convert_vc_column_text_to_paragraph( $content ) {
$pattern = '/\[vc_column_text\b[^\]]*\](.*?)\[\/vc_column_text\]/is';
$callback = function ( $m ) {
$inner = $m[1];
// Primero, convertir cualquier <img ...> dentro de $inner a bloques de imagen.
$inner = $this->convert_imgs_to_image_blocks( $inner );
return "<!-- wp:paragraph -->" . $inner . "<!-- /wp:paragraph -->";
};
return preg_replace_callback( $pattern, $callback, $content );
}
/**
* Convierte <img> en bloques de imagen:
*
* <img src="..." class="... wp-image-8 ..." ...>
*
* pasa a:
*
* <!-- wp:image {"id":8,"sizeSlug":"large","linkDestination":"none"} -->
* <figure class="wp-block-image size-large"><img src="..." alt="" class="wp-image-8"/></figure>
* <!-- /wp:image -->
*
* El ID se toma del número en la clase wp-image-N.
*/
protected function convert_imgs_to_image_blocks( $html ) {
$pattern = '/<img\b([^>]*)>/i';
$callback = function ( $m ) {
$attrs_str = $m[1];
// Extraer src.
$src = '';
if ( preg_match( '/\bsrc\s*=\s*"([^"]*)"/i', $attrs_str, $m_src ) ) {
$src = $m_src[1];
}
// Extraer clase completa (para conservar wp-image-N).
$class_attr = '';
if ( preg_match( '/\bclass\s*=\s*"([^"]*)"/i', $attrs_str, $m_class ) ) {
$class_attr = $m_class[1];
}
// Extraer ID de wp-image-N.
$id = 0;
if ( preg_match( '/wp-image-([0-9]+)/', $class_attr, $m_id ) ) {
$id = (int) $m_id[1];
}
if ( empty( $src ) || $id <= 0 ) {
// Si no tenemos src o id válido, no tocamos el <img>.
return $m[0];
}
// Asegurar que la clase wp-image-N está presente (y sólo una vez).
$classes = preg_split( '/\s+/', trim( $class_attr ) );
if ( ! in_array( 'wp-image-' . $id, $classes, true ) ) {
$classes[] = 'wp-image-' . $id;
}
$classes = array_filter( $classes );
$class_final = implode( ' ', $classes );
// Construir atributos del comentario del bloque.
$comment_attrs = sprintf(
'{"id":%d,"sizeSlug":"large","linkDestination":"none"}',
$id
);
// Figure estándar de bloque de imagen.
$img_tag = sprintf(
'<figure class="wp-block-image size-large"><img src="%1$s" alt="" class="%2$s"/></figure>',
esc_url( $src ),
esc_attr( $class_final )
);
$block = '<!-- wp:image ' . $comment_attrs . ' -->' . "\n";
$block .= $img_tag . "\n";
$block .= '<!-- /wp:image -->';
return $block;
};
return preg_replace_callback( $pattern, $callback, $html );
}
}
new AG_VC_Columns_To_Native();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment