Hi @charlie,
An update to yesterday evening’s reply. I have added the GLOBALS
line you sent in your last reply. It appears to export the HTML correctly for the first time, so the ACF fields are being output. Thank you! The bulk plugin has also been rewritten using your update. There is a small duplication of data in the output (more details in Secure Note), though it is something I can live with.
The working function and plugin code is as below.
FUNCTION
// EXPORT AS HTML
// On Document save, update post meta field _cs_built_html with an HTML copy of the post
add_filter("cs_save_document", function($doc) {
try {
ob_start();
// Needed For styling
do_action("wp_enqueue_scripts");
// Find document and renderContentFromDocument
$GLOBALS['post'] = get_post($doc->id());
$resolver = cornerstone("Resolver");
$docDB = $resolver->getDocument( $doc->id() );
$html = cornerstone("Resolver")->renderContentFromDocument($docDB);
// Fetch ACF fields and append to HTML
$fields = get_fields($doc->id());
if ($fields) {
error_log("ACF fields found: " . print_r($fields, true));
foreach ($fields as $name => $value) {
if($name == 'bird_animal_description') {
$html .= do_shortcode($value);
} else {
$html .= $value;
}
}
} else {
error_log("No ACF fields found for document with ID: " . $doc->id());
}
$html .= ob_get_clean();
// Attributes to remove
$attributes = [
'class', 'style', 'data-x-effect', 'data-x-slide-container', 'loading', 'width', 'height',
'aria-hidden', 'tabindex', 'data-x-effect-provider', 'data-x-slide-context', 'data-x-slide',
'aria-expanded', 'id', 'role', 'aria-selected', 'aria-controls', 'data-x-toggle', 'data-x-toggleable',
'data-x-toggle-collapse', 'aria-labelledby'
];
// Remove attributes
foreach ($attributes as $attribute) {
$html = preg_replace('/\s*' . $attribute . '\s*=\s*".*?"/i', '', $html);
}
// Remove content within certain tags
$tags = ['style', 'iframe', 'script', 'i', 'blockquote'];
foreach ($tags as $tag) {
$html = preg_replace('/<' . $tag . '\b[^>]*>.*?<\/' . $tag . '>/is', '', $html);
}
// Remove span tags
$html = preg_replace('~</?span[^>]*>~i', '', $html);
// Convert special characters to ASCII
$html = iconv('UTF-8', 'ASCII//IGNORE', $html);
// Remove all div tags
$html = preg_replace('~</?div[^>]*>~i', '', $html);
// Update post meta with HTML
update_post_meta($doc->id(), '_cs_built_html', $html);
} catch(\Throwable $e) {
trigger_error("Error with document HTML post meta saving : " . $e->getMessage());
}
});
PLUGIN
<?php
/**
* Plugin Name: Cornerstone Bulk HTML Generator
* Description: This is a custom plugin to update all posts and generate HTML for Cornerstone.
* Version: 1.0
* Author: White Media
* Author URI: https://www.whitemedia.uk/
*/
// Ensure direct access is blocked
defined('ABSPATH') || exit;
// Add admin menu
add_action('admin_menu', 'bulk_html_generator_menu');
function bulk_html_generator_menu() {
add_menu_page('Bulk HTML Generator', 'Bulk HTML Generator', 'manage_options', 'custom-post-processor', 'bulk_html_generator_page', 'dashicons-hammer', 6);
}
// Add AJAX handler
add_action('wp_ajax_process_posts', 'ajax_process_posts');
function ajax_process_posts() {
$selected_post_type = $_POST['post_type'];
$processed_count = 0;
$args = [
'post_type' => $selected_post_type,
'post_status' => 'publish',
'posts_per_page' => -1
];
$query = new WP_Query($args);
while($query->have_posts()) {
$query->the_post();
$post_id = get_the_ID();
process_post($post_id);
$processed_count++;
}
wp_reset_postdata();
echo json_encode(['processed' => $processed_count]);
wp_die(); // Use wp_die() instead of die() for AJAX in WordPress
}
function bulk_html_generator_page() {
$post_types = get_post_types(['public' => true], 'names');
$selected_post_type = isset($_POST['post_type']) ? $_POST['post_type'] : '';
$posts_count = $selected_post_type ? wp_count_posts($selected_post_type)->publish : 0;
echo '<div class="wrap">';
echo '<h1>Bulk HTML Generator</h1>';
echo '<script>
function startProcessing() {
document.getElementById("startProcessingBtn").style.display = "none";
document.getElementById("processingStatus").style.display = "block";
var postData = new FormData();
postData.append("action", "process_posts");
postData.append("post_type", document.forms[0].post_type.value);
fetch(ajaxurl, {
method: "POST",
body: postData
})
.then(response => response.json())
.then(data => {
document.getElementById("processingStatus").innerText = "Processed " + data.processed + " records.";
});
}
</script>';
echo '<form method="post">';
echo '<label for="post_type">Select Post Type: </label>';
echo '<select name="post_type" onchange="this.form.submit()">';
echo '<option value="">-- Select --</option>';
foreach($post_types as $post_type) {
$selected = $selected_post_type == $post_type ? 'selected' : '';
echo '<option value="' . $post_type . '" ' . $selected . '>' . $post_type . '</option>';
}
echo '</select>';
echo '<p>Total Posts: ' . $posts_count . '</p>';
if($selected_post_type) {
echo '<input type="button" class="button button-primary" value="Start Processing" id="startProcessingBtn" onclick="startProcessing();">';
}
echo '</form>';
echo '<p id="processingStatus" style="display: none;">Processing... <span class="spinner is-active" style="float:none;"></span></p>';
echo '</div>';
}
function process_post($post_id) {
try {
ob_start();
// Needed For styling
do_action("wp_enqueue_scripts");
// Find document and renderContentFromDocument
$GLOBALS['post'] = get_post($post_id);
$resolver = cornerstone("Resolver");
$docDB = $resolver->getDocument($post_id);
$html = cornerstone("Resolver")->renderContentFromDocument($docDB);
// Fetch ACF fields and append to HTML
$fields = get_fields($post_id);
if ($fields) {
error_log("ACF fields found: " . print_r($fields, true));
foreach ($fields as $name => $value) {
if ($name == 'bird_animal_description') {
$html .= do_shortcode($value);
} else {
$html .= $value;
}
}
} else {
error_log("No ACF fields found for document with ID: " . $post_id);
}
$html .= ob_get_clean();
// Attributes to remove
$attributes = [
'class', 'style', 'data-x-effect', 'data-x-slide-container', 'loading', 'width', 'height',
'aria-hidden', 'tabindex', 'data-x-effect-provider', 'data-x-slide-context', 'data-x-slide',
'aria-expanded', 'id', 'role', 'aria-selected', 'aria-controls', 'data-x-toggle', 'data-x-toggleable',
'data-x-toggle-collapse', 'aria-labelledby'
];
// Remove attributes
foreach ($attributes as $attribute) {
$html = preg_replace('/\s*' . $attribute . '\s*=\s*".*?"/i', '', $html);
}
// Remove content within certain tags
$tags = ['style', 'iframe', 'script', 'i', 'blockquote'];
foreach ($tags as $tag) {
$html = preg_replace('/<' . $tag . '\b[^>]*>.*?<\/' . $tag . '>/is', '', $html);
}
// Remove span tags
$html = preg_replace('~</?span[^>]*>~i', '', $html);
// Convert special characters to ASCII
$html = iconv('UTF-8', 'ASCII//IGNORE', $html);
// Remove all div tags
$html = preg_replace('~</?div[^>]*>~i', '', $html);
// Update post meta with HTML
update_post_meta($post_id, '_cs_built_html', $html);
} catch (\Throwable $e) {
trigger_error("Error with document HTML post meta saving : " . $e->getMessage());
}
}
// Optionally, enqueue scripts and styles. This example just sets up the action hook.
add_action('admin_enqueue_scripts', 'bulk_html_generator_enqueue');
function bulk_html_generator_enqueue($hook) {
if ('toplevel_page_custom-post-processor' !== $hook) {
return;
}
// Enqueue your styles and scripts here
// wp_enqueue_style(...)
// wp_enqueue_script(...)
}
// Activation and Deactivation hooks
register_activation_hook(__FILE__, 'bulk_html_generator_activate');
register_deactivation_hook(__FILE__, 'bulk_html_generator_deactivate');
function bulk_html_generator_activate() {
// Code to run when the plugin is activated
}
function bulk_html_generator_deactivate() {
// Code to run when the plugin is deactivated
}
Thanks for all your help!!