Files
2026-03-18 14:21:32 -07:00

681 lines
26 KiB
PHP

<?php
/**
* Admin Class
*
* Handles the admin settings page and meta box for manual exclusion.
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
class WP_To_HTML_Admin {
/**
* 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() {
add_action('admin_menu', array($this, 'add_admin_menu'));
add_action('admin_init', array($this, 'register_settings'));
add_action('add_meta_boxes', array($this, 'add_meta_box'));
add_action('save_post', array($this, 'save_meta_box'), 10, 2);
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets'));
// AJAX endpoint for regeneration status
add_action('wp_ajax_wp_to_html_get_status', array($this, 'ajax_get_status'));
}
/**
* Add admin menu
*/
public function add_admin_menu() {
add_options_page(
__('WP to HTML Settings', 'wp-to-html'),
__('WP to HTML', 'wp-to-html'),
'manage_options',
'wp-to-html',
array($this, 'render_settings_page')
);
}
/**
* Register settings
*/
public function register_settings() {
register_setting('wp_to_html_settings', 'wp_to_html_settings', array($this, 'sanitize_settings'));
// General Settings Section
add_settings_section(
'wp_to_html_general',
__('General Settings', 'wp-to-html'),
array($this, 'render_general_section'),
'wp-to-html'
);
add_settings_field(
'enabled',
__('Enable Static HTML Generation', 'wp-to-html'),
array($this, 'render_enabled_field'),
'wp-to-html',
'wp_to_html_general'
);
// Exclusion Settings Section
add_settings_section(
'wp_to_html_exclusions',
__('Exclusion Settings', 'wp-to-html'),
array($this, 'render_exclusions_section'),
'wp-to-html'
);
add_settings_field(
'excluded_shortcodes',
__('Excluded Shortcodes', 'wp-to-html'),
array($this, 'render_shortcodes_field'),
'wp-to-html',
'wp_to_html_exclusions'
);
add_settings_field(
'excluded_blocks',
__('Excluded Blocks', 'wp-to-html'),
array($this, 'render_blocks_field'),
'wp-to-html',
'wp_to_html_exclusions'
);
// Cron Settings Section
add_settings_section(
'wp_to_html_cron',
__('Scheduled Regeneration', 'wp-to-html'),
array($this, 'render_cron_section'),
'wp-to-html'
);
add_settings_field(
'cron_interval',
__('Regeneration Schedule', 'wp-to-html'),
array($this, 'render_cron_field'),
'wp-to-html',
'wp_to_html_cron'
);
// Performance Settings Section
add_settings_section(
'wp_to_html_performance',
__('Performance Settings', 'wp-to-html'),
array($this, 'render_performance_section'),
'wp-to-html'
);
add_settings_field(
'bundle_css',
__('Bundle CSS Files', 'wp-to-html'),
array($this, 'render_bundle_css_field'),
'wp-to-html',
'wp_to_html_performance'
);
add_settings_field(
'bundle_js',
__('Bundle JavaScript Files', 'wp-to-html'),
array($this, 'render_bundle_js_field'),
'wp-to-html',
'wp_to_html_performance'
);
// Cache Info Section
add_settings_section(
'wp_to_html_cache',
__('Cache Information', 'wp-to-html'),
array($this, 'render_cache_section'),
'wp-to-html'
);
}
/**
* Sanitize settings
*/
public function sanitize_settings($input) {
$sanitized = array();
$sanitized['enabled'] = isset($input['enabled']) ? (bool) $input['enabled'] : false;
$sanitized['excluded_shortcodes'] = isset($input['excluded_shortcodes'])
? sanitize_text_field($input['excluded_shortcodes'])
: '';
$sanitized['excluded_blocks'] = isset($input['excluded_blocks'])
? sanitize_text_field($input['excluded_blocks'])
: '';
// Sanitize cron interval
$valid_intervals = array('disabled', 'wp_to_html_hourly', 'wp_to_html_twicedaily', 'daily');
$sanitized['cron_interval'] = isset($input['cron_interval']) && in_array($input['cron_interval'], $valid_intervals)
? $input['cron_interval']
: 'wp_to_html_hourly';
// Sanitize bundling options
$sanitized['bundle_css'] = isset($input['bundle_css']) ? (bool) $input['bundle_css'] : false;
$sanitized['bundle_js'] = isset($input['bundle_js']) ? (bool) $input['bundle_js'] : false;
// Reschedule cron if interval changed
$current_settings = WP_To_HTML::get_settings();
if (!isset($current_settings['cron_interval']) || $current_settings['cron_interval'] !== $sanitized['cron_interval']) {
WP_To_HTML_Cron::reschedule_cron($sanitized['cron_interval']);
}
return $sanitized;
}
/**
* Enqueue admin assets
*/
public function enqueue_admin_assets($hook) {
if ($hook === 'settings_page_wp-to-html') {
wp_enqueue_style(
'wp-to-html-admin',
WP_TO_HTML_PLUGIN_URL . 'admin/css/admin.css',
array(),
WP_TO_HTML_VERSION
);
wp_enqueue_script(
'wp-to-html-admin',
WP_TO_HTML_PLUGIN_URL . 'admin/js/admin.js',
array('jquery'),
WP_TO_HTML_VERSION,
true
);
wp_localize_script('wp-to-html-admin', 'wpToHtml', array(
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('wp_to_html_bulk'),
'statusNonce' => wp_create_nonce('wp_to_html_status'),
'strings' => array(
'processing' => __('Processing...', 'wp-to-html'),
'complete' => __('Complete!', 'wp-to-html'),
'error' => __('An error occurred', 'wp-to-html'),
'confirmClear' => __('Are you sure you want to clear all cached HTML files?', 'wp-to-html'),
'statusIdle' => __('No regeneration in progress', 'wp-to-html'),
'statusRunning' => __('Regeneration in progress...', 'wp-to-html'),
'statusPending' => __('Waiting to start...', 'wp-to-html'),
'statusComplete' => __('Regeneration complete', 'wp-to-html'),
'sourceCron' => __('(Scheduled Task)', 'wp-to-html'),
'sourceAdminBar' => __('(Admin Bar)', 'wp-to-html'),
'sourcePluginUpdate' => __('(Plugin Update)', 'wp-to-html'),
'sourceSettingsPage' => __('(Settings Page)', 'wp-to-html'),
),
));
}
}
/**
* Render settings page
*/
public function render_settings_page() {
if (!current_user_can('manage_options')) {
return;
}
// Check if cache was just cleared
if (isset($_GET['cache_cleared']) && $_GET['cache_cleared'] === '1') {
add_settings_error('wp_to_html', 'cache_cleared', __('Cache cleared successfully.', 'wp-to-html'), 'success');
}
?>
<div class="wrap">
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
<?php settings_errors('wp_to_html'); ?>
<div class="wp-to-html-admin-container">
<div class="wp-to-html-main">
<form action="options.php" method="post">
<?php
settings_fields('wp_to_html_settings');
do_settings_sections('wp-to-html');
submit_button(__('Save Settings', 'wp-to-html'));
?>
</form>
</div>
<div class="wp-to-html-sidebar">
<div class="wp-to-html-card">
<h3><?php _e('Bulk Actions', 'wp-to-html'); ?></h3>
<p><?php _e('Generate or clear cached HTML files in bulk.', 'wp-to-html'); ?></p>
<div class="wp-to-html-bulk-actions">
<button type="button" class="button button-primary" id="wp-to-html-convert-all">
<?php _e('Convert All Pages & Posts', 'wp-to-html'); ?>
</button>
<button type="button" class="button button-secondary" id="wp-to-html-clear-cache">
<?php _e('Clear All Cache', 'wp-to-html'); ?>
</button>
</div>
<div id="wp-to-html-progress" style="display: none;">
<div class="wp-to-html-progress-bar">
<div class="wp-to-html-progress-fill"></div>
</div>
<p class="wp-to-html-progress-text"></p>
</div>
<div id="wp-to-html-results" style="display: none;"></div>
</div>
<div class="wp-to-html-card" id="wp-to-html-status-card">
<h3><?php _e('Regeneration Status', 'wp-to-html'); ?></h3>
<div id="wp-to-html-live-status">
<?php
$status = WP_To_HTML_Cron::get_regeneration_status();
if ($status && in_array($status['status'], array('running', 'pending'))):
?>
<div class="status-running">
<span class="dashicons dashicons-update spin"></span>
<span class="status-text">
<?php echo $status['status'] === 'pending'
? __('Waiting to start...', 'wp-to-html')
: __('Regeneration in progress...', 'wp-to-html'); ?>
</span>
<?php if (isset($status['source'])): ?>
<span class="status-source">
<?php
$sources = array(
'cron' => __('(Scheduled Task)', 'wp-to-html'),
'admin_bar' => __('(Admin Bar)', 'wp-to-html'),
'plugin_update' => __('(Plugin Update)', 'wp-to-html'),
'settings_page' => __('(Settings Page)', 'wp-to-html'),
);
echo isset($sources[$status['source']]) ? $sources[$status['source']] : '';
?>
</span>
<?php endif; ?>
<?php if (isset($status['total']) && $status['total'] > 0): ?>
<div class="status-progress">
<?php printf(__('%d / %d pages processed', 'wp-to-html'), $status['processed'], $status['total']); ?>
</div>
<?php endif; ?>
</div>
<?php elseif ($status && $status['status'] === 'complete'): ?>
<div class="status-complete">
<span class="dashicons dashicons-yes-alt"></span>
<span class="status-text"><?php _e('Last regeneration complete', 'wp-to-html'); ?></span>
<div class="status-details">
<?php printf(__('%d converted, %d skipped, %d errors', 'wp-to-html'),
$status['converted'], $status['skipped'], $status['errors']); ?>
</div>
</div>
<?php else: ?>
<div class="status-idle">
<span class="dashicons dashicons-clock"></span>
<span class="status-text"><?php _e('No regeneration in progress', 'wp-to-html'); ?></span>
</div>
<?php endif; ?>
</div>
</div>
<div class="wp-to-html-card">
<h3><?php _e('Server Configuration', 'wp-to-html'); ?></h3>
<p><?php _e('For Nginx servers, add this to your server configuration:', 'wp-to-html'); ?></p>
<pre class="wp-to-html-code"><?php echo esc_html($this->get_nginx_rules()); ?></pre>
</div>
</div>
</div>
</div>
<?php
}
/**
* Render general section description
*/
public function render_general_section() {
echo '<p>' . __('Configure the main settings for static HTML generation.', 'wp-to-html') . '</p>';
}
/**
* Render enabled field
*/
public function render_enabled_field() {
$settings = WP_To_HTML::get_settings();
?>
<label>
<input type="checkbox" name="wp_to_html_settings[enabled]" value="1"
<?php checked($settings['enabled'], true); ?>>
<?php _e('Enable static HTML generation for pages and posts', 'wp-to-html'); ?>
</label>
<p class="description">
<?php _e('When enabled, pages and posts will be converted to static HTML files for faster loading.', 'wp-to-html'); ?>
</p>
<?php
}
/**
* Render exclusions section description
*/
public function render_exclusions_section() {
echo '<p>' . __('Configure which pages should be excluded from static HTML generation.', 'wp-to-html') . '</p>';
}
/**
* Render shortcodes field
*/
public function render_shortcodes_field() {
$settings = WP_To_HTML::get_settings();
?>
<input type="text" name="wp_to_html_settings[excluded_shortcodes]"
value="<?php echo esc_attr($settings['excluded_shortcodes']); ?>"
class="large-text">
<p class="description">
<?php _e('Comma-separated list of shortcodes. Pages containing these shortcodes will not be converted to static HTML.', 'wp-to-html'); ?>
<br>
<?php _e('Example: contact-form-7, wpforms, gravityform', 'wp-to-html'); ?>
</p>
<?php
}
/**
* Render blocks field
*/
public function render_blocks_field() {
$settings = WP_To_HTML::get_settings();
?>
<input type="text" name="wp_to_html_settings[excluded_blocks]"
value="<?php echo esc_attr($settings['excluded_blocks']); ?>"
class="large-text">
<p class="description">
<?php _e('Comma-separated list of Gutenberg block names or partial names. Pages containing these blocks will not be converted to static HTML.', 'wp-to-html'); ?>
<br>
<?php _e('Example: wpforms, gravityforms, contact-form-7, ninja-forms', 'wp-to-html'); ?>
</p>
<p class="description">
<strong><?php _e('Automatic exclusions:', 'wp-to-html'); ?></strong>
<?php _e('Pages with comments enabled and WooCommerce pages (cart, checkout, my account, products) are automatically excluded.', 'wp-to-html'); ?>
</p>
<?php
}
/**
* Render cron section description
*/
public function render_cron_section() {
echo '<p>' . __('Configure automatic regeneration of stale cached pages.', 'wp-to-html') . '</p>';
}
/**
* Render cron interval field
*/
public function render_cron_field() {
$settings = WP_To_HTML::get_settings();
$current_interval = isset($settings['cron_interval']) ? $settings['cron_interval'] : 'wp_to_html_hourly';
$intervals = array(
'disabled' => __('Disabled', 'wp-to-html'),
'wp_to_html_hourly' => __('Every Hour', 'wp-to-html'),
'wp_to_html_twicedaily' => __('Twice Daily', 'wp-to-html'),
'daily' => __('Daily', 'wp-to-html'),
);
?>
<select name="wp_to_html_settings[cron_interval]">
<?php foreach ($intervals as $value => $label): ?>
<option value="<?php echo esc_attr($value); ?>" <?php selected($current_interval, $value); ?>>
<?php echo esc_html($label); ?>
</option>
<?php endforeach; ?>
</select>
<p class="description">
<?php _e('How often to check for and regenerate stale cached pages. Only pages that have been modified since their last cache will be regenerated.', 'wp-to-html'); ?>
</p>
<?php
// Show next scheduled run
$next_run = WP_To_HTML_Cron::get_next_run();
$last_run = WP_To_HTML_Cron::get_last_run();
if ($next_run): ?>
<p class="description">
<strong><?php _e('Next scheduled run:', 'wp-to-html'); ?></strong>
<?php echo esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $next_run)); ?>
</p>
<?php endif;
if ($last_run): ?>
<p class="description">
<strong><?php _e('Last run:', 'wp-to-html'); ?></strong>
<?php echo esc_html($last_run['time']); ?>
(<?php printf(__('%d pages regenerated', 'wp-to-html'), $last_run['regenerated']); ?>)
</p>
<?php endif;
}
/**
* Render performance section description
*/
public function render_performance_section() {
echo '<p>' . __('Configure performance optimizations for static HTML files.', 'wp-to-html') . '</p>';
}
/**
* Render bundle CSS field
*/
public function render_bundle_css_field() {
$settings = WP_To_HTML::get_settings();
?>
<label>
<input type="checkbox" name="wp_to_html_settings[bundle_css]" value="1"
<?php checked(!empty($settings['bundle_css']), true); ?>>
<?php _e('Combine all CSS files into a single bundled file', 'wp-to-html'); ?>
</label>
<p class="description">
<?php _e('When enabled, all CSS stylesheets will be downloaded and combined into a single file for faster page loading.', 'wp-to-html'); ?>
</p>
<?php
}
/**
* Render bundle JS field
*/
public function render_bundle_js_field() {
$settings = WP_To_HTML::get_settings();
?>
<label>
<input type="checkbox" name="wp_to_html_settings[bundle_js]" value="1"
<?php checked(!empty($settings['bundle_js']), true); ?>>
<?php _e('Combine all JavaScript files into a single bundled file', 'wp-to-html'); ?>
</label>
<p class="description">
<?php _e('When enabled, all JavaScript files will be downloaded and combined into a single file for faster page loading.', 'wp-to-html'); ?>
</p>
<?php
}
/**
* Render cache section
*/
public function render_cache_section() {
$generator = WP_To_HTML_Generator::get_instance();
$stats = $generator->get_cache_stats();
?>
<table class="wp-to-html-stats">
<tr>
<th><?php _e('Cache Directory:', 'wp-to-html'); ?></th>
<td><code><?php echo esc_html(WP_TO_HTML_CACHE_DIR); ?></code></td>
</tr>
<tr>
<th><?php _e('Cached Files:', 'wp-to-html'); ?></th>
<td><?php echo esc_html($stats['total_files']); ?></td>
</tr>
<tr>
<th><?php _e('Total Size:', 'wp-to-html'); ?></th>
<td><?php echo esc_html(size_format($stats['total_size'])); ?></td>
</tr>
<?php if ($stats['oldest_file']): ?>
<tr>
<th><?php _e('Oldest Cache:', 'wp-to-html'); ?></th>
<td><?php echo esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $stats['oldest_file'])); ?></td>
</tr>
<?php endif; ?>
<?php if ($stats['newest_file']): ?>
<tr>
<th><?php _e('Newest Cache:', 'wp-to-html'); ?></th>
<td><?php echo esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $stats['newest_file'])); ?></td>
</tr>
<?php endif; ?>
</table>
<?php
}
/**
* Add meta box to post/page editor
*/
public function add_meta_box() {
$post_types = get_post_types(array('public' => true), 'names');
$excluded = array('attachment', 'product', 'product_variation');
$post_types = array_diff($post_types, $excluded);
foreach ($post_types as $post_type) {
add_meta_box(
'wp_to_html_exclude',
__('WP to HTML', 'wp-to-html'),
array($this, 'render_meta_box'),
$post_type,
'side',
'default'
);
}
}
/**
* Render meta box content
*/
public function render_meta_box($post) {
wp_nonce_field('wp_to_html_meta_box', 'wp_to_html_meta_box_nonce');
$exclude = get_post_meta($post->ID, '_wp_to_html_exclude', true);
$generator = WP_To_HTML_Generator::get_instance();
$has_cache = $generator->has_cache($post->ID);
$exclusion_reason = $generator->should_exclude($post);
?>
<div class="wp-to-html-meta-box">
<p>
<label>
<input type="checkbox" name="wp_to_html_exclude" value="1"
<?php checked($exclude, '1'); ?>>
<?php _e('Keep as dynamic PHP', 'wp-to-html'); ?>
</label>
</p>
<p class="description">
<?php _e('Check this to prevent this page from being converted to static HTML.', 'wp-to-html'); ?>
</p>
<hr>
<p class="wp-to-html-status">
<strong><?php _e('Status:', 'wp-to-html'); ?></strong><br>
<?php if ($exclusion_reason && $exclude !== '1'): ?>
<span class="dashicons dashicons-warning" style="color: #dba617;"></span>
<?php echo esc_html($exclusion_reason); ?>
<?php elseif ($exclude === '1'): ?>
<span class="dashicons dashicons-lock" style="color: #72777c;"></span>
<?php _e('Manually excluded', 'wp-to-html'); ?>
<?php elseif ($has_cache): ?>
<span class="dashicons dashicons-yes-alt" style="color: #00a32a;"></span>
<?php _e('Cached as static HTML', 'wp-to-html'); ?>
<?php else: ?>
<span class="dashicons dashicons-clock" style="color: #72777c;"></span>
<?php _e('Not yet cached', 'wp-to-html'); ?>
<?php endif; ?>
</p>
</div>
<?php
}
/**
* Save meta box data
*/
public function save_meta_box($post_id, $post) {
// Verify nonce
if (!isset($_POST['wp_to_html_meta_box_nonce']) ||
!wp_verify_nonce($_POST['wp_to_html_meta_box_nonce'], 'wp_to_html_meta_box')) {
return;
}
// Check permissions
if (!current_user_can('edit_post', $post_id)) {
return;
}
// Don't save for autosaves
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
// Save the exclusion setting
$exclude = isset($_POST['wp_to_html_exclude']) ? '1' : '';
update_post_meta($post_id, '_wp_to_html_exclude', $exclude);
}
/**
* Get Nginx configuration rules
*/
private function get_nginx_rules() {
$cache_path = str_replace(ABSPATH, '/', WP_TO_HTML_CACHE_DIR);
return "# WP-to-HTML Static Serving
location / {
# Skip for logged-in users
if (\$http_cookie ~* \"wordpress_logged_in\") {
set \$skip_cache 1;
}
# Skip for POST requests
if (\$request_method = POST) {
set \$skip_cache 1;
}
# Try to serve cached file
set \$cache_file \$document_root{$cache_path}\$uri/index.html;
if (-f \$cache_file) {
rewrite ^(.*)\$ {$cache_path}\$uri/index.html break;
}
}";
}
/**
* AJAX handler to get regeneration status
*/
public function ajax_get_status() {
check_ajax_referer('wp_to_html_status', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(__('Permission denied', 'wp-to-html'));
}
$status = WP_To_HTML_Cron::get_regeneration_status();
if (!$status) {
wp_send_json_success(array(
'status' => 'idle',
));
}
wp_send_json_success($status);
}
}