Changeset 3484943
- Timestamp:
- 03/17/2026 04:21:27 PM (2 weeks ago)
- Location:
- sqm-views
- Files:
-
- 64 added
- 6 deleted
- 14 edited
-
assets/screenshot-1.png (modified) (previous)
-
assets/screenshot-2.png (modified) (previous)
-
assets/screenshot-3.png (modified) (previous)
-
assets/screenshot-4.png (modified) (previous)
-
tags/1.2.4 (added)
-
tags/1.2.4/CHANGELOG.md (added)
-
tags/1.2.4/INSTALL_DROPIN.txt (added)
-
tags/1.2.4/LICENSE.txt (added)
-
tags/1.2.4/README.md (added)
-
tags/1.2.4/artifacts (added)
-
tags/1.2.4/artifacts/dashboard-1.2.4.bundle.js (added)
-
tags/1.2.4/artifacts/dashboard-1.2.4.bundle.libs.js (added)
-
tags/1.2.4/artifacts/dashboard-1.2.4.bundle.min.js (added)
-
tags/1.2.4/artifacts/sqm-views-pages-1.2.4.js (added)
-
tags/1.2.4/artifacts/sqm-views-pages-1.2.4.min.js (added)
-
tags/1.2.4/assets (added)
-
tags/1.2.4/assets/admin (added)
-
tags/1.2.4/assets/admin/processing.js (added)
-
tags/1.2.4/composer.json (added)
-
tags/1.2.4/composer.lock (added)
-
tags/1.2.4/dropin (added)
-
tags/1.2.4/dropin/sqm-views-pages.template (added)
-
tags/1.2.4/languages (added)
-
tags/1.2.4/languages/sqm-views.pot (added)
-
tags/1.2.4/readme.txt (added)
-
tags/1.2.4/sqm-views-process-statistics.php (added)
-
tags/1.2.4/sqm-views.php (added)
-
tags/1.2.4/src (added)
-
tags/1.2.4/src/DocumentSchemas.php (added)
-
tags/1.2.4/src/Logger.php (added)
-
tags/1.2.4/src/ProcessorResponse.php (added)
-
tags/1.2.4/src/RequestValidator.php (added)
-
tags/1.2.4/src/SQMViewsCLI.php (added)
-
tags/1.2.4/src/SQMViewsCron.php (added)
-
tags/1.2.4/src/SQMViewsEvent.php (added)
-
tags/1.2.4/src/SQMViewsRapidProcessor.php (added)
-
tags/1.2.4/src/SQMViewsRecord.php (added)
-
tags/1.2.4/src/SQMViewsSettings.php (added)
-
tags/1.2.4/src/includes (added)
-
tags/1.2.4/src/includes/activation.php (added)
-
tags/1.2.4/src/includes/charts-api.php (added)
-
tags/1.2.4/src/includes/rest-api.php (added)
-
tags/1.2.4/src/includes/tracker.php (added)
-
tags/1.2.4/src/includes/utils.php (added)
-
tags/1.2.4/uninstall.php (added)
-
tags/1.2.4/vendor (added)
-
tags/1.2.4/vendor/autoload.php (added)
-
tags/1.2.4/vendor/composer (added)
-
tags/1.2.4/vendor/composer/ClassLoader.php (added)
-
tags/1.2.4/vendor/composer/InstalledVersions.php (added)
-
tags/1.2.4/vendor/composer/LICENSE (added)
-
tags/1.2.4/vendor/composer/autoload_classmap.php (added)
-
tags/1.2.4/vendor/composer/autoload_files.php (added)
-
tags/1.2.4/vendor/composer/autoload_namespaces.php (added)
-
tags/1.2.4/vendor/composer/autoload_psr4.php (added)
-
tags/1.2.4/vendor/composer/autoload_real.php (added)
-
tags/1.2.4/vendor/composer/autoload_static.php (added)
-
tags/1.2.4/vendor/composer/installed.json (added)
-
tags/1.2.4/vendor/composer/installed.php (added)
-
tags/1.2.4/vendor/composer/platform_check.php (added)
-
trunk/CHANGELOG.md (modified) (5 diffs)
-
trunk/INSTALL_DROPIN.txt (added)
-
trunk/Makefile.mk (deleted)
-
trunk/README.md (modified) (2 diffs)
-
trunk/artifacts/dashboard-1.1.8.bundle.js (deleted)
-
trunk/artifacts/dashboard-1.1.8.bundle.libs.js (deleted)
-
trunk/artifacts/dashboard-1.1.8.bundle.min.js (deleted)
-
trunk/artifacts/dashboard-1.2.4.bundle.js (added)
-
trunk/artifacts/dashboard-1.2.4.bundle.libs.js (added)
-
trunk/artifacts/dashboard-1.2.4.bundle.min.js (added)
-
trunk/artifacts/sqm-views-pages-1.1.8.js (deleted)
-
trunk/artifacts/sqm-views-pages-1.1.8.min.js (deleted)
-
trunk/artifacts/sqm-views-pages-1.2.4.js (added)
-
trunk/artifacts/sqm-views-pages-1.2.4.min.js (added)
-
trunk/composer.json (modified) (1 diff)
-
trunk/dropin (added)
-
trunk/dropin/sqm-views-pages.template (added)
-
trunk/readme.txt (modified) (8 diffs)
-
trunk/sqm-views.php (modified) (1 diff)
-
trunk/src/SQMViewsSettings.php (modified) (3 diffs)
-
trunk/src/includes/activation.php (modified) (12 diffs)
-
trunk/src/includes/charts-api.php (modified) (1 diff)
-
trunk/src/includes/utils.php (modified) (1 diff)
-
trunk/vendor/composer/installed.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
sqm-views/trunk/CHANGELOG.md
r3451552 r3484943 13 13 ## [Unreleased] 14 14 15 ## [1.2.4] - 2026-03-16 16 17 ### Added 18 - Dashboard: Table view — new chart type showing data in a tabular format with sorting 19 - Dashboard: URL state syncing — chart filters and chart type are persisted in the URL for shareable links 20 - Dashboard: CSV and PNG export — export chart data as CSV or save the chart as a PNG image 21 - Dashboard: Dynamic Y-axis rescaling — Y-axis adjusts automatically when toggling series in the legend, making smaller data visible after hiding large series 22 - Dashboard: Clickable legend links — article IDs in the "By Content" legend are now links to the article page; clicking the title still toggles the series 23 - Charts API: Article URLs — the by_content series response now includes a url field with the post permalink 24 - Drop-in: Versioned archive — the build script now produces a separate versioned drop-in zip for the fast endpoint 25 - Drop-in added to the plugin as sqm-views-pages.template, added functionality to install drop-in from the admin interface 26 27 ### Changed 28 - Charts API: simplified post title resolution to individual cached queries instead of batch lookups 29 - Drop-in: applied the same security measures as the main plugin (input validation, sanitization) 30 - Ensured ABSPATH and SQMVIEWS_WP_UPLOADS are defined before utils.php is loaded via autoloader 31 32 --- 33 34 15 35 ## [1.1.9] - 2026-02-01 16 36 First official release … … 54 74 ### Compatibility 55 75 56 - Updated t ested WordPress version to 6.976 - Updated the tested WordPress version to 6.9 57 77 58 78 ### Build … … 66 86 67 87 ### Changed 68 Minor update. Readme file improved. Screenshots removed from zip file.88 Minor update. Readme file improved. Screenshots removed from a zip file. 69 89 70 90 --- … … 88 108 - Test endpoint now returns lightweight response on fast endpoint (skips diagnostics) 89 109 - WordPress filter hooks (session_timeout, ping_interval) now bypassed on fast endpoint for performance 90 - Default log level changed from INFO to ERROR in production (DEBUG when WP_DEBUG is true)91 - Admin timestamp display now uses site timezone instead of UTC110 - The default log level changed from INFO to ERROR in production (DEBUG when WP_DEBUG is true) 111 - Admin timestamp display now uses the site timezone instead of UTC 92 112 93 113 ### Technical Details 94 - Test endpoint response includes 'endpoint' field ('fast' or 'api')114 - Test endpoint response includes the 'endpoint' field ('fast' or 'api') 95 115 - Fast endpoint identified by constant defined in sqm-views-pages.php 96 116 … … 99 119 100 120 ## [1.0.7] - 2025-10-26 101 - Maintenance release -version bump only121 - Maintenance release – version bump only 102 122 - No functional changes to the plugin 103 123 -
sqm-views/trunk/README.md
r3451552 r3484943 711 711 This plugin follows [Semantic Versioning](https://semver.org/) (MAJOR.MINOR.PATCH). 712 712 713 **Current Version:** 1. 1.9713 **Current Version:** 1.2.4 714 714 715 715 ### Version Check in Code … … 730 730 --- 731 731 732 **Version:** 1. 1.9732 **Version:** 1.2.4 733 733 **Author:** Pavel Khloponin 734 734 **License:** GPLv3 or later -
sqm-views/trunk/composer.json
r3451552 r3484943 2 2 "name": "sqm-views/sqm-views", 3 3 "description": "Plugin to collect, store, and use pageviews.", 4 "version": "1. 1.9",4 "version": "1.2.4", 5 5 "_comment": "SQMVIEWS_BUILD_GLOBAL_VERSION - WARNING: Do NOT edit the version field manually! Use the bump-version.sh script: ./bump-version.sh patch|minor|major", 6 6 "type": "wordpress-plugin", -
sqm-views/trunk/readme.txt
r3451552 r3484943 5 5 Tested up to: 6.9 6 6 Requires PHP: 8.1 7 Stable tag: 1. 1.97 Stable tag: 1.2.4 8 8 License: GPLv3 9 9 License URI: https://www.gnu.org/licenses/gpl-3.0.html … … 32 32 * Ultra-fast tracking endpoint (bypasses a core load) 33 33 * Minimal overhead (~5ms per request with the drop-in script) 34 * File-based batch processing (no database requests during pageview event collection, only during cron processing)34 * File-based batch processing (no database requests during the pageview event collection, only during cron processing) 35 35 * Efficient data storage with automatic archiving 36 36 * Configurable background processing via WP-Cron … … 118 118 = Why Choose SQMViews? = 119 119 120 **vs External Analytics:**120 **vs. External Analytics:** 121 121 * No external JavaScript libraries 122 122 * No tracking cookies required … … 166 166 * Select which post types to track 167 167 * Choose taxonomy archives to monitor 168 * Select JavaScript loading method (inline/external)168 * Select a JavaScript loading method (inline/external) 169 169 170 170 2. **Set Processing Schedule** - Go to **SQMViews → Processing** 171 * Choose cron interval (hourly recommended)171 * Choose a cron interval (hourly recommended) 172 172 * Verify endpoint status (fast endpoint preferred) 173 173 … … 182 182 183 183 1. **Enable Fast Endpoint** (automatic on most hosts) 184 * Copy sqm-views-pages.php in WordPress root folder (next to wp-config.php)184 * Copy sqm-views-pages.php in the WordPress root folder (next to wp-config.php) 185 185 * Bypasses WordPress load 186 186 * Reduces tracking overhead from ~250ms to ~5ms … … 382 382 * `sqm_views_trackable_taxonomies` - Customize which taxonomies can be tracked (1 param: $taxonomies) 383 383 * `sqm_views_data_taxonomies` - Modify taxonomies included in tracking data (1 param: $taxonomies) 384 * `sqm_views_encryption_key` - Override encryption key (1 param: $key)384 * `sqm_views_encryption_key` - Override the encryption key (1 param: $key) 385 385 * `sqm_views_tracker_endpoint` - Customize tracking endpoint URL (2 params: $endpoint, $saved_endpoint) 386 386 * `sqm_views_minified_js` - Control whether to use minified tracker JS (1 param: $use_min) 387 * `sqm_views_tracker_script_path` - Customize t racker script file path (2 params: $path, $use_min)387 * `sqm_views_tracker_script_path` - Customize the tracker script file path (2 params: $path, $use_min) 388 388 * `sqm_views_tracker_script_url` - Customize tracker script URL (2 params: $url, $use_min) 389 389 * `sqm_views_inline_js` - Control whether to inline tracker JS (1 param: $use_inline) … … 394 394 * `sqm_views_calculated_metrics` - Modify calculated metrics (2 params: $metrics, $records) 395 395 * `sqm_views_session_timeout` - Customize session inactivity timeout (1 param: $timeout) 396 * `sqm_views_ping_interval` - Customize ping interval for session tracking (1 param: $interval)396 * `sqm_views_ping_interval` - Customize the ping interval for session tracking (1 param: $interval) 397 397 398 398 **Dashboard:** … … 419 419 * `sqm_views_activated` - Fires after plugin activation (0 params) 420 420 * `sqm_views_upgraded` - Fires after plugin upgrade (2 params: $from_version, $to_version) 421 * `sqm_views_before_upgrade` - Fires before upgrade process (2 params: $from_version, $to_version)422 * `sqm_views_after_upgrade` - Fires after upgrade process (2 params: $from_version, $to_version)421 * `sqm_views_before_upgrade` - Fires before the upgrade process (2 params: $from_version, $to_version) 422 * `sqm_views_after_upgrade` - Fires after the upgrade process (2 params: $from_version, $to_version) 423 423 * `sqm_views_uninstalled` - Fires during plugin uninstallation (0 params) 424 424 -
sqm-views/trunk/sqm-views.php
r3451552 r3484943 12 12 * Plugin URI: https://searchquerymaster.com/sqm-views 13 13 * Description: Lightweight page view tracking and engagement analytics with interactive dashboards. No external services, privacy-focused. 14 * Version: 1. 1.914 * Version: 1.2.4 15 15 * Author: Pavel Khloponin 16 16 * Author URI: https://github.com/pkhlop -
sqm-views/trunk/src/SQMViewsSettings.php
r3451552 r3484943 46 46 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) ); 47 47 add_action( 'wp_ajax_sqm_views_process_batch', array( $this, 'ajax_process_batch' ) ); 48 add_action( 'wp_ajax_sqm_views_install_dropin', array( $this, 'ajax_install_dropin' ) ); 48 49 } 49 50 … … 377 378 <td><?php echo wp_kses_post( sqm_views_check_endpoint() ); ?></td> 378 379 </tr> 380 <tr> 381 <th scope="row"><?php esc_html_e( 'Fast Endpoint Drop-in', 'sqm-views' ); ?></th> 382 <td> 383 <?php 384 $dropin_source = plugin_dir_path( SQMVIEWS_PLUGIN_FILE ) . 'dropin/sqm-views-pages.template'; 385 $dropin_target = ABSPATH . 'sqm-views-pages.php'; 386 $dropin_exists = file_exists( $dropin_target ); 387 $source_exists = file_exists( $dropin_source ); 388 $dropin_writable = wp_is_writable( ABSPATH ); 389 390 if ( $dropin_exists ) { 391 echo '<span style="color: green;">✔ ' . esc_html__( 'Installed', 'sqm-views' ) . '</span>'; 392 } else { 393 echo '<span style="color: orange;">⚠ ' . esc_html__( 'Not installed', 'sqm-views' ) . '</span>'; 394 } 395 396 if ( $source_exists && $dropin_writable ) { 397 $button_label = $dropin_exists 398 ? __( 'Reinstall Drop-in', 'sqm-views' ) 399 : __( 'Install Drop-in', 'sqm-views' ); 400 $button_class = $dropin_exists ? 'button' : 'button button-primary'; 401 ?> 402 <button type="button" id="sqm-views-install-dropin" 403 class="<?php echo esc_attr( $button_class ); ?>" 404 style="margin-left: 10px;" 405 data-nonce="<?php echo esc_attr( wp_create_nonce( 'sqm_views_install_dropin' ) ); ?>"> 406 <?php echo esc_html( $button_label ); ?> 407 </button> 408 <span id="sqm-views-dropin-status" style="margin-left: 10px;"></span> 409 <script> 410 (function() { 411 var btn = document.getElementById('sqm-views-install-dropin'); 412 if (!btn) return; 413 btn.addEventListener('click', function() { 414 btn.disabled = true; 415 var status = document.getElementById('sqm-views-dropin-status'); 416 status.textContent = '<?php echo esc_js( __( 'Installing...', 'sqm-views' ) ); ?>'; 417 var xhr = new XMLHttpRequest(); 418 xhr.open('POST', ajaxurl); 419 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 420 xhr.onload = function() { 421 try { 422 var resp = JSON.parse(xhr.responseText); 423 if (resp.success) { 424 status.innerHTML = '<span style="color:green;">' + resp.data.message + '</span>'; 425 setTimeout(function() { location.reload(); }, 1500); 426 } else { 427 status.innerHTML = '<span style="color:red;">' + (resp.data.message || 'Failed') + '</span>'; 428 btn.disabled = false; 429 } 430 } catch(e) { 431 status.innerHTML = '<span style="color:red;">Unexpected error</span>'; 432 btn.disabled = false; 433 } 434 }; 435 xhr.onerror = function() { 436 status.innerHTML = '<span style="color:red;">Network error</span>'; 437 btn.disabled = false; 438 }; 439 xhr.send('action=sqm_views_install_dropin&nonce=' + btn.dataset.nonce); 440 }); 441 })(); 442 </script> 443 <?php 444 } elseif ( ! $source_exists ) { 445 echo '<br><small>' . esc_html__( 'Drop-in template not found in plugin. Please reinstall the plugin.', 'sqm-views' ) . '</small>'; 446 } elseif ( ! $dropin_writable ) { 447 echo '<br><small>' . esc_html__( 'WordPress root directory is not writable. Please install manually.', 'sqm-views' ) . '</small>'; 448 } 449 ?> 450 </td> 451 </tr> 379 452 </table> 380 453 </div> … … 708 781 709 782 /** 783 * AJAX handler for drop-in installation. 784 * 785 * Copies the bundled .php.dist template to the WordPress root as sqm-views-pages.php. 786 * 787 * @return void 788 */ 789 public function ajax_install_dropin() { 790 if ( ! current_user_can( 'manage_options' ) ) { 791 wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'sqm-views' ) ) ); 792 } 793 794 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'sqm_views_install_dropin' ) ) { 795 wp_send_json_error( array( 'message' => __( 'Invalid nonce.', 'sqm-views' ) ) ); 796 } 797 798 $source = plugin_dir_path( SQMVIEWS_PLUGIN_FILE ) . 'dropin/sqm-views-pages.template'; 799 $target = ABSPATH . 'sqm-views-pages.php'; 800 801 if ( ! file_exists( $source ) ) { 802 wp_send_json_error( array( 'message' => __( 'Drop-in template not found. Please reinstall the plugin.', 'sqm-views' ) ) ); 803 } 804 805 if ( ! wp_is_writable( ABSPATH ) ) { 806 wp_send_json_error( array( 'message' => __( 'WordPress root directory is not writable.', 'sqm-views' ) ) ); 807 } 808 809 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_copy -- Copying drop-in to WordPress root requires direct file operation. 810 if ( copy( $source, $target ) ) { 811 // Re-check endpoint after installation. 812 sqm_views_check_endpoint(); 813 wp_send_json_success( array( 'message' => __( 'Drop-in installed successfully.', 'sqm-views' ) ) ); 814 } else { 815 wp_send_json_error( array( 'message' => __( 'Failed to copy drop-in file.', 'sqm-views' ) ) ); 816 } 817 } 818 819 /** 710 820 * AJAX handler for batch processing. 711 821 * -
sqm-views/trunk/src/includes/activation.php
r3451552 r3484943 13 13 14 14 define( 'SQMVIEWS_REST_TIMEOUT', 5 ); 15 16 /** 17 * Checks if an index exists on a table. 18 * 19 * Uses information_schema.STATISTICS which is compatible with both MySQL and MariaDB. 20 * 21 * @param string $table_name The table name (with prefix). 22 * @param string $index_name The index name to check. 23 * 24 * @return bool True if index exists, false otherwise. 25 */ 26 function index_exists( string $table_name, string $index_name ): bool { 27 global $wpdb; 28 29 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Schema check during activation. 30 $result = $wpdb->get_var( 31 $wpdb->prepare( 32 'SELECT COUNT(*) FROM information_schema.STATISTICS 33 WHERE TABLE_SCHEMA = DATABASE() 34 AND TABLE_NAME = %s 35 AND INDEX_NAME = %s', 36 $table_name, 37 $index_name 38 ) 39 ); 40 41 return (int) $result > 0; 42 } 43 44 /** 45 * Checks if a foreign key constraint exists on a table. 46 * 47 * Uses information_schema which is compatible with both MySQL and MariaDB. 48 * 49 * @param string $table_name The table name (with prefix). 50 * @param string $column_name The column name that has the foreign key. 51 * 52 * @return bool True if foreign key exists, false otherwise. 53 */ 54 function foreign_key_exists( string $table_name, string $column_name ): bool { 55 global $wpdb; 56 57 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Schema check during activation. 58 $result = $wpdb->get_var( 59 $wpdb->prepare( 60 'SELECT COUNT(*) FROM information_schema.KEY_COLUMN_USAGE 61 WHERE TABLE_SCHEMA = DATABASE() 62 AND TABLE_NAME = %s 63 AND COLUMN_NAME = %s 64 AND REFERENCED_TABLE_NAME IS NOT NULL', 65 $table_name, 66 $column_name 67 ) 68 ); 69 70 return (int) $result > 0; 71 } 15 72 16 73 /** … … 97 154 98 155 /** 99 * Creates required database tables 156 * Creates required database tables, indices and foreign keys 100 157 * 101 158 * @return void … … 114 171 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 115 172 /** @lang MySQL */ ' 116 CREATE TABLE IF NOT EXISTS%i (173 CREATE TABLE %i ( 117 174 `tid` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, 118 175 `tgroup` VARCHAR(20) NOT NULL, … … 120 177 `wpid` BIGINT UNSIGNED NOT NULL DEFAULT 0, 121 178 `altid` VARCHAR(50), 122 PRIMARY KEY (`tid`));',179 PRIMARY KEY (`tid`));', 123 180 "{$prefix}sqm_views_trackables" 124 181 ) … … 129 186 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 130 187 /** @lang MySQL */ ' 131 CREATE TABLE IF NOT EXISTS%i (188 CREATE TABLE %i ( 132 189 `gid` VARCHAR(50) NOT NULL, 133 190 `tid` BIGINT UNSIGNED NOT NULL, … … 142 199 `exit` VARCHAR(20) NOT NULL, 143 200 `utc_moment` DATETIME NOT NULL, 144 PRIMARY KEY (`gid`)201 PRIMARY KEY (`gid`) 145 202 );', 146 203 "{$prefix}sqm_views_records" … … 151 208 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 152 209 /** @lang MySQL */ ' 153 CREATE TABLE IF NOT EXISTS%i (210 CREATE TABLE %i ( 154 211 `date` DATE NOT NULL, 155 212 `eid` BIGINT UNSIGNED NOT NULL, … … 160 217 `low_freq` BIGINT UNSIGNED NOT NULL, 161 218 `count` BIGINT UNSIGNED NOT NULL, 162 PRIMARY KEY (`date`, `eid`, `tid`)219 PRIMARY KEY (`date`, `eid`, `tid`) 163 220 );', 164 221 "{$prefix}sqm_views_daily" … … 170 227 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 171 228 /** @lang MySQL */ ' 172 CREATE TABLE IF NOT EXISTS%i (229 CREATE TABLE %i ( 173 230 `eid` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, 174 231 `name` VARCHAR(20) NOT NULL UNIQUE, 175 PRIMARY KEY (`eid`)232 PRIMARY KEY (`eid`) 176 233 );', 177 234 "{$prefix}sqm_views_events" … … 198 255 $suppress_errors = $wpdb->suppress_errors( true ); 199 256 200 // Index creation - errors are suppressed to allow safe re-activation. 201 // If indexes already exist, the queries will fail silently (expected behavior). 202 $index_results[] = $wpdb->query( 203 $wpdb->prepare( 204 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 205 /** @lang MySQL */ 206 'CREATE INDEX `sqm_views_trackables_index_0` ON %i (`tgroup`);', 207 "{$prefix}sqm_views_trackables" 208 ) 209 ); 210 211 $index_results[] = $wpdb->query( 212 $wpdb->prepare( 213 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 214 /** @lang MySQL */ 215 'CREATE INDEX `sqm_views_trackables_index_1` ON %i (`tgroup`, `ttype`, `wpid`, `altid`);', 216 "{$prefix}sqm_views_trackables" 217 ) 218 ); 219 220 $index_results[] = $wpdb->query( 221 $wpdb->prepare( 222 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 223 /** @lang MySQL */ 224 'CREATE INDEX `sqm_views_records_index_0` ON %i (`tid`);', 225 "{$prefix}sqm_views_records" 226 ) 227 ); 228 229 $index_results[] = $wpdb->query( 230 $wpdb->prepare( 231 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 232 /** @lang MySQL */ 233 'CREATE INDEX `sqm_views_records_index_1` ON %i (`gid`);', 234 "{$prefix}sqm_views_records" 235 ) 236 ); 237 238 $index_results[] = $wpdb->query( 239 $wpdb->prepare( 240 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 241 /** @lang MySQL */ 242 'CREATE INDEX `sqm_views_daily_index_0` ON %i (`date`, `eid`, `tid`);', 243 "{$prefix}sqm_views_daily" 244 ) 245 ); 246 247 $index_results[] = $wpdb->query( 248 $wpdb->prepare( 249 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 250 /** @lang MySQL */ 251 'ALTER TABLE %i ADD FOREIGN KEY(`tid`) REFERENCES %i(`tid`) ON UPDATE NO ACTION ON DELETE NO ACTION;', 252 "{$prefix}sqm_views_records", 253 "{$prefix}sqm_views_trackables" 254 ) 255 ); 256 257 $index_results[] = $wpdb->query( 258 $wpdb->prepare( 259 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 260 /** @lang MySQL */ 261 'ALTER TABLE %i ADD FOREIGN KEY(`eid`) REFERENCES %i(`eid`) ON UPDATE NO ACTION ON DELETE NO ACTION;', 262 "{$prefix}sqm_views_records", 263 "{$prefix}sqm_views_events" 264 ) 265 ); 266 267 $index_results[] = $wpdb->query( 268 $wpdb->prepare( 269 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 270 /** @lang MySQL */ 271 'ALTER TABLE %i ADD FOREIGN KEY(`eid`) REFERENCES %i(`eid`) ON UPDATE NO ACTION ON DELETE NO ACTION;', 272 "{$prefix}sqm_views_daily", 273 "{$prefix}sqm_views_events" 274 ) 275 ); 276 277 $index_results[] = $wpdb->query( 278 $wpdb->prepare( 279 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 280 /** @lang MySQL */ 281 'ALTER TABLE %i ADD FOREIGN KEY(`tid`) REFERENCES %i(`tid`) ON UPDATE NO ACTION ON DELETE NO ACTION;', 282 "{$prefix}sqm_views_daily", 283 "{$prefix}sqm_views_trackables" 284 ) 285 ); 257 // Index creation - skip if already exists to avoid slow operations on large tables. 258 if ( ! index_exists( "{$prefix}sqm_views_trackables", 'sqm_views_trackables_index_0' ) ) { 259 $index_results[] = $wpdb->query( 260 $wpdb->prepare( 261 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 262 /** @lang MySQL */ 263 'CREATE INDEX `sqm_views_trackables_index_0` ON %i (`tgroup`);', 264 "{$prefix}sqm_views_trackables" 265 ) 266 ); 267 } 268 269 if ( ! index_exists( "{$prefix}sqm_views_trackables", 'sqm_views_trackables_index_1' ) ) { 270 $index_results[] = $wpdb->query( 271 $wpdb->prepare( 272 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 273 /** @lang MySQL */ 274 'CREATE INDEX `sqm_views_trackables_index_1` ON %i (`tgroup`, `ttype`, `wpid`, `altid`);', 275 "{$prefix}sqm_views_trackables" 276 ) 277 ); 278 } 279 280 if ( ! index_exists( "{$prefix}sqm_views_records", 'sqm_views_records_index_0' ) ) { 281 $index_results[] = $wpdb->query( 282 $wpdb->prepare( 283 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 284 /** @lang MySQL */ 285 'CREATE INDEX `sqm_views_records_index_0` ON %i (`tid`);', 286 "{$prefix}sqm_views_records" 287 ) 288 ); 289 } 290 291 if ( ! index_exists( "{$prefix}sqm_views_records", 'sqm_views_records_index_1' ) ) { 292 $index_results[] = $wpdb->query( 293 $wpdb->prepare( 294 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 295 /** @lang MySQL */ 296 'CREATE INDEX `sqm_views_records_index_1` ON %i (`gid`);', 297 "{$prefix}sqm_views_records" 298 ) 299 ); 300 } 301 302 if ( ! index_exists( "{$prefix}sqm_views_daily", 'sqm_views_daily_index_0' ) ) { 303 $index_results[] = $wpdb->query( 304 $wpdb->prepare( 305 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 306 /** @lang MySQL */ 307 'CREATE INDEX `sqm_views_daily_index_0` ON %i (`date`, `eid`, `tid`);', 308 "{$prefix}sqm_views_daily" 309 ) 310 ); 311 } 312 313 // Foreign key creation - skip if already exists to avoid slow ALTER TABLE on large tables. 314 if ( ! foreign_key_exists( "{$prefix}sqm_views_records", 'tid' ) ) { 315 $index_results[] = $wpdb->query( 316 $wpdb->prepare( 317 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 318 /** @lang MySQL */ 319 'ALTER TABLE %i ADD FOREIGN KEY(`tid`) REFERENCES %i(`tid`) ON UPDATE NO ACTION ON DELETE NO ACTION;', 320 "{$prefix}sqm_views_records", 321 "{$prefix}sqm_views_trackables" 322 ) 323 ); 324 } 325 326 if ( ! foreign_key_exists( "{$prefix}sqm_views_records", 'eid' ) ) { 327 $index_results[] = $wpdb->query( 328 $wpdb->prepare( 329 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 330 /** @lang MySQL */ 331 'ALTER TABLE %i ADD FOREIGN KEY(`eid`) REFERENCES %i(`eid`) ON UPDATE NO ACTION ON DELETE NO ACTION;', 332 "{$prefix}sqm_views_records", 333 "{$prefix}sqm_views_events" 334 ) 335 ); 336 } 337 338 if ( ! foreign_key_exists( "{$prefix}sqm_views_daily", 'eid' ) ) { 339 $index_results[] = $wpdb->query( 340 $wpdb->prepare( 341 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 342 /** @lang MySQL */ 343 'ALTER TABLE %i ADD FOREIGN KEY(`eid`) REFERENCES %i(`eid`) ON UPDATE NO ACTION ON DELETE NO ACTION;', 344 "{$prefix}sqm_views_daily", 345 "{$prefix}sqm_views_events" 346 ) 347 ); 348 } 349 350 if ( ! foreign_key_exists( "{$prefix}sqm_views_daily", 'tid' ) ) { 351 $index_results[] = $wpdb->query( 352 $wpdb->prepare( 353 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- IDE language hint. 354 /** @lang MySQL */ 355 'ALTER TABLE %i ADD FOREIGN KEY(`tid`) REFERENCES %i(`tid`) ON UPDATE NO ACTION ON DELETE NO ACTION;', 356 "{$prefix}sqm_views_daily", 357 "{$prefix}sqm_views_trackables" 358 ) 359 ); 360 } 286 361 287 362 $index_results[] = $wpdb->query( … … 434 509 * Drops all plugin database tables 435 510 * 511 * Tables are dropped in correct order to respect foreign key constraints: 512 * 1. daily, records (reference trackables and events) 513 * 2. trackables, events (referenced by daily and records) 514 * 436 515 * @return void 437 516 */ … … 442 521 443 522 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, Generic.Commenting.DocComment.MissingShort -- Schema operations during plugin uninstall. 523 524 // Disable foreign key checks to allow dropping tables in any order safely. 525 $wpdb->query( 'SET FOREIGN_KEY_CHECKS = 0' ); 526 527 // Drop tables that have foreign keys first. 444 528 $wpdb->query( $wpdb->prepare( /** @lang MySQL */ 'DROP TABLE IF EXISTS %i', "{$prefix}sqm_views_daily" ) ); 529 $wpdb->query( $wpdb->prepare( /** @lang MySQL */ 'DROP TABLE IF EXISTS %i', "{$prefix}sqm_views_records" ) ); 530 // Then drop tables that are referenced by foreign keys. 445 531 $wpdb->query( $wpdb->prepare( /** @lang MySQL */ 'DROP TABLE IF EXISTS %i', "{$prefix}sqm_views_trackables" ) ); 446 $wpdb->query( $wpdb->prepare( /** @lang MySQL */ 'DROP TABLE IF EXISTS %i', "{$prefix}sqm_views_records" ) );447 532 $wpdb->query( $wpdb->prepare( /** @lang MySQL */ 'DROP TABLE IF EXISTS %i', "{$prefix}sqm_views_events" ) ); 533 534 // Re-enable foreign key checks. 535 $wpdb->query( 'SET FOREIGN_KEY_CHECKS = 1' ); 536 448 537 // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, Generic.Commenting.DocComment.MissingShort 449 538 } -
sqm-views/trunk/src/includes/charts-api.php
r3451552 r3484943 299 299 $top_keys = array_slice( array_keys( $totals ), 0, $limit ); 300 300 301 // Resolve post titles and URLs for the top tids via trackables → wp_posts. 302 // Each tid maps to a wpid (WordPress post ID) in the trackables table. 303 // The list is bounded by $limit (max 100), so individual lookups are fine. 304 $title_map = array(); 305 $url_map = array(); 306 foreach ( $top_keys as $k ) { 307 $wpid_row = $wpdb->get_row( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- bounded by $limit, result cached with chart response 308 $wpdb->prepare( 309 /* @lang MySQL */ 310 'SELECT `wpid` FROM %i WHERE `tid` = %d', 311 "{$wpdb->prefix}sqm_views_trackables", 312 intval( $k ) 313 ) 314 ); 315 if ( $wpid_row && $wpid_row->wpid > 0 ) { 316 $wpid = (int) $wpid_row->wpid; 317 $post_title = get_the_title( $wpid ); 318 if ( '' !== $post_title ) { 319 $title_map[ $k ] = $post_title; 320 $url_map[ $k ] = get_permalink( $wpid ); 321 } 322 } 323 } 324 301 325 $series = array(); 302 326 foreach ( $top_keys as $k ) { 303 $series[] = array( 304 'name' => 'tid ' . $k, // SECURITY: Integer key converted to string. 327 $title = isset( $title_map[ $k ] ) ? $title_map[ $k ] : ''; 328 if ( '' !== $title && mb_strlen( $title ) > 40 ) { 329 $title = mb_substr( $title, 0, 37 ) . '...'; 330 } 331 $label = '' !== $title ? '[' . $k . '] ' . $title : 'tid ' . $k; 332 333 $entry = array( 334 'name' => $label, // SECURITY: title from wp_posts, tid is integer. 305 335 'values' => $series_map[ $k ], 306 336 ); 337 if ( isset( $url_map[ $k ] ) ) { 338 $entry['url'] = $url_map[ $k ]; 339 } 340 $series[] = $entry; 307 341 } 308 342 -
sqm-views/trunk/src/includes/utils.php
r3451552 r3484943 30 30 * @since 1.0.0 31 31 */ 32 define( 'SQMVIEWS_BUILD_GLOBAL_VERSION', '1. 1.9' );32 define( 'SQMVIEWS_BUILD_GLOBAL_VERSION', '1.2.4' ); 33 33 34 34 define( 'SQMVIEWS_NAME', 'sqm-views' ); -
sqm-views/trunk/vendor/composer/installed.php
r3451552 r3484943 2 2 'root' => array( 3 3 'name' => 'sqm-views/sqm-views', 4 'pretty_version' => '1. 1.9',5 'version' => '1. 1.9.0',4 'pretty_version' => '1.2.4', 5 'version' => '1.2.4.0', 6 6 'reference' => null, 7 7 'type' => 'wordpress-plugin', … … 12 12 'versions' => array( 13 13 'sqm-views/sqm-views' => array( 14 'pretty_version' => '1. 1.9',15 'version' => '1. 1.9.0',14 'pretty_version' => '1.2.4', 15 'version' => '1.2.4.0', 16 16 'reference' => null, 17 17 'type' => 'wordpress-plugin',
Note: See TracChangeset
for help on using the changeset viewer.