Changeset 3487855
- Timestamp:
- 03/21/2026 04:31:23 PM (8 days ago)
- Location:
- export-wp-page-to-static-html/trunk
- Files:
-
- 7 edited
-
README.txt (modified) (2 diffs)
-
export-wp-page-to-static-html.php (modified) (6 diffs)
-
includes/class-admin.php (modified) (1 diff)
-
includes/class-core.php (modified) (6 diffs)
-
includes/class-exporter.php (modified) (4 diffs)
-
includes/class-rest.php (modified) (4 diffs)
-
includes/class-whats-new.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
export-wp-page-to-static-html/trunk/README.txt
r3479013 r3487855 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 6.0. 6.17 Stable tag: 6.0.7.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 111 111 112 112 == 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 113 118 = 6.0.6.0 = 114 119 * Added PDF exporting functionality. -
export-wp-page-to-static-html/trunk/export-wp-page-to-static-html.php
r3479013 r3487855 4 4 * Plugin URI: https://myrecorp.com 5 5 * 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.16 * Version: 6.0.7.0 7 7 * Author: ReCorp 8 8 * Author URI: https://www.upwork.com/fl/rayhan1 … … 20 20 load_plugin_textdomain('wp-to-html', false, dirname(plugin_basename(__FILE__)) . '/languages'); 21 21 }); 22 define('WP_TO_HTML_VERSION', '6.0. 6.1');22 define('WP_TO_HTML_VERSION', '6.0.7.0'); 23 23 define('WP_TO_HTML_PATH', plugin_dir_path(__FILE__)); 24 24 define('WP_TO_HTML_URL', plugin_dir_url(__FILE__)); … … 161 161 162 162 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 */ 178 function 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 163 197 register_activation_hook(__FILE__, function() { 164 198 $installed_version = get_option('wp_to_html_version', ''); … … 184 218 // then bump the stored version. 185 219 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 } 187 224 update_option('wp_to_html_version', WP_TO_HTML_VERSION, false); 188 225 } … … 196 233 197 234 /** 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). 200 238 */ 201 239 add_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 205 240 if (!current_user_can('manage_options')) return; 206 241 if (wp_doing_ajax() || wp_doing_cron()) return; … … 398 433 update_option('wp_to_html_version', $current_version, false); 399 434 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)) { 402 437 set_transient('wp_to_html_redirect_to_whats_new', 1, 60); 438 update_option('wp_to_html_whats_new_version', $current_version, false); 403 439 } 404 440 } -
export-wp-page-to-static-html/trunk/includes/class-admin.php
r3479004 r3487855 275 275 <div class="eh-seg" role="tablist" aria-label="<?php esc_attr_e('What to export', 'wp-to-html'); ?>"> 276 276 <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'); ?> 278 278 </button> 279 279 -
export-wp-page-to-static-html/trunk/includes/class-core.php
r3479004 r3487855 70 70 } 71 71 } 72 // public function process_background() {73 // global $wpdb;74 75 // // ✅ Define tables FIRST76 // $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 row81 // $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 idle99 // if ($status->state === 'stopped') {100 101 // // Clear queue and assets102 // $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 counters130 // $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 them139 // // '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 work153 // $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 162 72 163 73 private function update_status(array $data) { … … 432 342 'updated_at' => current_time('mysql'), 433 343 ], ['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 } 434 356 } 435 357 … … 450 372 $stats['updated_at'] = time(); 451 373 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 } 452 389 453 390 // ------------------------------------------------------------------ … … 662 599 $no_failures = (($failed_urls + $failed_assets) === 0); 663 600 $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 ]); 664 612 } else { 665 613 // Schedule immediately so the bg nudge (fired after lock release) can pick it up. … … 667 615 wp_schedule_single_event(time(), 'wp_to_html_process_event'); 668 616 $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 { 670 629 671 630 if (Advanced_Debugger::enabled()) { … … 995 954 996 955 } 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 997 1130 /** 998 1131 * Watchdog: reclaim stuck rows left in `processing` due to PHP fatals/timeouts/network hangs. -
export-wp-page-to-static-html/trunk/includes/class-exporter.php
r3479013 r3487855 106 106 } 107 107 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' => -1131 ]);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 URLs140 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 181 108 182 109 /** … … 218 145 } 219 146 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 }251 147 252 148 /** … … 555 451 $last_phase = (string) get_option('wp_to_html_queue_build_last_phase', ''); 556 452 557 if ($ins - $last_logged >= $b s|| $ph !== $last_phase) {453 if ($ins - $last_logged >= $batch_size || $ph !== $last_phase) { 558 454 if (WP_TO_HTML_DEBUG) { 559 455 $this->log('Queue build tick: phase=' . $ph . ' inserted=' . $ins); … … 1636 1532 } 1637 1533 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 }1661 1534 1662 1535 private function rewrite_dom_paths($html, $dot) { -
export-wp-page-to-static-html/trunk/includes/class-rest.php
r3479013 r3487855 1241 1241 wp_clear_scheduled_hook('wp_to_html_process_event'); 1242 1242 1243 try { (new \WpToHtml\Exporter())->log_public('Export paused by the user.'); } catch (\Throwable $e) {} 1244 1243 1245 return ['message' => __('Paused', 'wp-to-html')]; 1244 1246 } … … 1254 1256 ['id' => 1] 1255 1257 ); 1258 1259 try { (new \WpToHtml\Exporter())->log_public('Export resumed by the user.'); } catch (\Throwable $e) {} 1256 1260 1257 1261 wp_schedule_single_event(time(), 'wp_to_html_process_event'); … … 1293 1297 global $wpdb; 1294 1298 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 1295 1311 $wpdb->update( 1296 $ wpdb->prefix . 'wp_to_html_status',1312 $status_table, 1297 1313 [ 1298 1314 'state' => 'stopped', … … 1304 1320 wp_clear_scheduled_hook('wp_to_html_process_event'); 1305 1321 1322 try { (new \WpToHtml\Exporter())->log_public('Export stopped by the user.'); } catch (\Throwable $e) {} 1323 1306 1324 // Best-effort cleanup of temp export user. 1307 1325 $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 } 1308 1342 1309 1343 return ['message' => __('Stopped', 'wp-to-html')]; -
export-wp-page-to-static-html/trunk/includes/class-whats-new.php
r3479013 r3487855 296 296 <div class="wth-version"> 297 297 <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> 299 299 </div> 300 300 </div> 301 301 302 <!-- 6.0. 6.0 Changelog Cards -->302 <!-- 6.0.7.0 Changelog Cards --> 303 303 <div class="wth-cards"> 304 304 305 <!-- Added: PDF exporting-->305 <!-- Fixed: Stop button halts background immediately --> 306 306 <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> 309 309 </div> 310 310 <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 --> 317 317 <div class="wth-card"> 318 318 <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"><p olyline 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> 320 320 </div> 321 321 <div class="wth-card-body"> 322 322 <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('E nhanced 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 --> 328 328 <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> 331 331 </div> 332 332 <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> 338 388 </div> 339 389
Note: See TracChangeset
for help on using the changeset viewer.