PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 3.5.3
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v3.5.3
4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 All 200 releases
← All changes | includes/Core/Migration.php +85 -186 4.9.23.5.3 View file →
@@ -1,12 +1,8 @@
1 1 <?php
2 -// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange -- one-shot data/schema migrations; caching unwanted.
2 +
3 3 namespace WPDeveloper\BetterDocs\Core;
4 4
5 -if ( ! defined( 'ABSPATH' ) ) {
6 - exit;
7 -}
8 -
9 5 use WP_Query;
10 6 use WPDeveloper\BetterDocs\Utils\Base;
11 7 use WPDeveloper\BetterDocs\Utils\Database;
12 8 use WPDeveloper\BetterDocs\Dependencies\DI\Container;
@@ -11,200 +7,103 @@
11 7 use WPDeveloper\BetterDocs\Utils\Database;
12 8 use WPDeveloper\BetterDocs\Dependencies\DI\Container;
13 9
14 10 class Migration extends Base {
15 - /**
16 - * Database
17 - * @var Database
18 - */
19 - private $database;
20 - private $settings;
21 - private $container;
11 + /**
12 + * Database
13 + * @var Database
14 + */
15 + private $database;
16 + private $settings;
17 + private $container;
22 18
23 - public function __construct( Container $container ) {
24 - $this->container = $container;
25 - $this->database = $container->get( Database::class );
26 - $this->settings = $container->get( Settings::class );
27 - }
19 + public function __construct( Container $container ) {
20 + $this->container = $container;
21 + $this->database = $container->get( Database::class );
22 + $this->settings = $container->get( Settings::class );
23 + }
28 24
29 - public function init( $version ) {
30 - if ( $version > 250 ) {
31 - for ( $_version = 250; $_version <= $version; $_version++ ) {
32 - if ( method_exists( $this, "v$_version" ) ) {
33 - call_user_func( [ $this, "v$_version" ] );
34 - }
35 - }
36 - }
25 + public function init( $version ) {
26 + if( $version > 250 ) {
27 + for( $_version = 250; $_version <= $version; $_version++ ) {
28 + if( method_exists( $this, "v$_version") ) {
29 + call_user_func([$this, "v$_version"]);
30 + }
31 + }
32 + }
37 33
38 - $this->search_migration();
39 - $this->fix_search_table_collation();
40 - $this->backfill_search_keyword_hash();
34 + $this->search_migration();
41 35
42 - /**
43 - * Settings Migration
44 - */
45 - $this->settings->migration( $version );
46 - }
36 + /**
37 + * Settings Migration
38 + */
39 + $this->settings->migration( $version );
40 + }
47 41
48 - public function search_migration() {
49 - global $wpdb;
50 - if ( ! $this->database->get( 'betterdocs_search_data_migration', false ) ) {
51 - $search_data = $this->database->get( 'betterdocs_search_data' );
52 - if ( ! empty( $search_data ) ) {
53 - $search_data_arr = unserialize( $search_data );
54 - foreach ( $search_data_arr as $key => $value ) {
55 - $args = [
56 - 'post_type' => 'docs',
57 - 'post_status' => 'publish',
58 - 'posts_per_page' => -1,
59 - 'suppress_filters' => true, // phpcs:ignore WordPressVIPMinimum.Hooks.PreGetPosts.PreGetPosts,WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters -- migration must run on raw posts without language filters.
60 - 's' => $key
61 - ];
42 + public function search_migration() {
43 + global $wpdb;
44 + if ( ! $this->database->get( 'betterdocs_search_data_migration', false ) ) {
45 + $search_data = $this->database->get( 'betterdocs_search_data' );
46 + if ( ! empty( $search_data ) ) {
47 + $search_data_arr = unserialize( $search_data );
48 + foreach ( $search_data_arr as $key => $value ) {
49 + $args = [
50 + 'post_type' => 'docs',
51 + 'post_status' => 'publish',
52 + 'posts_per_page' => -1,
53 + 'suppress_filters' => true,
54 + 's' => $key
55 + ];
62 56
63 - $loop = new WP_Query( $args );
64 - if ( $loop->have_posts() ) {
65 - $count = $value;
66 - $not_found_count = 0;
67 - } else {
68 - $count = 0;
69 - $not_found_count = $value;
70 - }
57 + $loop = new WP_Query( $args );
58 + if ( $loop->have_posts() ) {
59 + $count = $value;
60 + $not_found_count = 0;
61 + } else {
62 + $count = 0;
63 + $not_found_count = $value;
64 + }
71 65
72 - // Use BINARY comparison to avoid collation mismatch errors
73 - $keyword = $wpdb->get_var(
74 - $wpdb->prepare(
75 - "
66 + $keyword = $wpdb->get_var(
67 + $wpdb->prepare( "
76 68 SELECT keyword
77 69 FROM {$wpdb->prefix}betterdocs_search_keyword
78 - WHERE BINARY keyword = %s",
79 - $key
80 - )
81 - );
70 + WHERE keyword = %s",
71 + $key
72 + )
73 + );
82 74
83 - if ( $keyword == null ) {
84 - $insert = $wpdb->query(
85 - $wpdb->prepare(
86 - "INSERT INTO {$wpdb->prefix}betterdocs_search_keyword
87 - ( keyword, keyword_hash )
88 - VALUES ( %s, %s )",
89 - [
90 - $key,
91 - md5( $key )
92 - ]
93 - )
94 - );
75 + if ( $keyword == NUll ) {
76 + $insert = $wpdb->query(
77 + $wpdb->prepare(
78 + "INSERT INTO {$wpdb->prefix}betterdocs_search_keyword
79 + ( keyword )
80 + VALUES ( %s )",
81 + [
82 + $key
83 + ]
84 + )
85 + );
95 86
96 - if ( $insert ) {
97 - $wpdb->query(
98 - $wpdb->prepare(
99 - "INSERT INTO {$wpdb->prefix}betterdocs_search_log
87 + if ( $insert ) {
88 + $wpdb->query(
89 + $wpdb->prepare(
90 + "INSERT INTO {$wpdb->prefix}betterdocs_search_log
100 91 (keyword_id, count, not_found_count, created_at)
101 92 VALUES (%d, %d, %d, %s)",
102 - [
103 - $wpdb->insert_id,
104 - $count,
105 - $not_found_count,
106 - gmdate( 'Y-m-d' )
107 - ]
108 - )
109 - );
110 - }
111 - }
112 - }
93 + [
94 + $wpdb->insert_id,
95 + $count,
96 + $not_found_count,
97 + date( 'Y-m-d' )
98 + ]
99 + )
100 + );
101 + }
102 + }
103 + }
113 104
114 - $this->database->save( 'betterdocs_search_data_migration', '1.0' );
115 - }
116 - }
117 - }
118 -
119 - /**
120 - * Fix collation issues in search tables
121 - * Converts tables to proper UTF-8 charset and collation to prevent
122 - * "Illegal mix of collations" errors when searching with non-Latin characters
123 - *
124 - * @since 1.0.2
125 - * @return void
126 - */
127 - public function fix_search_table_collation() {
128 - global $wpdb;
129 -
130 - // Check if migration already ran
131 - if ( $this->database->get( 'betterdocs_search_collation_fixed', false ) ) {
132 - return;
133 - }
134 -
135 - // Get the WordPress default charset and collation; restrict to a safe identifier
136 - // alphabet because they're interpolated into ALTER TABLE statements below.
137 - $charset = preg_replace( '/[^A-Za-z0-9_]/', '', $wpdb->charset ? $wpdb->charset : 'utf8mb4' );
138 - $collate = preg_replace( '/[^A-Za-z0-9_]/', '', $wpdb->collate ? $wpdb->collate : 'utf8mb4_unicode_520_ci' );
139 -
140 - // Fix search_keyword table
141 - $search_keyword_table = $wpdb->prefix . 'betterdocs_search_keyword';
142 - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange -- one-shot collation migration; identifiers come from $wpdb only.
143 - if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $search_keyword_table ) ) === $search_keyword_table ) {
144 - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange,PluginCheck.Security.DirectDB.UnescapedDBParameter
145 - $wpdb->query( "ALTER TABLE `{$search_keyword_table}` CONVERT TO CHARACTER SET {$charset} COLLATE {$collate}" );
146 - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange,PluginCheck.Security.DirectDB.UnescapedDBParameter
147 - $wpdb->query( "ALTER TABLE `{$search_keyword_table}` MODIFY keyword TEXT CHARACTER SET {$charset} COLLATE {$collate} NOT NULL" );
148 - }
149 -
150 - // Fix search_log table
151 - $search_log_table = $wpdb->prefix . 'betterdocs_search_log';
152 - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange
153 - if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $search_log_table ) ) === $search_log_table ) {
154 - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange,PluginCheck.Security.DirectDB.UnescapedDBParameter
155 - $wpdb->query( "ALTER TABLE `{$search_log_table}` CONVERT TO CHARACTER SET {$charset} COLLATE {$collate}" );
156 - }
157 -
158 - // Mark migration as complete
159 - $this->database->save( 'betterdocs_search_collation_fixed', '1.0' );
160 - }
161 -
162 - /**
163 - * Populate keyword_hash for rows that predate the column.
164 - *
165 - * The column is added by dbDelta with an empty default; until it holds the
166 - * hash, insert_search_keyword() cannot find those rows by index and would
167 - * insert duplicates. Runs in bounded batches so a large keyword table does
168 - * not stall the request that triggers the upgrade — anything left over is
169 - * picked up on the next admin load.
170 - *
171 - * @since 1.0.3
172 - * @return void
173 - */
174 - public function backfill_search_keyword_hash() {
175 - global $wpdb;
176 -
177 - if ( $this->database->get( 'betterdocs_search_keyword_hash_filled', false ) ) {
178 - return;
179 - }
180 -
181 - $table = $wpdb->prefix . 'betterdocs_search_keyword';
182 -
183 - // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- one-shot schema backfill; identifier comes from $wpdb.
184 - if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) !== $table ) {
185 - return;
186 - }
187 -
188 - // Bail if the column never made it (dbDelta failed) — retry next load.
189 - if ( ! $wpdb->get_results( "SHOW COLUMNS FROM `{$table}` LIKE 'keyword_hash'" ) ) {
190 - return;
191 - }
192 -
193 - $batches = 0;
194 - do {
195 - $updated = $wpdb->query(
196 - "UPDATE `{$table}` SET keyword_hash = MD5( keyword ) WHERE keyword_hash = '' LIMIT 2000"
197 - );
198 - $batches++;
199 - } while ( $updated > 0 && $batches < 25 );
200 -
201 - // Only finish once nothing is left, so a table larger than 50k keywords
202 - // resumes instead of being marked done half-filled.
203 - $remaining = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}` WHERE keyword_hash = ''" );
204 - // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
205 -
206 - if ( 0 === $remaining ) {
207 - $this->database->save( 'betterdocs_search_keyword_hash_filled', '1.0.3' );
208 - }
209 - }
105 + $this->database->save( 'betterdocs_search_data_migration', '1.0' );
106 + }
107 + }
108 + }
210 109 }