PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.5
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.5
4.9.5 4.9.4 4.9.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 All 203 releases
← All changes | includes/Core/Migration.php +96 -3 4.5.6 → 4.9.5 View file →
@@ -36,8 +36,10 @@
36 36 }
37 37
38 38 $this->search_migration();
39 39 $this->fix_search_table_collation();
40 + $this->backfill_search_keyword_hash();
41 + $this->reset_formerly_locked_analytics_settings();
40 42
41 43 /**
42 44 * Settings Migration
43 45 */
@@ -82,12 +84,13 @@
82 84 if ( $keyword == null ) {
83 85 $insert = $wpdb->query(
84 86 $wpdb->prepare(
85 87 "INSERT INTO {$wpdb->prefix}betterdocs_search_keyword
86 - ( keyword )
87 - VALUES ( %s )",
88 + ( keyword, keyword_hash )
89 + VALUES ( %s, %s )",
88 90 [
89 - $key
91 + $key,
92 + md5( $key )
90 93 ]
91 94 )
92 95 );
93 96
@@ -154,6 +157,96 @@
154 157 }
155 158
156 159 // Mark migration as complete
157 160 $this->database->save( 'betterdocs_search_collation_fixed', '1.0' );
161 + }
162 +
163 + /**
164 + * Populate keyword_hash for rows that predate the column.
165 + *
166 + * The column is added by dbDelta with an empty default; until it holds the
167 + * hash, insert_search_keyword() cannot find those rows by index and would
168 + * insert duplicates. Runs in bounded batches so a large keyword table does
169 + * not stall the request that triggers the upgrade — anything left over is
170 + * picked up on the next admin load.
171 + *
172 + * @since 1.0.3
173 + * @return void
174 + */
175 + public function backfill_search_keyword_hash() {
176 + global $wpdb;
177 +
178 + if ( $this->database->get( 'betterdocs_search_keyword_hash_filled', false ) ) {
179 + return;
180 + }
181 +
182 + $table = $wpdb->prefix . 'betterdocs_search_keyword';
183 +
184 + // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- one-shot schema backfill; identifier comes from $wpdb.
185 + if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) !== $table ) {
186 + return;
187 + }
188 +
189 + // Bail if the column never made it (dbDelta failed) — retry next load.
190 + if ( ! $wpdb->get_results( "SHOW COLUMNS FROM `{$table}` LIKE 'keyword_hash'" ) ) {
191 + return;
192 + }
193 +
194 + $batches = 0;
195 + do {
196 + $updated = $wpdb->query(
197 + "UPDATE `{$table}` SET keyword_hash = MD5( keyword ) WHERE keyword_hash = '' LIMIT 2000"
198 + );
199 + $batches++;
200 + } while ( $updated > 0 && $batches < 25 );
201 +
202 + // Only finish once nothing is left, so a table larger than 50k keywords
203 + // resumes instead of being marked done half-filled.
204 + $remaining = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}` WHERE keyword_hash = ''" );
205 + // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
206 +
207 + if ( 0 === $remaining ) {
208 + $this->database->save( 'betterdocs_search_keyword_hash_filled', '1.0.3' );
209 + }
210 + }
211 +
212 + /**
213 + * Clear the placeholder values the old Pro lock saved for the analytics
214 + * collection settings.
215 + *
216 + * Until these settings were unlocked in Free (WP.org guideline 5), the
217 + * settings screen merged Settings::pro_settings_default_values() into its
218 + * form whenever Pro was inactive, so any save stored `analytics_from`,
219 + * `unique_visitor_count`, `exclude_bot_analytics` and
220 + * `child_category_exclude` as boolean false. Free ignored those keys then,
221 + * so the tracker counted everyone, excluded bots and skipped unique visits.
222 + *
223 + * Free now reads the stored values, so a leftover `exclude_bot_analytics =
224 + * false` would suddenly start counting bot traffic. A boolean false in
225 + * `analytics_from` is the marker of that merge: no UI ever saves it (Free's
226 + * select saves a string; Pro's Analytics → Settings never writes the key).
227 + * When it is present, restore the behaviour those sites actually had.
228 + * `unique_visitor_count` is left as stored — false there matches the old
229 + * behaviour — and `child_category_exclude` false is already the default.
230 + *
231 + * @return void
232 + */
233 + public function reset_formerly_locked_analytics_settings() {
234 + if ( $this->database->get( 'betterdocs_unlocked_analytics_settings_migrated', false ) ) {
235 + return;
236 + }
237 +
238 + $settings = $this->database->get( 'betterdocs_settings', [] );
239 +
240 + if ( is_array( $settings ) && array_key_exists( 'analytics_from', $settings ) && false === $settings['analytics_from'] ) {
241 + $settings['analytics_from'] = 'everyone';
242 +
243 + if ( array_key_exists( 'exclude_bot_analytics', $settings ) && ! $settings['exclude_bot_analytics'] ) {
244 + $settings['exclude_bot_analytics'] = true;
245 + }
246 +
247 + $this->database->save( 'betterdocs_settings', $settings );
248 + }
249 +
250 + $this->database->save( 'betterdocs_unlocked_analytics_settings_migrated', '1' );
158 251 }
159 252 }