Plugin Directory

Changeset 3487854


Ignore:
Timestamp:
03/21/2026 04:29:44 PM (7 days ago)
Author:
recorp
Message:

Added tag 6.0.7.0

Location:
export-wp-page-to-static-html/tags/6.0.7.0
Files:
17 copied

Legend:

Unmodified
Added
Removed
  • export-wp-page-to-static-html/tags/6.0.7.0/README.txt

    r3479013 r3487854  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 6.0.6.1
     7Stable tag: 6.0.7.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    111111
    112112== Changelog ==
     113= 6.0.7.0 =
     114* Fixed: Clicking Stop now immediately halts background export processing.
     115* Improved: Export log now records when an export is paused, resumed, or stopped by the user.
     116* Improved: Internal code improvements for better reliability and stability.
     117
    113118= 6.0.6.0 =
    114119* Added PDF exporting functionality.
  • export-wp-page-to-static-html/tags/6.0.7.0/export-wp-page-to-static-html.php

    r3479013 r3487854  
    44 * Plugin URI:        https://myrecorp.com
    55 * Description:       Export WP Pages to Static HTML is the most flexible static HTML export plugin for WordPress. Unlike full-site generators, Export WP Pages to Static HTML gives you surgical control — export exactly the posts, pages, or custom post types you need, in the status you want, as the user role you choose.
    6  * Version:           6.0.6.1
     6 * Version:           6.0.7.0
    77 * Author:            ReCorp
    88 * Author URI:        https://www.upwork.com/fl/rayhan1
     
    2020    load_plugin_textdomain('wp-to-html', false, dirname(plugin_basename(__FILE__)) . '/languages');
    2121});
    22 define('WP_TO_HTML_VERSION', '6.0.6.1');
     22define('WP_TO_HTML_VERSION', '6.0.7.0');
    2323define('WP_TO_HTML_PATH', plugin_dir_path(__FILE__));
    2424define('WP_TO_HTML_URL', plugin_dir_url(__FILE__));
     
    161161
    162162
     163/**
     164 * Decide whether a "What's New" redirect should fire for this version update.
     165 *
     166 * Rules:
     167 *   1. Fresh install ($old_version empty): never redirect.
     168 *   2. User has never been redirected before (wp_to_html_whats_new_version empty):
     169 *      redirect once, regardless of increment size (one-time catch-up for all users).
     170 *   3. Going forward: redirect only when the first three version segments (x.y.z)
     171 *      advance. The fourth "build" segment alone (e.g. 6.0.6.1 → 6.0.6.2) does NOT
     172 *      trigger a redirect.
     173 *
     174 * The caller must also run:
     175 *   update_option('wp_to_html_whats_new_version', $new_version, false)
     176 * so future comparisons start from the correct baseline.
     177 */
     178function wp_to_html_should_redirect_to_whats_new(string $old_version, string $new_version): bool {
     179    // Fresh install: no redirect.
     180    if ($old_version === '') return false;
     181
     182    $last_seen = (string) get_option('wp_to_html_whats_new_version', '');
     183
     184    // Never redirected before (all users on first-ever catch-up): redirect once.
     185    if ($last_seen === '') return true;
     186
     187    // Major release check: compare x.y.z of last-redirected version vs new version.
     188    $last_parts = array_pad(explode('.', $last_seen), 4, '0');
     189    $new_parts  = array_pad(explode('.', $new_version), 4, '0');
     190
     191    $last_xyz = implode('.', array_slice($last_parts, 0, 3));
     192    $new_xyz  = implode('.', array_slice($new_parts, 0, 3));
     193
     194    return version_compare($new_xyz, $last_xyz, '>');
     195}
     196
    163197register_activation_hook(__FILE__, function() {
    164198    $installed_version = get_option('wp_to_html_version', '');
     
    184218        // then bump the stored version.
    185219        wp_to_html_ensure_tables();
    186         set_transient('wp_to_html_redirect_to_whats_new', 1, 60);
     220        if (wp_to_html_should_redirect_to_whats_new($installed_version, WP_TO_HTML_VERSION)) {
     221            set_transient('wp_to_html_redirect_to_whats_new', 1, 60);
     222            update_option('wp_to_html_whats_new_version', WP_TO_HTML_VERSION, false);
     223        }
    187224        update_option('wp_to_html_version', WP_TO_HTML_VERSION, false);
    188225    }
     
    196233
    197234/**
    198  * "What's New" redirect after plugin update — Pro only.
    199  * Only fires when the Pro add-on is active (WP_TO_HTML_PRO_ACTIVE defined).
     235 * "What's New" redirect after plugin update — all users on major releases.
     236 * Fires for every user when the first three version segments (x.y.z) advance,
     237 * or on first-ever update for users who were never redirected before (free users).
    200238 */
    201239add_action('admin_init', function () {
    202     // Only redirect when Pro is active.
    203     if (!defined('WP_TO_HTML_PRO_ACTIVE') || !WP_TO_HTML_PRO_ACTIVE) return;
    204 
    205240    if (!current_user_can('manage_options')) return;
    206241    if (wp_doing_ajax() || wp_doing_cron()) return;
     
    398433    update_option('wp_to_html_version', $current_version, false);
    399434
    400     // Set What's New redirect transient — only shown when Pro is active (see admin_init hook).
    401     if ($installed_version !== '' && version_compare($installed_version, $current_version, '<')) {
     435    // Set What's New redirect transient (major releases for all users; see admin_init hook).
     436    if (wp_to_html_should_redirect_to_whats_new($installed_version, $current_version)) {
    402437        set_transient('wp_to_html_redirect_to_whats_new', 1, 60);
     438        update_option('wp_to_html_whats_new_version', $current_version, false);
    403439    }
    404440}
  • export-wp-page-to-static-html/tags/6.0.7.0/includes/class-admin.php

    r3479004 r3487854  
    275275    <div class="eh-seg" role="tablist" aria-label="<?php esc_attr_e('What to export', 'wp-to-html'); ?>">
    276276        <button type="button" id="eh-export-custom" role="tab" aria-pressed="true">
    277             <?php esc_html_e('Pick custom items', 'wp-to-html'); ?>
     277            <?php esc_html_e('Select Posts/Pages', 'wp-to-html'); ?>
    278278        </button>
    279279
  • export-wp-page-to-static-html/tags/6.0.7.0/includes/class-core.php

    r3479004 r3487854  
    7070        }
    7171    }
    72     // public function process_background() {
    73     //     global $wpdb;
    74 
    75     //     // ✅ Define tables FIRST
    76     //     $status_table = $wpdb->prefix . 'wp_to_html_status';
    77     //     $queue_table  = $wpdb->prefix . 'wp_to_html_queue';
    78     //     $assets_table = $wpdb->prefix . 'wp_to_html_assets';
    79 
    80     //     // ✅ Get latest status row
    81     //     $status = $wpdb->get_row("SELECT * FROM {$status_table} ORDER BY id DESC LIMIT 1");
    82     //     if (!$status) {
    83     //         return;
    84     //     }
    85 
    86     //     // If not running, don't do work (prevents cron from continuing after completion)
    87     //     if ((int)($status->is_running ?? 0) === 0 && in_array($status->state, ['completed','stopped','error'], true)) {
    88     //         wp_clear_scheduled_hook('wp_to_html_process_event');
    89     //         return;
    90     //     }
    91 
    92     //     // ✅ Paused: do nothing (also don't schedule new)
    93     //     if ($status->state === 'paused') {
    94     //         wp_clear_scheduled_hook('wp_to_html_process_event');
    95     //         return;
    96     //     }
    97 
    98     //     // ✅ Stopped: clear everything & reset to idle
    99     //     if ($status->state === 'stopped') {
    100 
    101     //         // Clear queue and assets
    102     //         $wpdb->query("TRUNCATE TABLE {$queue_table}");
    103     //         $wpdb->query("TRUNCATE TABLE {$assets_table}");
    104 
    105     //         
    106 
    107     //             'state'      => 'idle',
    108     //             'is_running' => 0,
    109     //         ], ['id' => $status->id]);
    110 
    111     //         wp_clear_scheduled_hook('wp_to_html_process_event');
    112     //         return;
    113     //     }
    114 
    115     //     // ✅ Mark running (in case cron fired after a refresh)
    116     //     if ($status->state !== 'running' || (int)($status->is_running ?? 0) !== 1) {
    117     //         $wpdb->update($status_table, [
    118     //             'state'      => 'running',
    119     //             'is_running' => 1,
    120     //         ], ['id' => $status->id]);
    121     //     }
    122 
    123     //     $exporter = new Exporter();
    124 
    125     //     // Process batches (your exporter likely reads from tables internally)
    126     //     $exporter->process_batch(10);
    127     //     $exporter->process_asset_batch(30);
    128 
    129     //     // ✅ Update counters
    130     //     $processed_urls    = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$queue_table} WHERE status='done'");
    131     //     $processed_assets  = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$assets_table} WHERE status='done'");
    132     //     $remaining_urls    = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$queue_table} WHERE status='pending'");
    133     //     $remaining_assets  = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$assets_table} WHERE status='pending'");
    134 
    135     //     $wpdb->update($status_table, [
    136     //         'processed_urls'    => $processed_urls,
    137     //         'processed_assets'  => $processed_assets,
    138     //         // Optional: keep these if you have columns for them
    139     //         // 'updated_at'     => current_time('mysql'),
    140     //     ], ['id' => $status->id]);
    141 
    142     //     if ($remaining_urls > 0 || $remaining_assets > 0) {
    143 
    144     //         // 🔁 Schedule next batch (avoid duplicates)
    145     //         if (!wp_next_scheduled('wp_to_html_process_event')) {
    146     //             wp_schedule_single_event(time() + 2, 'wp_to_html_process_event');
    147     //         }
    148 
    149     //         return;
    150     //     }
    151 
    152     //     // ✅ Finished: no remaining work
    153     //     $wpdb->update($status_table, [
    154     //         'state'      => 'completed',
    155     //         'is_running' => 0,
    156     //     ], ['id' => $status->id]);
    157 
    158     //     wp_clear_scheduled_hook('wp_to_html_process_event');
    159     // }
    160 
    161 
    16272
    16373    private function update_status(array $data) {
     
    432342                    'updated_at'       => current_time('mysql'),
    433343                ], ['id' => 1]);
     344
     345                // Mid-tick stop/pause check: the user may have clicked Stop or Pause
     346                // while this tick was running. Re-read state from DB after each completed
     347                // batch and bail immediately so the next batch never starts.
     348                // This is the earliest safe exit point — the current batch is fully done
     349                // and counts are already written to DB.
     350                $live_state = (string) $wpdb->get_var(
     351                    $wpdb->prepare("SELECT state FROM {$status_table} WHERE id=%d", 1)
     352                );
     353                if (in_array($live_state, ['stopped', 'paused'], true)) {
     354                    break;
     355                }
    434356            }
    435357
     
    450372            $stats['updated_at']      = time();
    451373            update_option($stats_key, $stats, false);
     374
     375            // Post-loop stopped/paused guard.
     376            // Without this, the status DB update below would blindly overwrite
     377            // 'stopped' → 'running' (and is_running → 1), re-arm the cron, fire
     378            // the bg nudge, and send a second API completion report when the
     379            // rescheduled tick eventually finishes — causing duplicate rows in the
     380            // API dashboard. The return is inside the try block so the finally
     381            // clause still fires: it releases the transient lock and, since
     382            // $fire_bg_nudge is never set, skips the bg nudge entirely.
     383            $post_loop_state = (string) $wpdb->get_var(
     384                $wpdb->prepare("SELECT state FROM {$status_table} WHERE id=%d", 1)
     385            );
     386            if (in_array($post_loop_state, ['stopped', 'paused'], true)) {
     387                return;
     388            }
    452389
    453390            // ------------------------------------------------------------------
     
    662599    $no_failures = (($failed_urls + $failed_assets) === 0);
    663600    $this->maybe_send_completion_email($no_failures, $zip_info);
     601
     602    // Send export report to ReCorp API server (non-blocking).
     603    $this->send_export_report_to_api($no_failures, [
     604        'total_urls'       => $total_urls,
     605        'processed_urls'   => $processed_urls,
     606        'failed_urls'      => $failed_urls,
     607        'total_assets'     => $total_assets,
     608        'processed_assets' => $processed_assets,
     609        'failed_assets'    => $failed_assets,
     610        'zip_info'         => $zip_info,
     611    ]);
    664612} else {
    665613    // Schedule immediately so the bg nudge (fired after lock release) can pick it up.
     
    667615    wp_schedule_single_event(time(), 'wp_to_html_process_event');
    668616    $fire_bg_nudge = true;
    669 }} finally {
     617}} catch (\Throwable $e) {
     618
     619            // Unexpected exception: report as 'failed' so it's visible in the API dashboard,
     620            // then re-throw so the error still propagates normally.
     621            try {
     622                $this->send_export_report_to_api(false, ['exception' => $e->getMessage()]);
     623            } catch (\Throwable $e2) {
     624                // Never let the report call swallow the original exception.
     625            }
     626            throw $e;
     627
     628        } finally {
    670629
    671630            if (Advanced_Debugger::enabled()) {
     
    995954
    996955    }
     956
     957    /**
     958     * Send export report to ReCorp API server after export completes, fails, or is stopped.
     959     *
     960     * API endpoint: https://api.myrecorp.com/wpptsh-report.php?type=error_log
     961     *
     962     * On SUCCESS (no failures): sends outcome + stats only — no log text.
     963     * On FAILURE / STOP: sends outcome + stats + full export log text so the
     964     * API server can store it for debugging.
     965     *
     966     * Non-blocking: uses wp_remote_post with 0.5s timeout (slightly higher than
     967     * fire-and-forget to ensure the body reaches the server on large payloads).
     968     *
     969     * @param bool  $success  Whether the export completed without any errors.
     970     * @param array $stats    Export statistics (urls, assets, zip info, etc.).
     971     */
     972    public function send_export_report_to_api(bool $success, array $stats = []): void {
     973
     974        $api_url = 'https://api.myrecorp.com/wpptsh-report.php?type=error_log';
     975
     976        /**
     977         * Filter the API endpoint for export reports.
     978         * Return empty string to disable reporting entirely.
     979         *
     980         * @param string $api_url  The API endpoint URL.
     981         */
     982        $api_url = (string) apply_filters('wp_to_html_export_report_api_url', $api_url);
     983        if ($api_url === '') {
     984            return;
     985        }
     986
     987        // Determine outcome.
     988        $outcome = 'completed';
     989        if (!$success && !empty($stats['exception'])) {
     990            $outcome = 'stopped_with_error';
     991        } elseif (!$success && !empty($stats['stopped'])) {
     992            $outcome = 'stopped_by_user';
     993        } elseif (!$success) {
     994            $outcome = 'completed_with_failures';
     995        }
     996
     997        $ctx = get_option('wp_to_html_export_context', []);
     998        if (!is_array($ctx)) $ctx = [];
     999
     1000        $scope      = isset($ctx['scope']) ? (string) $ctx['scope'] : 'unknown';
     1001        $asset_mode = isset($ctx['asset_collection_mode']) ? (string) $ctx['asset_collection_mode'] : 'strict';
     1002
     1003        $t_urls   = (int) ($stats['total_urls']       ?? 0);
     1004        $p_urls   = (int) ($stats['processed_urls']   ?? 0);
     1005        $f_urls   = (int) ($stats['failed_urls']      ?? 0);
     1006        $t_assets = (int) ($stats['total_assets']     ?? 0);
     1007        $p_assets = (int) ($stats['processed_assets'] ?? 0);
     1008        $f_assets = (int) ($stats['failed_assets']    ?? 0);
     1009
     1010        $is_pro = defined('WP_TO_HTML_PRO_ACTIVE') && WP_TO_HTML_PRO_ACTIVE;
     1011
     1012        // Build compact status summary (backward compat with old API column).
     1013        $status_parts = [
     1014            $outcome,
     1015            'scope=' . $scope,
     1016            'urls=' . $p_urls . '/' . $t_urls . ($f_urls > 0 ? '(f' . $f_urls . ')' : ''),
     1017            'assets=' . $p_assets . '/' . $t_assets . ($f_assets > 0 ? '(f' . $f_assets . ')' : ''),
     1018            $is_pro ? 'pro' : 'free',
     1019        ];
     1020        $status_string = implode(' | ', $status_parts);
     1021
     1022        // Build structured payload.
     1023        $payload = [
     1024            'site_url'         => home_url('/'),
     1025            'outcome'          => $outcome,
     1026            'status'           => $status_string,
     1027            'plugin_version'   => WP_TO_HTML_VERSION,
     1028            'wp_version'       => get_bloginfo('version'),
     1029            'php_version'      => phpversion(),
     1030            'scope'            => $scope,
     1031            'total_urls'       => $t_urls,
     1032            'processed_urls'   => $p_urls,
     1033            'failed_urls'      => $f_urls,
     1034            'total_assets'     => $t_assets,
     1035            'processed_assets' => $p_assets,
     1036            'failed_assets'    => $f_assets,
     1037        ];
     1038
     1039        // On failure or stop: include the full export log text so the API
     1040        // server can store it for remote debugging.
     1041        // On clean completion: no log text needed (saves bandwidth).
     1042        if ($outcome !== 'completed') {
     1043            $log_text = $this->read_export_log_for_report();
     1044            if ($log_text !== '') {
     1045                $payload['error_log'] = $log_text;
     1046            }
     1047        }
     1048
     1049        /**
     1050         * Filter the export report payload before sending.
     1051         *
     1052         * @param array  $payload  The report data.
     1053         * @param bool   $success  Whether export was successful.
     1054         * @param array  $stats    Raw statistics.
     1055         * @param string $outcome  One of: completed, completed_with_failures, stopped, failed.
     1056         */
     1057        $payload = (array) apply_filters('wp_to_html_export_report_payload', $payload, $success, $stats, $outcome);
     1058
     1059        // Blocking=true with a reasonable timeout ensures the TCP+TLS handshake
     1060        // and the full request body actually reach the server before PHP moves on.
     1061        // Non-blocking with tiny timeouts (< TLS handshake latency) silently drops
     1062        // the connection before any data is transmitted.
     1063        $has_log = !empty($payload['error_log']);
     1064
     1065        @wp_remote_post($api_url, [
     1066            'timeout'    => 15,
     1067            'blocking'   => true,
     1068            'sslverify'  => true,
     1069            'headers'    => [
     1070                'Content-Type' => 'application/json',
     1071                'User-Agent'   => 'WpToHtml/' . WP_TO_HTML_VERSION . '; ' . home_url('/'),
     1072            ],
     1073            'body'       => wp_json_encode($payload),
     1074        ]);
     1075
     1076        $this->log('Export report sent to API (' . $outcome . ($has_log ? ', with log' : '') . ').');
     1077    }
     1078
     1079    /**
     1080     * Read the export log file for inclusion in the API report.
     1081     * Returns the last 64 KB of the log (tail) to keep payloads reasonable.
     1082     * The most useful debugging info is at the end of the log.
     1083     *
     1084     * @return string Log text or empty string.
     1085     */
     1086    private function read_export_log_for_report(): string {
     1087        $log_file = WP_TO_HTML_EXPORT_DIR . '/export-log.txt';
     1088
     1089        if (!file_exists($log_file) || !is_readable($log_file)) {
     1090            return '';
     1091        }
     1092
     1093        $max_bytes = 64 * 1024; // 64 KB cap
     1094        $size = (int) @filesize($log_file);
     1095
     1096        if ($size <= 0) {
     1097            return '';
     1098        }
     1099
     1100        // If file is small enough, read it all.
     1101        if ($size <= $max_bytes) {
     1102            $content = @file_get_contents($log_file);
     1103            return is_string($content) ? $content : '';
     1104        }
     1105
     1106        // File is larger than cap: read the last 64 KB (tail).
     1107        $fp = @fopen($log_file, 'rb');
     1108        if (!$fp) {
     1109            return '';
     1110        }
     1111
     1112        fseek($fp, -$max_bytes, SEEK_END);
     1113        $content = @fread($fp, $max_bytes);
     1114        fclose($fp);
     1115
     1116        if (!is_string($content) || $content === '') {
     1117            return '';
     1118        }
     1119
     1120        // Skip the first partial line (we likely landed mid-line).
     1121        $nl = strpos($content, "\n");
     1122        if ($nl !== false && $nl < 200) {
     1123            $content = substr($content, $nl + 1);
     1124        }
     1125
     1126        return "... (truncated, last 64KB) ...\n" . $content;
     1127    }
     1128
     1129
    9971130/**
    9981131 * Watchdog: reclaim stuck rows left in `processing` due to PHP fatals/timeouts/network hangs.
  • export-wp-page-to-static-html/tags/6.0.7.0/includes/class-exporter.php

    r3479013 r3487854  
    106106    }
    107107
    108     public function run() {
    109 
    110         $this->log('Starting export...');
    111 
    112        
    113         $urls = [];
    114 
    115         $full_site = !empty($args['full_site']);
    116         $include_home = array_key_exists('include_home', $args) ? (bool) $args['include_home'] : true;
    117         $selected = isset($args['selected']) ? (array) $args['selected'] : [];
    118 
    119         if ($include_home) {
    120             $urls[] = home_url('/');
    121         }
    122 
    123         if ($full_site) {
    124 
    125             $post_types = get_post_types(['public' => true], 'names');
    126 
    127             $posts = get_posts([
    128                 'post_type' => $post_types,
    129                 'post_status' => 'publish',
    130                 'numberposts' => -1
    131             ]);
    132 
    133             foreach ($posts as $post) {
    134                 $urls[] = get_permalink($post);
    135             }
    136 
    137         } else {
    138 
    139             // Selected export: accept [{id,type}] OR raw IDs OR URLs
    140             foreach ($selected as $item) {
    141 
    142                 $id = 0;
    143 
    144                 if (is_array($item)) {
    145                     if (isset($item['id'])) {
    146                         $id = (int) $item['id'];
    147                     } elseif (isset($item['ID'])) {
    148                         $id = (int) $item['ID'];
    149                     }
    150                     if (!empty($item['url'])) {
    151                         $u = esc_url_raw((string) $item['url']);
    152                         if (!empty($u)) $urls[] = $u;
    153                         continue;
    154                     }
    155                 } elseif (is_numeric($item)) {
    156                     $id = (int) $item;
    157                 } elseif (is_string($item)) {
    158                     $u = esc_url_raw($item);
    159                     if (!empty($u)) $urls[] = $u;
    160                     continue;
    161                 }
    162 
    163                 if ($id > 0) {
    164                     $u = get_permalink($id);
    165                     if ($u) $urls[] = $u;
    166                 }
    167             }
    168         }
    169 
    170         $urls = array_unique(array_filter($urls));
    171 
    172 $this->log('Total URLs collected: ' . count($urls));
    173 
    174         foreach ($urls as $url) {
    175             $this->export_url($url);
    176         }
    177 
    178         $this->log('Export completed.');
    179     }
    180 
    181108
    182109    /**
     
    218145    }
    219146
    220     private function export_url($url) {
    221 
    222         $this->log('Exporting: ' . $url);
    223 
    224         $response = wp_remote_get($url);
    225 
    226         if (is_wp_error($response)) {
    227             $this->log('Failed: ' . $url);
    228             return;
    229         }
    230 
    231         $html = wp_remote_retrieve_body($response);
    232 
    233         // Determine output directory for this URL before rewriting assets.
    234         $path = (string) parse_url($url, PHP_URL_PATH);
    235         $path = trim($path, '/');
    236         if ($path === '') {
    237             $path = 'index';
    238         }
    239 
    240         $dir = WP_TO_HTML_EXPORT_DIR . '/' . $path;
    241         wp_mkdir_p($dir);
    242 
    243         // Rewrite/copy assets.
    244         $asset_manager = new Asset_Manager($this->group_assets);
    245         $html = $asset_manager->process($html, $dir);
    246 
    247         file_put_contents($dir . '/index.html', $html);
    248 
    249         $this->log('Saved: ' . $path);
    250     }
    251147
    252148    /**
     
    555451            $last_phase = (string) get_option('wp_to_html_queue_build_last_phase', '');
    556452
    557             if ($ins - $last_logged >= $bs || $ph !== $last_phase) {
     453            if ($ins - $last_logged >= $batch_size || $ph !== $last_phase) {
    558454                if (WP_TO_HTML_DEBUG) {
    559455                    $this->log('Queue build tick: phase=' . $ph . ' inserted=' . $ins);
     
    16361532    }
    16371533           
    1638     public function handle_export($request) {
    1639 
    1640         $exporter = new Exporter();
    1641 
    1642         if ($request->get_param('init')) {
    1643             $exporter->build_queue();
    1644             return ['message' => __('Queue built', 'wp-to-html')];
    1645         }
    1646 
    1647         $urls_done = $exporter->process_batch(20);
    1648         $assets_done = $exporter->process_asset_batch(50);
    1649 
    1650         return [
    1651             'urls_remaining' => $this->count_pending('wp_to_html_queue'),
    1652             'assets_remaining' => $this->count_pending('wp_to_html_assets')
    1653         ];
    1654     }
    1655 
    1656     private function count_pending($table_name) {
    1657         global $wpdb;
    1658         $table = $wpdb->prefix . $table_name;
    1659         return $wpdb->get_var("SELECT COUNT(*) FROM $table WHERE status='pending'");
    1660     }
    16611534
    16621535    private function rewrite_dom_paths($html, $dot) {
  • export-wp-page-to-static-html/tags/6.0.7.0/includes/class-rest.php

    r3479013 r3487854  
    12411241        wp_clear_scheduled_hook('wp_to_html_process_event');
    12421242
     1243        try { (new \WpToHtml\Exporter())->log_public('Export paused by the user.'); } catch (\Throwable $e) {}
     1244
    12431245        return ['message' => __('Paused', 'wp-to-html')];
    12441246    }
     
    12541256            ['id' => 1]
    12551257        );
     1258
     1259        try { (new \WpToHtml\Exporter())->log_public('Export resumed by the user.'); } catch (\Throwable $e) {}
    12561260
    12571261        wp_schedule_single_event(time(), 'wp_to_html_process_event');
     
    12931297        global $wpdb;
    12941298
     1299        $status_table = $wpdb->prefix . 'wp_to_html_status';
     1300        $queue_table  = $wpdb->prefix . 'wp_to_html_queue';
     1301        $assets_table = $wpdb->prefix . 'wp_to_html_assets';
     1302
     1303        // Capture stats BEFORE changing state (so counts are accurate).
     1304        $total_urls       = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$queue_table}");
     1305        $processed_urls   = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$queue_table} WHERE status='done'");
     1306        $failed_urls      = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$queue_table} WHERE status='failed'");
     1307        $total_assets     = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$assets_table}");
     1308        $processed_assets = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$assets_table} WHERE status='done'");
     1309        $failed_assets    = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$assets_table} WHERE status='failed'");
     1310
    12951311        $wpdb->update(
    1296             $wpdb->prefix . 'wp_to_html_status',
     1312            $status_table,
    12971313            [
    12981314                'state'      => 'stopped',
     
    13041320        wp_clear_scheduled_hook('wp_to_html_process_event');
    13051321
     1322        try { (new \WpToHtml\Exporter())->log_public('Export stopped by the user.'); } catch (\Throwable $e) {}
     1323
    13061324        // Best-effort cleanup of temp export user.
    13071325        $this->cleanup_temp_export_user();
     1326
     1327        // Send export report to API server (stopped by user).
     1328        try {
     1329            $core = \WpToHtml\Core::get_instance();
     1330            $core->send_export_report_to_api(false, [
     1331                'total_urls'       => $total_urls,
     1332                'processed_urls'   => $processed_urls,
     1333                'failed_urls'      => $failed_urls,
     1334                'total_assets'     => $total_assets,
     1335                'processed_assets' => $processed_assets,
     1336                'failed_assets'    => $failed_assets,
     1337                'stopped'          => true,
     1338            ]);
     1339        } catch (\Throwable $e) {
     1340            // Never fail the stop action due to report.
     1341        }
    13081342
    13091343        return ['message' => __('Stopped', 'wp-to-html')];
  • export-wp-page-to-static-html/tags/6.0.7.0/includes/class-whats-new.php

    r3479013 r3487854  
    296296                    <div class="wth-version">
    297297                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.24 12.24a6 6 0 0 0-8.49-8.49L5 10.5V19h8.5z"/><line x1="16" y1="8" x2="2" y2="22"/><line x1="17.5" y1="15" x2="9" y2="15"/></svg>
    298                         <?php esc_html_e('Version', 'wp-to-html'); ?> <strong>6.0.6.0</strong>
     298                        <?php esc_html_e('Version', 'wp-to-html'); ?> <strong>6.0.7.0</strong>
    299299                    </div>
    300300                </div>
    301301
    302                 <!-- 6.0.6.0 Changelog Cards -->
     302                <!-- 6.0.7.0 Changelog Cards -->
    303303                <div class="wth-cards">
    304304
    305                     <!-- Added: PDF exporting -->
     305                    <!-- Fixed: Stop button halts background immediately -->
    306306                    <div class="wth-card">
    307                         <div class="wth-card-icon added">
    308                             <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="12" y1="18" x2="12" y2="12"/><line x1="9" y1="15" x2="15" y2="15"/></svg>
     307                        <div class="wth-card-icon fixed">
     308                            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/></svg>
    309309                        </div>
    310310                        <div class="wth-card-body">
    311                             <div class="wth-card-label added"><?php esc_html_e('Added', 'wp-to-html'); ?></div>
    312                             <div class="wth-card-text"><?php esc_html_e('PDF exporting functionality — generate a PDF of any page directly from the frontend with a single click.', 'wp-to-html'); ?></div>
    313                         </div>
    314                     </div>
    315 
    316                     <!-- Improved: Exporting experience -->
     311                            <div class="wth-card-label fixed"><?php esc_html_e('Fixed', 'wp-to-html'); ?></div>
     312                            <div class="wth-card-text"><?php esc_html_e('Clicking Stop now immediately halts background export processing. Previously, an active background tick could continue running for several seconds after stopping.', 'wp-to-html'); ?></div>
     313                        </div>
     314                    </div>
     315
     316                    <!-- Improved: Log records pause/resume/stop -->
    317317                    <div class="wth-card">
    318318                        <div class="wth-card-icon improved">
    319                             <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
     319                            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
    320320                        </div>
    321321                        <div class="wth-card-body">
    322322                            <div class="wth-card-label improved"><?php esc_html_e('Improved', 'wp-to-html'); ?></div>
    323                             <div class="wth-card-text"><?php esc_html_e('Enhanced exporting experience — faster, more reliable, with better progress feedback throughout the process.', 'wp-to-html'); ?></div>
    324                         </div>
    325                     </div>
    326 
    327                     <!-- Improved: Layout -->
     323                            <div class="wth-card-text"><?php esc_html_e('Export activity log now records when an export is paused, resumed, or stopped by the user, making it easier to track what happened during an export session.', 'wp-to-html'); ?></div>
     324                        </div>
     325                    </div>
     326
     327                    <!-- Improved: Internal code improvements -->
    328328                    <div class="wth-card">
    329                         <div class="wth-card-icon improved">
    330                             <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="9" y1="21" x2="9" y2="9"/></svg>
     329                        <div class="wth-card-icon core">
     330                            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
    331331                        </div>
    332332                        <div class="wth-card-body">
    333                             <div class="wth-card-label improved"><?php esc_html_e('Improved', 'wp-to-html'); ?></div>
    334                             <div class="wth-card-text"><?php esc_html_e('Redesigned layout that is easier to understand — cleaner sections, clearer labels, and a more intuitive flow.', 'wp-to-html'); ?></div>
    335                         </div>
    336                     </div>
    337 
     333                            <div class="wth-card-label core"><?php esc_html_e('Core', 'wp-to-html'); ?></div>
     334                            <div class="wth-card-text"><?php esc_html_e('Internal code improvements for better reliability and stability across different server environments.', 'wp-to-html'); ?></div>
     335                        </div>
     336                    </div>
     337
     338                </div>
     339
     340                <!-- Previous Release: 6.0.6.0 -->
     341                <div class="wth-prev-release">
     342                    <div class="wth-prev-release-heading">
     343                        <hr>
     344                        <span class="wth-prev-release-label"><?php esc_html_e('Previous Release', 'wp-to-html'); ?></span>
     345                        <hr>
     346                    </div>
     347
     348                    <div style="margin-bottom:16px;">
     349                        <span class="wth-prev-version-pill">
     350                            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.24 12.24a6 6 0 0 0-8.49-8.49L5 10.5V19h8.5z"/><line x1="16" y1="8" x2="2" y2="22"/></svg>
     351                            <?php esc_html_e('Version', 'wp-to-html'); ?> 6.0.6.0
     352                        </span>
     353                    </div>
     354
     355                    <div class="wth-cards">
     356
     357                        <div class="wth-card">
     358                            <div class="wth-card-icon added">
     359                                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="12" y1="18" x2="12" y2="12"/><line x1="9" y1="15" x2="15" y2="15"/></svg>
     360                            </div>
     361                            <div class="wth-card-body">
     362                                <div class="wth-card-label added"><?php esc_html_e('Added', 'wp-to-html'); ?></div>
     363                                <div class="wth-card-text"><?php esc_html_e('PDF exporting functionality — generate a PDF of any page directly from the frontend with a single click.', 'wp-to-html'); ?></div>
     364                            </div>
     365                        </div>
     366
     367                        <div class="wth-card">
     368                            <div class="wth-card-icon improved">
     369                                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
     370                            </div>
     371                            <div class="wth-card-body">
     372                                <div class="wth-card-label improved"><?php esc_html_e('Improved', 'wp-to-html'); ?></div>
     373                                <div class="wth-card-text"><?php esc_html_e('Enhanced exporting experience — faster, more reliable, with better progress feedback throughout the process.', 'wp-to-html'); ?></div>
     374                            </div>
     375                        </div>
     376
     377                        <div class="wth-card">
     378                            <div class="wth-card-icon improved">
     379                                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="9" y1="21" x2="9" y2="9"/></svg>
     380                            </div>
     381                            <div class="wth-card-body">
     382                                <div class="wth-card-label improved"><?php esc_html_e('Improved', 'wp-to-html'); ?></div>
     383                                <div class="wth-card-text"><?php esc_html_e('Redesigned layout that is easier to understand — cleaner sections, clearer labels, and a more intuitive flow.', 'wp-to-html'); ?></div>
     384                            </div>
     385                        </div>
     386
     387                    </div>
    338388                </div>
    339389
Note: See TracChangeset for help on using the changeset viewer.