First Commit

This commit is contained in:
2026-03-18 14:21:32 -07:00
parent 8712bbcc1a
commit 1cf5bbeeb0
12 changed files with 4185 additions and 0 deletions
+785
View File
@@ -0,0 +1,785 @@
<?php
/**
* Hooks Class
*
* Handles WordPress hooks for automatic cache regeneration.
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
class WP_To_HTML_Hooks {
/**
* Single instance
*/
private static $instance = null;
/**
* Get singleton instance
*/
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
private function __construct() {
$this->init_hooks();
}
/**
* Initialize all hooks
*/
private function init_hooks() {
// Post/page update hooks
add_action('save_post', array($this, 'on_save_post'), 20, 3);
add_action('delete_post', array($this, 'on_delete_post'), 10);
add_action('trash_post', array($this, 'on_delete_post'), 10);
add_action('transition_post_status', array($this, 'on_status_change'), 10, 3);
// Comment hooks
add_action('comment_post', array($this, 'on_comment_change'), 10, 2);
add_action('edit_comment', array($this, 'on_comment_edit'), 10);
add_action('delete_comment', array($this, 'on_comment_delete'), 10);
add_action('wp_set_comment_status', array($this, 'on_comment_status_change'), 10, 2);
// Global change hooks - clear entire cache
add_action('switch_theme', array($this, 'on_global_change'));
add_action('customize_save_after', array($this, 'on_global_change'));
add_action('update_option_sidebars_widgets', array($this, 'on_global_change'));
add_action('wp_update_nav_menu', array($this, 'on_global_change'));
// Plugin/theme update hooks - regenerate after other plugins update, skip for this plugin
add_action('upgrader_process_complete', array($this, 'on_plugin_update'), 10, 2);
// WooCommerce hooks (if active)
add_action('woocommerce_product_set_stock', array($this, 'on_woo_product_change'));
add_action('woocommerce_variation_set_stock', array($this, 'on_woo_product_change'));
// Admin bar indicator for cache status
add_action('admin_bar_menu', array($this, 'add_admin_bar_indicator'), 100);
add_action('wp_head', array($this, 'admin_bar_styles'));
add_action('admin_head', array($this, 'admin_bar_styles'));
add_action('wp_footer', array($this, 'admin_bar_scripts'));
add_action('admin_footer', array($this, 'admin_bar_scripts'));
// AJAX handlers for admin bar actions
add_action('wp_ajax_wp_to_html_adminbar_clear', array($this, 'ajax_adminbar_clear'));
add_action('wp_ajax_wp_to_html_adminbar_regenerate', array($this, 'ajax_adminbar_regenerate'));
add_action('wp_ajax_wp_to_html_adminbar_regenerate_page', array($this, 'ajax_adminbar_regenerate_page'));
add_action('wp_ajax_wp_to_html_adminbar_clear_regenerate', array($this, 'ajax_adminbar_clear_regenerate'));
}
/**
* Handle post save
*
* @param int $post_id Post ID
* @param WP_Post $post Post object
* @param bool $update Whether this is an update
*/
public function on_save_post($post_id, $post, $update) {
// Don't process revisions
if (wp_is_post_revision($post_id)) {
return;
}
// Don't process autosaves
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
// Only process pages and posts (and custom post types that are public)
$allowed_types = $this->get_allowed_post_types();
if (!in_array($post->post_type, $allowed_types)) {
return;
}
$generator = WP_To_HTML_Generator::get_instance();
// If the post is published, regenerate the cache
if ($post->post_status === 'publish') {
// Schedule regeneration to run after all other save operations
wp_schedule_single_event(time() + 5, 'wp_to_html_regenerate_post', array($post_id));
} else {
// Post is not published, delete the cache
$generator->delete_cache($post_id);
}
}
/**
* Handle post deletion
*
* @param int $post_id Post ID
*/
public function on_delete_post($post_id) {
$generator = WP_To_HTML_Generator::get_instance();
$generator->delete_cache($post_id);
}
/**
* Handle post status transitions
*
* @param string $new_status New status
* @param string $old_status Old status
* @param WP_Post $post Post object
*/
public function on_status_change($new_status, $old_status, $post) {
$generator = WP_To_HTML_Generator::get_instance();
// If unpublishing, delete cache
if ($old_status === 'publish' && $new_status !== 'publish') {
$generator->delete_cache($post->ID);
}
// If publishing, generate cache
if ($new_status === 'publish' && $old_status !== 'publish') {
wp_schedule_single_event(time() + 5, 'wp_to_html_regenerate_post', array($post->ID));
}
}
/**
* Handle new comment
*
* @param int $comment_id Comment ID
* @param int|string $comment_approved Approval status
*/
public function on_comment_change($comment_id, $comment_approved) {
$comment = get_comment($comment_id);
if ($comment) {
$generator = WP_To_HTML_Generator::get_instance();
$generator->delete_cache($comment->comment_post_ID);
}
}
/**
* Handle comment edit
*
* @param int $comment_id Comment ID
*/
public function on_comment_edit($comment_id) {
$comment = get_comment($comment_id);
if ($comment) {
$generator = WP_To_HTML_Generator::get_instance();
$generator->delete_cache($comment->comment_post_ID);
}
}
/**
* Handle comment deletion
*
* @param int $comment_id Comment ID
*/
public function on_comment_delete($comment_id) {
$comment = get_comment($comment_id);
if ($comment) {
$generator = WP_To_HTML_Generator::get_instance();
$generator->delete_cache($comment->comment_post_ID);
}
}
/**
* Handle comment status change
*
* @param int $comment_id Comment ID
* @param string $status New status
*/
public function on_comment_status_change($comment_id, $status) {
$comment = get_comment($comment_id);
if ($comment) {
$generator = WP_To_HTML_Generator::get_instance();
$generator->delete_cache($comment->comment_post_ID);
}
}
/**
* Handle global changes that affect all pages
*/
public function on_global_change() {
$generator = WP_To_HTML_Generator::get_instance();
$generator->clear_all_cache();
}
/**
* Handle plugin/theme updates
*
* Regenerates cache when other plugins are updated, but skips when this plugin is updated
*
* @param WP_Upgrader $upgrader Upgrader instance
* @param array $options Update options including type and plugins
*/
public function on_plugin_update($upgrader, $options) {
// Only handle plugin updates
if (!isset($options['type']) || $options['type'] !== 'plugin') {
return;
}
// Get the list of plugins being updated
$plugins = array();
if (isset($options['plugins'])) {
$plugins = (array) $options['plugins'];
} elseif (isset($options['plugin'])) {
$plugins = array($options['plugin']);
}
// Check if our plugin is in the update list
$our_plugin = 'WP-to-HTML/wp-to-html.php';
$our_plugin_alt = plugin_basename(WP_TO_HTML_PLUGIN_DIR . 'wp-to-html.php');
foreach ($plugins as $plugin) {
if ($plugin === $our_plugin || $plugin === $our_plugin_alt) {
// This plugin is being updated - don't clear or regenerate
return;
}
}
// Other plugins are being updated - clear cache and regenerate
$generator = WP_To_HTML_Generator::get_instance();
$generator->clear_all_cache();
// Set pending status for plugin update regeneration
WP_To_HTML_Cron::set_regeneration_status(array(
'status' => 'pending',
'source' => 'plugin_update',
'started' => time(),
'message' => __('Waiting for plugin update to complete...', 'wp-to-html'),
));
// Schedule regeneration to run after the update completes
wp_schedule_single_event(time() + 10, 'wp_to_html_regenerate_all', array('plugin_update'));
}
/**
* Handle WooCommerce product changes
*
* @param WC_Product $product Product object
*/
public function on_woo_product_change($product) {
// Products are excluded by default, but clear cache just in case
if (is_object($product) && method_exists($product, 'get_id')) {
$generator = WP_To_HTML_Generator::get_instance();
$generator->delete_cache($product->get_id());
}
}
/**
* Get allowed post types for static generation
*
* @return array List of post type slugs
*/
private function get_allowed_post_types() {
$post_types = get_post_types(array(
'public' => true,
), 'names');
// Remove excluded types
$excluded = array('attachment', 'product', 'product_variation');
return array_diff($post_types, $excluded);
}
/**
* Add admin bar indicator for cache status
*
* @param WP_Admin_Bar $admin_bar Admin bar instance
*/
public function add_admin_bar_indicator($admin_bar) {
// Only show for users who can manage options
if (!current_user_can('manage_options')) {
return;
}
$settings = WP_To_HTML::get_settings();
$generator = WP_To_HTML_Generator::get_instance();
// Determine if we're on the frontend viewing a singular page
$is_frontend_singular = !is_admin() && is_singular();
if ($is_frontend_singular) {
// Frontend: Show page-specific status and regenerate option
$post_id = get_the_ID();
if (!$post_id) {
return;
}
$post = get_post($post_id);
// Determine cache status
$exclusion_reason = $generator->should_exclude($post);
$has_cache = $generator->has_cache($post_id);
if (!$settings['enabled']) {
$status = 'disabled';
$icon = '⏸';
$title = __('WP-to-HTML: Disabled', 'wp-to-html');
$color = '#72777c';
} elseif ($exclusion_reason) {
$status = 'excluded';
$icon = '⚠';
$title = sprintf(__('WP-to-HTML: Excluded - %s', 'wp-to-html'), $exclusion_reason);
$color = '#dba617';
} elseif ($has_cache) {
$status = 'cached';
$icon = '⚡';
$title = __('WP-to-HTML: Cached (Static HTML)', 'wp-to-html');
$color = '#00a32a';
} else {
$status = 'not-cached';
$icon = '○';
$title = __('WP-to-HTML: Not Cached Yet', 'wp-to-html');
$color = '#72777c';
}
$admin_bar->add_node(array(
'id' => 'wp-to-html-status',
'title' => '<span class="wp-to-html-indicator" style="color: ' . esc_attr($color) . ';" title="' . esc_attr($title) . '">' . $icon . '</span> <span class="wp-to-html-label">' . esc_html($title) . '</span>',
'href' => admin_url('options-general.php?page=wp-to-html'),
'meta' => array(
'class' => 'wp-to-html-admin-bar-item wp-to-html-status-' . $status,
),
));
// Add submenu items for frontend
$admin_bar->add_node(array(
'id' => 'wp-to-html-settings',
'parent' => 'wp-to-html-status',
'title' => __('⚙️ Settings', 'wp-to-html'),
'href' => admin_url('options-general.php?page=wp-to-html'),
));
// Only show regenerate if not excluded
if (!$exclusion_reason && $settings['enabled']) {
$admin_bar->add_node(array(
'id' => 'wp-to-html-regenerate-page',
'parent' => 'wp-to-html-status',
'title' => __('🔄 Regenerate This Page', 'wp-to-html'),
'href' => '#',
'meta' => array(
'class' => 'wp-to-html-action-regenerate-page',
'onclick' => 'return false;',
'data-post-id' => $post_id,
),
));
}
} else if (is_admin()) {
// Backend: Show global actions
$stats = $generator->get_cache_stats();
$icon = '⚡';
// Don't show "(0 cached)" - only show count if there are cached pages
if ($stats['total_files'] > 0) {
$title = sprintf(__('WP-to-HTML (%d cached)', 'wp-to-html'), $stats['total_files']);
} else {
$title = __('WP-to-HTML', 'wp-to-html');
}
$admin_bar->add_node(array(
'id' => 'wp-to-html-status',
'title' => '<span class="wp-to-html-indicator">' . $icon . '</span> <span class="wp-to-html-label">' . esc_html($title) . '</span>',
'href' => admin_url('options-general.php?page=wp-to-html'),
'meta' => array(
'class' => 'wp-to-html-admin-bar-item',
),
));
// Add submenu items for backend
$admin_bar->add_node(array(
'id' => 'wp-to-html-settings',
'parent' => 'wp-to-html-status',
'title' => __('⚙️ Settings', 'wp-to-html'),
'href' => admin_url('options-general.php?page=wp-to-html'),
));
$admin_bar->add_node(array(
'id' => 'wp-to-html-clear',
'parent' => 'wp-to-html-status',
'title' => __('🗑️ Clear All Cache', 'wp-to-html'),
'href' => '#',
'meta' => array(
'class' => 'wp-to-html-action-clear',
'onclick' => 'return false;',
),
));
$admin_bar->add_node(array(
'id' => 'wp-to-html-regenerate',
'parent' => 'wp-to-html-status',
'title' => __('🔄 Regenerate All', 'wp-to-html'),
'href' => '#',
'meta' => array(
'class' => 'wp-to-html-action-regenerate',
'onclick' => 'return false;',
),
));
$admin_bar->add_node(array(
'id' => 'wp-to-html-clear-regenerate',
'parent' => 'wp-to-html-status',
'title' => __('✨ Clear & Regenerate All', 'wp-to-html'),
'href' => '#',
'meta' => array(
'class' => 'wp-to-html-action-clear-regenerate',
'onclick' => 'return false;',
),
));
}
}
/**
* Add admin bar styles for the cache indicator
*/
public function admin_bar_styles() {
if (!is_admin_bar_showing() || !current_user_can('manage_options')) {
return;
}
?>
<style>
#wpadminbar .wp-to-html-indicator {
font-size: 16px;
margin-right: 4px;
}
#wpadminbar .wp-to-html-label {
font-size: 13px;
}
@media screen and (max-width: 782px) {
#wpadminbar .wp-to-html-label {
display: none;
}
}
#wpadminbar .wp-to-html-action-clear:hover,
#wpadminbar .wp-to-html-action-regenerate:hover {
cursor: pointer;
}
#wpadminbar .wp-to-html-processing .ab-item {
opacity: 0.6;
pointer-events: none;
}
</style>
<?php
}
/**
* Add admin bar scripts for AJAX actions
*/
public function admin_bar_scripts() {
if (!is_admin_bar_showing() || !current_user_can('manage_options')) {
return;
}
?>
<script>
(function() {
document.addEventListener('DOMContentLoaded', function() {
var clearBtn = document.querySelector('#wp-admin-bar-wp-to-html-clear .ab-item');
var regenBtn = document.querySelector('#wp-admin-bar-wp-to-html-regenerate .ab-item');
var regenPageBtn = document.querySelector('#wp-admin-bar-wp-to-html-regenerate-page .ab-item');
var nonce = '<?php echo wp_create_nonce('wp_to_html_adminbar'); ?>';
var ajaxUrl = '<?php echo admin_url('admin-ajax.php'); ?>';
if (clearBtn) {
clearBtn.addEventListener('click', function(e) {
e.preventDefault();
if (!confirm('<?php echo esc_js(__('Are you sure you want to clear all cached HTML files?', 'wp-to-html')); ?>')) {
return;
}
var parent = this.parentElement;
parent.classList.add('wp-to-html-processing');
this.textContent = '<?php echo esc_js(__('Clearing...', 'wp-to-html')); ?>';
fetch(ajaxUrl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=wp_to_html_adminbar_clear&nonce=' + nonce
})
.then(function(response) { return response.json(); })
.then(function(data) {
parent.classList.remove('wp-to-html-processing');
if (data.success) {
clearBtn.textContent = '<?php echo esc_js(__('✓ Cache Cleared!', 'wp-to-html')); ?>';
setTimeout(function() { location.reload(); }, 1000);
} else {
clearBtn.textContent = '<?php echo esc_js(__('Error occurred', 'wp-to-html')); ?>';
}
})
.catch(function() {
parent.classList.remove('wp-to-html-processing');
clearBtn.textContent = '<?php echo esc_js(__('Error occurred', 'wp-to-html')); ?>';
});
});
}
if (regenBtn) {
regenBtn.addEventListener('click', function(e) {
e.preventDefault();
if (!confirm('<?php echo esc_js(__('This will regenerate all eligible pages. Continue?', 'wp-to-html')); ?>')) {
return;
}
var parent = this.parentElement;
parent.classList.add('wp-to-html-processing');
this.textContent = '<?php echo esc_js(__('Regenerating...', 'wp-to-html')); ?>';
fetch(ajaxUrl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=wp_to_html_adminbar_regenerate&nonce=' + nonce
})
.then(function(response) { return response.json(); })
.then(function(data) {
parent.classList.remove('wp-to-html-processing');
if (data.success) {
regenBtn.textContent = '<?php echo esc_js(__('✓ Regenerated!', 'wp-to-html')); ?> (' + data.data.converted + ' pages)';
setTimeout(function() { location.reload(); }, 1500);
} else {
regenBtn.textContent = '<?php echo esc_js(__('Error occurred', 'wp-to-html')); ?>';
}
})
.catch(function() {
parent.classList.remove('wp-to-html-processing');
regenBtn.textContent = '<?php echo esc_js(__('Error occurred', 'wp-to-html')); ?>';
});
});
}
if (regenPageBtn) {
regenPageBtn.addEventListener('click', function(e) {
e.preventDefault();
var parent = this.parentElement;
var postId = parent.querySelector('[data-post-id]');
if (!postId) {
// Try to get post ID from the li element's data attribute
postId = document.querySelector('#wp-admin-bar-wp-to-html-regenerate-page');
}
// Extract post ID - it's stored in the meta
var postIdValue = postId ? postId.getAttribute('data-post-id') : null;
if (!postIdValue) {
// Fallback: get from current page URL or use a hidden input
postIdValue = '<?php echo is_singular() ? get_the_ID() : 0; ?>';
}
parent.classList.add('wp-to-html-processing');
this.textContent = '<?php echo esc_js(__('Regenerating...', 'wp-to-html')); ?>';
fetch(ajaxUrl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=wp_to_html_adminbar_regenerate_page&nonce=' + nonce + '&post_id=' + postIdValue
})
.then(function(response) { return response.json(); })
.then(function(data) {
parent.classList.remove('wp-to-html-processing');
if (data.success) {
regenPageBtn.textContent = '<?php echo esc_js(__('✓ Page Regenerated!', 'wp-to-html')); ?>';
setTimeout(function() { location.reload(); }, 1000);
} else {
regenPageBtn.textContent = data.data || '<?php echo esc_js(__('Error occurred', 'wp-to-html')); ?>';
}
})
.catch(function() {
parent.classList.remove('wp-to-html-processing');
regenPageBtn.textContent = '<?php echo esc_js(__('Error occurred', 'wp-to-html')); ?>';
});
});
}
// Clear & Regenerate All button
var clearRegenBtn = document.querySelector('#wp-admin-bar-wp-to-html-clear-regenerate .ab-item');
if (clearRegenBtn) {
clearRegenBtn.addEventListener('click', function(e) {
e.preventDefault();
if (!confirm('<?php echo esc_js(__('This will clear all cache and regenerate all eligible pages. Continue?', 'wp-to-html')); ?>')) {
return;
}
var parent = this.parentElement;
parent.classList.add('wp-to-html-processing');
this.textContent = '<?php echo esc_js(__('Clearing and Regenerating...', 'wp-to-html')); ?>';
fetch(ajaxUrl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=wp_to_html_adminbar_clear_regenerate&nonce=' + nonce
})
.then(function(response) { return response.json(); })
.then(function(data) {
parent.classList.remove('wp-to-html-processing');
if (data.success) {
clearRegenBtn.textContent = '<?php echo esc_js(__('✓ Done!', 'wp-to-html')); ?> (' + data.data.converted + ' pages)';
setTimeout(function() { location.reload(); }, 1500);
} else {
clearRegenBtn.textContent = '<?php echo esc_js(__('Error occurred', 'wp-to-html')); ?>';
}
})
.catch(function() {
parent.classList.remove('wp-to-html-processing');
clearRegenBtn.textContent = '<?php echo esc_js(__('Error occurred', 'wp-to-html')); ?>';
});
});
}
});
})();
</script>
<?php
}
/**
* AJAX handler for clearing cache from admin bar
*/
public function ajax_adminbar_clear() {
check_ajax_referer('wp_to_html_adminbar', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(__('Permission denied', 'wp-to-html'));
}
$generator = WP_To_HTML_Generator::get_instance();
$generator->clear_all_cache();
wp_send_json_success(array(
'message' => __('Cache cleared successfully', 'wp-to-html'),
));
}
/**
* AJAX handler for regenerating all from admin bar
*/
public function ajax_adminbar_regenerate() {
check_ajax_referer('wp_to_html_adminbar', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(__('Permission denied', 'wp-to-html'));
}
// Set initial status
WP_To_HTML_Cron::set_regeneration_status(array(
'status' => 'running',
'source' => 'admin_bar',
'started' => time(),
'total' => 0,
'processed' => 0,
'converted' => 0,
'skipped' => 0,
'errors' => 0,
));
$bulk = WP_To_HTML_Bulk::get_instance();
$results = $bulk->convert_all();
// Set complete status
WP_To_HTML_Cron::set_regeneration_status(array(
'status' => 'complete',
'source' => 'admin_bar',
'completed' => time(),
'total' => $results['total'],
'processed' => $results['total'],
'converted' => $results['converted'],
'skipped' => $results['skipped'],
'errors' => $results['errors'],
));
wp_send_json_success(array(
'message' => __('Regeneration complete', 'wp-to-html'),
'converted' => $results['converted'],
'skipped' => $results['skipped'],
'errors' => $results['errors'],
));
}
/**
* AJAX handler for regenerating a single page from admin bar
*/
public function ajax_adminbar_regenerate_page() {
check_ajax_referer('wp_to_html_adminbar', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(__('Permission denied', 'wp-to-html'));
}
$post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0;
if (!$post_id) {
wp_send_json_error(__('Invalid post ID', 'wp-to-html'));
}
$generator = WP_To_HTML_Generator::get_instance();
$result = $generator->generate($post_id);
if (is_wp_error($result)) {
wp_send_json_error($result->get_error_message());
}
wp_send_json_success(array(
'message' => __('Page regenerated successfully', 'wp-to-html'),
));
}
/**
* AJAX handler for clearing cache and regenerating all from admin bar
*/
public function ajax_adminbar_clear_regenerate() {
check_ajax_referer('wp_to_html_adminbar', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(__('Permission denied', 'wp-to-html'));
}
// First clear the cache
$generator = WP_To_HTML_Generator::get_instance();
$generator->clear_all_cache();
// Then regenerate all
$bulk = WP_To_HTML_Bulk::get_instance();
$results = $bulk->convert_all();
wp_send_json_success(array(
'message' => __('Cache cleared and regeneration complete', 'wp-to-html'),
'converted' => $results['converted'],
'skipped' => $results['skipped'],
'errors' => $results['errors'],
));
}
}
// Register the scheduled event handlers
add_action('wp_to_html_regenerate_post', function($post_id) {
$generator = WP_To_HTML_Generator::get_instance();
$generator->generate($post_id);
});
// Register handler for regenerating all pages after plugin updates
add_action('wp_to_html_regenerate_all', function($source = 'plugin_update') {
// Set running status
WP_To_HTML_Cron::set_regeneration_status(array(
'status' => 'running',
'source' => $source,
'started' => time(),
'total' => 0,
'processed' => 0,
'converted' => 0,
'skipped' => 0,
'errors' => 0,
));
$bulk = WP_To_HTML_Bulk::get_instance();
$results = $bulk->convert_all();
// Set complete status
WP_To_HTML_Cron::set_regeneration_status(array(
'status' => 'complete',
'source' => $source,
'completed' => time(),
'total' => $results['total'],
'processed' => $results['total'],
'converted' => $results['converted'],
'skipped' => $results['skipped'],
'errors' => $results['errors'],
));
});