PluginProbe
BetterLinks – Link Shortener, Link Cloaking, Redirects, Affiliate Link Manager & MCP / trunk
BetterLinks – Link Shortener, Link Cloaking, Redirects, Affiliate Link Manager & MCP vtrunk
3.1.3 3.1.2 3.1.1 3.1.0 3.0.1 3.0.0 2.4.13 2.4.12 2.4.11 2.4.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 All 110 releases
betterlinks / includes / Installer.php

Installer.php in BetterLinks – Link Shortener, Link Cloaking, Redirects, Affiliate Link Manager & MCP trunk, at includes/Installer.php

607 lines 26.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace BetterLinks;
3 if ( ! defined( 'ABSPATH' ) ) { exit; }
4
5 use BetterLinks\Admin\Cache;
6
7 class Installer extends \WP_Background_Process
8 {
9 use Traits\DBTables;
10 use Traits\DBMigrate;
11 use Traits\Terms;
12
13 // phpcs:disable PluginCheck.Security.DirectDB, WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL
14
15 protected $wpdb;
16 protected $charset_collate;
17 protected $action = 'betterlinks_background_task';
18 public $activation;
19 public $migration;
20 public $db_version;
21
22 public function __construct()
23 {
24 parent::__construct();
25 global $wpdb;
26 $this->wpdb = $wpdb;
27 $this->charset_collate = $wpdb->get_charset_collate();
28 $this->activation = ['set_activation_flag','create_db_tables', 'db_migration', 'fix_betterlinks_db', 'insert_terms_data', 'create_json_files', 'save_settings', 'update_json_links', 'clear_cache'];
29 $this->migration = ['set_activation_flag', 'db_migration', 'fix_betterlinks_db', 'update_json_links', 'sync_missing_links_to_json', 'clear_cache', 'fix_json_files'];
30 $this->db_version = Helper::btl_get_option('betterlinks_db_version');
31 }
32
33 /**
34 * Task
35 *
36 * Override this method to perform any actions required on each
37 * queue item. Return the modified item for further processing
38 * in the next pass through. Or, return false to remove the
39 * item from the queue.
40 *
41 * @param mixed $item Queue item to iterate over
42 *
43 * @return mixed
44 */
45 protected function task($item)
46 {
47 if (method_exists($this, $item)) {
48 try {
49 $this->$item();
50 } catch (\Exception $e) {
51 if (defined('WP_DEBUG') && WP_DEBUG) {
52 trigger_error('BetterLinks background task triggered fatal error for callback ' . esc_html($item), E_USER_WARNING); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
53 }
54 }
55 } elseif(!(strpos($item, "prli_links-") === false)) {
56 $item = absint(substr($item, 11)); // getting the ID(number) by deleting 'prli_links-' (used 11 because the length of 'prli_links-' is 11)
57 $migrator = new \BetterLinks\Tools\Migration\PTLOneClick();
58 if( ! $migrator->insert_link( $item ) ) {
59 return true;
60 }
61 } elseif(!(strpos($item, "prli_clicks-") === false)) {
62 $item = absint(substr($item, 12)); // getting the ID(number) by deleting 'prli_clicks-' (used 12 because the length of 'prli_clicks-' is 12)
63 $migrator = new \BetterLinks\Tools\Migration\PTLOneClick();
64 if( ! $migrator->insert_click( $item ) ) {
65 return true;
66 }
67 } elseif(
68 $item === "betterlinks_notice_ptl_migrate" ||
69 $item === "betterlinks_ptl_links_migrated" ||
70 $item === "betterlinks_ptl_clicks_migrated"
71 ){
72 $this->after_migration_done();
73 Helper::btl_update_option($item, true);
74 }
75 return false;
76 }
77
78 /**
79 * Complete
80 *
81 * Override if applicable, but ensure that the below actions are
82 * performed, or, call parent::complete().
83 */
84 protected function complete()
85 {
86 parent::complete();
87 // Show notice to user or perform some other arbitrary task...
88 }
89
90 public function after_migration_done(){
91 // 'betterlinks/admin/after_import_data' hook's work done here
92 $Cron = new \BetterLinks\Cron();
93 $Cron->write_json_links();
94 $Cron->analytics();
95 Helper::clear_query_cache();
96 }
97
98 public function set_activation_flag()
99 {
100 $activation_flag = Helper::btl_get_option('betterlinks_activation_flag');
101 $new_flag_data = array_merge(
102 (is_array($activation_flag) ? $activation_flag : []),
103 ["last_activation_background_processes_firing_timestamp" => time()]
104 );
105 Helper::btl_update_option('betterlinks_activation_flag', $new_flag_data, !$activation_flag, !!$activation_flag);
106 }
107 public function create_db_tables()
108 {
109 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
110 $this->createBetterLinksTable();
111 $this->createBetterTermsTable();
112 $this->createBetterTermsRelationshipsTable();
113 $this->createBetterLinksCountriesTable(); // Create countries table first
114 $this->createBetterClicksTable(); // Create clicks table after countries table
115 $this->createBetterLinkMetaTable();
116 $this->createBetterLinkPasswordTable();
117 $this->createBetterUserAgentsTable();
118 $this->modifyBetterLinksClicksTableAddUserAgent();
119 // set plugin version in 'option table' if not already setted
120 // (i.e. when this plugin gets installed on a site for the very first time)
121 if (!Helper::btl_get_option('betterlinks_version')) {
122 Helper::btl_update_option('betterlinks_version', BETTERLINKS_VERSION, true);
123 }
124 // set db version in 'option table' if not already setted
125 // (i.e. when this plugin gets installed on a site for the very first time)
126 if (!Helper::btl_get_option('betterlinks_db_version')) {
127 Helper::btl_update_option('betterlinks_db_version', BETTERLINKS_DB_VERSION, true);
128 }
129 }
130
131 /**
132 * Recreate any of our tables that are missing from the database.
133 *
134 * create_db_tables() only runs on activation and on a version bump, so an
135 * install where a CREATE TABLE failed - for instance the invalid
136 * `longtext NOT NULL default ''`, which MySQL 8 rejects outright under
137 * STRICT_TRANS_TABLES - stays broken until the next upgrade, with every read
138 * of the absent table raising a DB error. Check once per install and only
139 * record the repair as done when the tables are actually present afterwards.
140 *
141 * @return void
142 */
143 public function heal_missing_tables()
144 {
145 if (Helper::btl_get_option('betterlinks_tables_healed')) {
146 return;
147 }
148
149 $tables = [
150 'betterlinks' => 'createBetterLinksTable',
151 'betterlinks_terms' => 'createBetterTermsTable',
152 'betterlinks_terms_relationships' => 'createBetterTermsRelationshipsTable',
153 'betterlinks_countries' => 'createBetterLinksCountriesTable',
154 'betterlinks_clicks' => 'createBetterClicksTable',
155 'betterlinkmeta' => 'createBetterLinkMetaTable',
156 'betterlinks_password' => 'createBetterLinkPasswordTable',
157 'betterlinks_user_agents' => 'createBetterUserAgentsTable',
158 ];
159
160 $missing = [];
161 foreach ($tables as $suffix => $creator) {
162 if (!$this->table_exists($this->wpdb->prefix . $suffix)) {
163 $missing[$suffix] = $creator;
164 }
165 }
166
167 if (empty($missing)) {
168 Helper::btl_update_option('betterlinks_tables_healed', BETTERLINKS_VERSION, true);
169 return;
170 }
171
172 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
173 $healed = true;
174 foreach ($missing as $suffix => $creator) {
175 $this->$creator();
176 if (!$this->table_exists($this->wpdb->prefix . $suffix)) {
177 $healed = false;
178 }
179 }
180
181 // Leave the flag unset when a table still could not be created, so the
182 // next request retries instead of locking in a broken schema.
183 if ($healed) {
184 Helper::btl_update_option('betterlinks_tables_healed', BETTERLINKS_VERSION, true);
185 }
186 }
187
188 /**
189 * @param string $table Fully prefixed table name.
190 * @return bool
191 */
192 protected function table_exists($table)
193 {
194 return $this->wpdb->get_var($this->wpdb->prepare('SHOW TABLES LIKE %s', $table)) === $table;
195 }
196
197 public function insert_terms_data()
198 {
199 try {
200 Helper::insert_term([
201 'term_name' => 'Uncategorized',
202 'term_slug' => 'uncategorized',
203 'term_type' => 'category',
204 ]);
205 } catch (\Throwable $th) {
206 echo esc_html( $th->getMessage() );
207 }
208 }
209
210 public function save_settings()
211 {
212 if (!Helper::btl_get_option(BETTERLINKS_LINKS_OPTION_NAME)) {
213 $fbs_cat = 0;
214 if( defined( 'FLUENT_BOARDS' ) ){
215 $args = array(
216 'ID' => 0,
217 'term_name' => 'Fluent Boards',
218 'term_slug' => 'btl-fluent-boards',
219 'term_type' => 'category',
220 );
221 $results = $this->create_term( $args );
222 $fbs_cat = !empty( $results['ID'] ) ? $results['ID'] : 0;
223 }
224 $value = [
225 'redirect_type' => '307',
226 'nofollow' => true,
227 'sponsored' => '',
228 'track_me' => true,
229 'param_forwarding' => '',
230 'wildcards' => false,
231 'disablebotclicks' => false,
232 'is_allow_gutenberg' => true,
233 'force_https' => false,
234 'prefix' => 'go',
235 'is_allow_qr' => false,
236 'is_random_string' => false, // Legacy setting - keep for backward compatibility
237 'url_slug_generation_type' => 'random_mixed', // New setting
238 'is_autolink_icon' => false,
239 'is_autolink_headings' => true,
240 'is_case_sensitive' => false,
241 'enable_custom_domain_menu' => true,
242 'enable_promo_cards' => true,
243 'enable_bio_links' => true,
244 // MCP connector master switch. Off by default: turning it on
245 // exposes the OAuth-capable MCP endpoint and is an explicit
246 // admin decision.
247 'enable_mcp' => false,
248 // The bio pages' own link category is machinery, so it stays off
249 // Manage Links until the admin opts in.
250 'show_bio_links_category' => false,
251 // Site-wide "Made with BetterLinks" credit on bio pages. On by
252 // default; turning it off white-labels every page at once.
253 'show_bio_links_branding' => true,
254 'enable_auto_title_suggestion' => true,
255 'enable_user_agent_tracking' => false,
256 'fbs' => [
257 'enable_fbs' => true,
258 'cat_id' => $fbs_cat,
259 'delete_on' => 'task_delete'
260 ]
261 ];
262 Helper::btl_update_option(BETTERLINKS_LINKS_OPTION_NAME, json_encode($value));
263 }
264 Cache::init();
265 }
266
267 /**
268 * Create files/directories.
269 */
270 public function create_json_files()
271 {
272 $emptyContent = '{}';
273 $files = [
274 [
275 'base' => BETTERLINKS_UPLOAD_DIR_PATH,
276 'file' => 'index.html',
277 'content' => '',
278 ],
279 [
280 'base' => BETTERLINKS_UPLOAD_DIR_PATH,
281 'file' => 'links.json',
282 'content' => $emptyContent,
283 ],
284 [
285 'base' => BETTERLINKS_UPLOAD_DIR_PATH,
286 'file' => 'clicks.json',
287 'content' => $emptyContent,
288 ],
289 [
290 'base' => BETTERLINKS_UPLOAD_DIR_PATH,
291 'file' => 'settings.json',
292 'content' => $emptyContent,
293 ],
294 ];
295
296 global $wp_filesystem;
297 if ( empty( $wp_filesystem ) ) {
298 require_once ABSPATH . 'wp-admin/includes/file.php';
299 WP_Filesystem();
300 }
301 foreach ($files as $file) {
302 $target = trailingslashit($file['base']) . $file['file'];
303 if (wp_mkdir_p($file['base']) && ! file_exists( $target )) {
304 $wp_filesystem->put_contents( $target, $file['content'], FS_CHMOD_FILE );
305 }
306 }
307
308 self::ensure_uploads_protected();
309 }
310
311 /**
312 * Drop a deny rule into the BetterLinks uploads directory so the generated
313 * links.json / clicks.json / settings.json files cannot be fetched over HTTP.
314 * Every consumer of those files reads them from disk, so denying web access
315 * costs nothing. Called on install and, for installs that predate the rule,
316 * from Cron::write_json_links() as a self-heal.
317 *
318 * @return void
319 */
320 public static function ensure_uploads_protected()
321 {
322 if ( ! defined( 'BETTERLINKS_UPLOAD_DIR_PATH' ) ) {
323 return;
324 }
325
326 $base = trailingslashit( BETTERLINKS_UPLOAD_DIR_PATH );
327 $htaccess = $base . '.htaccess';
328 $index = $base . 'index.html';
329
330 // Already hardened - keep this cheap, it runs on every JSON rewrite.
331 if ( file_exists( $htaccess ) && file_exists( $index ) ) {
332 return;
333 }
334
335 if ( ! wp_mkdir_p( BETTERLINKS_UPLOAD_DIR_PATH ) ) {
336 return;
337 }
338
339 global $wp_filesystem;
340 if ( empty( $wp_filesystem ) ) {
341 require_once ABSPATH . 'wp-admin/includes/file.php';
342 WP_Filesystem();
343 }
344 if ( empty( $wp_filesystem ) ) {
345 return;
346 }
347
348 if ( ! file_exists( $htaccess ) ) {
349 $rules = "# BetterLinks - deny direct web access to generated data files.\n"
350 . "<IfModule mod_authz_core.c>\n"
351 . "\tRequire all denied\n"
352 . "</IfModule>\n"
353 . "<IfModule !mod_authz_core.c>\n"
354 . "\tOrder allow,deny\n"
355 . "\tDeny from all\n"
356 . "</IfModule>\n";
357 $wp_filesystem->put_contents( $htaccess, $rules, FS_CHMOD_FILE );
358 }
359
360 if ( ! file_exists( $index ) ) {
361 $wp_filesystem->put_contents( $index, '', FS_CHMOD_FILE );
362 }
363 }
364
365 public function update_json_links()
366 {
367 $Cron = new Cron();
368 $Cron->write_json_links();
369 }
370
371 /**
372 * Sync all missing links from database to JSON file during migration/update
373 * Ensures complete synchronization when plugin is updated to v2.4.8+
374 *
375 * @since 2.4.8
376 * @return void
377 */
378 public function sync_missing_links_to_json()
379 {
380 $result = Helper::sync_all_missing_links_to_json();
381 if ( !empty($result['synced']) && $result['synced'] > 0 ) {
382 // PCP-DEBUG-DISABLED: error_log( 'BetterLinks Migration: Synced ' . $result['synced'] . ' missing links to JSON (Total: ' . $result['total'] . ')' );
383 }
384 }
385
386 public function db_migration()
387 {
388 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
389 if ($this->db_version && $this->db_version != BETTERLINKS_DB_VERSION) {
390 if (BETTERLINKS_DB_VERSION == '1.1') {
391 $this->db_migration_1_1();
392 } elseif (BETTERLINKS_DB_VERSION == '1.2') {
393 $this->db_migration_1_2();
394 } elseif (BETTERLINKS_DB_VERSION == '1.4') {
395 $this->db_migration_1_4();
396 } elseif (BETTERLINKS_DB_VERSION == '1.5') {
397 $this->createBetterLinkMetaTable();
398 } elseif (BETTERLINKS_DB_VERSION == '1.6') {
399 $this->createBetterLinkPasswordTable();
400 }
401 if (version_compare($this->db_version, '1.3', '<')) {
402 $this->db_migration_1_1();
403 $this->db_migration_1_2();
404 }
405
406 if( version_compare(BETTERLINKS_DB_VERSION, '1.6', '>=') ) {
407 $this->createBetterLinkPasswordTable();
408 }
409
410 if( version_compare(BETTERLINKS_DB_VERSION, '1.6', '>') ) {
411 $this->modifyBetterLinksTable();
412 }
413
414 if( version_compare( BETTERLINKS_DB_VERSION, '1.6.1', '>' ) ) {
415 // run analytics total clicks & unique clicks data migration
416 \BetterLinks\Helper::update_links_analytics();
417 }
418 if( version_compare( BETTERLINKS_DB_VERSION, '1.6.3', '>=' ) ) {
419 $this->modifyBetterLinksClicksTable();
420 }
421
422 if( version_compare( BETTERLINKS_DB_VERSION, '1.6.3', '>' ) ) {
423 $this->update_settings();
424 $this->update_fluent_settings();
425 }
426 if( version_compare( BETTERLINKS_DB_VERSION, '1.6.4', '>' ) ) {
427 $this->update_fluent_task_delete_settings();
428 $this->update_cle_category();
429 }
430
431 if( version_compare( BETTERLINKS_DB_VERSION, '1.6.6', '>' ) ){
432 $this->modifyBetterLinksClicksTable2();
433 }
434
435 // Ensure countries table exists for all versions >= 1.6.7
436 if( version_compare( BETTERLINKS_DB_VERSION, '1.6.7', '>=' ) ){
437 $this->createBetterLinksCountriesTable();
438 $this->modifyBetterLinksClicksTable4();
439 }
440 // Ensure User Agent table exists for all versions >= 1.6.7
441 if( version_compare( BETTERLINKS_DB_VERSION, '2.0.0', '>=' ) ){
442 $this->createBetterUserAgentsTable();
443 $this->modifyBetterLinksClicksTableAddUserAgent();
444 }
445
446 // Migrate default settings for backward compatibility (runs for all versions)
447 // This ensures older users get new default settings that were added over time
448 $this->migrate_default_settings();
449
450 }
451 Helper::btl_update_option('betterlinks_db_version', BETTERLINKS_DB_VERSION);
452 }
453
454 public function clear_cache()
455 {
456 Helper::clear_query_cache();
457 // Analytics caches too, and a rebuild of `betterlinks_analytics_data`.
458 // Both can hold click totals computed by an older build — the per-link
459 // unique counts used to be paired to the wrong link — and neither is
460 // rewritten until the `betterlinks/analytics` cron next runs, which on a
461 // site with unreliable cron could be a long time. Rebuilding here means
462 // an update fixes the numbers straight away.
463 Helper::clear_analytics_cache();
464 Helper::update_links_analytics();
465 }
466
467 public function fix_betterlinks_db()
468 {
469 $btl_db_alter_options = Helper::btl_get_option(BETTERLINKS_DB_ALTER_OPTIONS);
470 $is_favorite_column_exist = isset($btl_db_alter_options["added_favorite_column"]) ? $btl_db_alter_options["added_favorite_column"] : false;
471 $is_fixed_missing_terms_relation_for_links = isset($btl_db_alter_options["fixed_missing_terms_relation_after_ta_one_click_migration"]) ? $btl_db_alter_options["fixed_missing_terms_relation_after_ta_one_click_migration"] : false;
472 $is_uncloaked_column_exist = isset($btl_db_alter_options["added_uncloaked_column"]) ? $btl_db_alter_options["added_uncloaked_column"] : false;
473 $added_index_to_created_at_column_in_clicks = isset($btl_db_alter_options["added_index_to_created_at_column"]) ? $btl_db_alter_options["added_index_to_created_at_column"] : false;
474 global $wpdb;
475 $wpdb->query("DELETE FROM {$wpdb->prefix}options WHERE option_name IN( 'betterlinks_autolink_options' )");
476 \BetterLinks\Helper::btl_update_autoload_option('betterlinks_analytics_data');
477
478 // Get actual table structure to verify columns exist
479 $is_db_alter_option_exist_array = is_array($btl_db_alter_options);
480 $betterlinks_table = $wpdb->prefix . 'betterlinks';
481 $betterlinks_clicks_table = $wpdb->prefix . 'betterlinks_clicks';
482 $betterlinks_columns = $wpdb->get_col("DESC $betterlinks_table", 0);
483
484 // Check actual table structure, not just cached options
485 $uncloaked_column_exists_in_table = in_array("uncloaked", $betterlinks_columns);
486 $favorite_column_exists_in_table = in_array("favorite", $betterlinks_columns);
487
488 if( $favorite_column_exists_in_table && $is_fixed_missing_terms_relation_for_links && $uncloaked_column_exists_in_table && $added_index_to_created_at_column_in_clicks){
489 return false;
490 }
491 $created_at_column = 'created_at';
492 if (!$added_index_to_created_at_column_in_clicks) {
493 $sql = "SHOW INDEX FROM $betterlinks_clicks_table WHERE Column_name = '$created_at_column'";
494 $query = $wpdb->get_results($sql);
495 if(empty($query)){
496 $query_result = $wpdb->query("ALTER TABLE $betterlinks_clicks_table ADD KEY created_at_idx (created_at)");
497 }else{
498 $query_result = true;
499 }
500 $new_data = array_merge(
501 ($is_db_alter_option_exist_array ? Helper::btl_get_option(BETTERLINKS_DB_ALTER_OPTIONS) : []),
502 [ "added_index_to_created_at_column" => $query_result ]
503 );
504 Helper::btl_update_option(BETTERLINKS_DB_ALTER_OPTIONS, $new_data, !$is_db_alter_option_exist_array, $is_db_alter_option_exist_array);
505 }
506
507 // Handle favorite column first - should be positioned after dynamic_redirect
508 if (!$favorite_column_exists_in_table) {
509 delete_transient(BETTERLINKS_CACHE_LINKS_NAME);
510 $query_result = $wpdb->query("ALTER TABLE $betterlinks_table ADD favorite varchar(255) NOT NULL AFTER dynamic_redirect");
511 $new_data = array_merge(
512 ($is_db_alter_option_exist_array ? Helper::btl_get_option(BETTERLINKS_DB_ALTER_OPTIONS) : []),
513 [ "added_favorite_column" => $query_result ]
514 );
515 Helper::btl_update_option(BETTERLINKS_DB_ALTER_OPTIONS, $new_data, !$is_db_alter_option_exist_array, $is_db_alter_option_exist_array);
516 } elseif (!$is_favorite_column_exist) {
517 // Column exists in table but not marked in options, update the options
518 $new_data = array_merge(
519 ($is_db_alter_option_exist_array ? Helper::btl_get_option(BETTERLINKS_DB_ALTER_OPTIONS) : []),
520 [ "added_favorite_column" => true ]
521 );
522 Helper::btl_update_option(BETTERLINKS_DB_ALTER_OPTIONS, $new_data, !$is_db_alter_option_exist_array, $is_db_alter_option_exist_array);
523 }
524
525 // Handle uncloaked column - should be positioned after favorite
526 if (!$uncloaked_column_exists_in_table) {
527 delete_transient(BETTERLINKS_CACHE_LINKS_NAME);
528 $query_result = $wpdb->query("ALTER TABLE $betterlinks_table ADD uncloaked varchar(10) default '' AFTER favorite");
529 $new_data = array_merge(
530 ($is_db_alter_option_exist_array ? Helper::btl_get_option(BETTERLINKS_DB_ALTER_OPTIONS) : []),
531 [ "added_uncloaked_column" => $query_result ]
532 );
533 Helper::btl_update_option(BETTERLINKS_DB_ALTER_OPTIONS, $new_data, !$is_db_alter_option_exist_array, $is_db_alter_option_exist_array);
534 } elseif (!$is_uncloaked_column_exist) {
535 // Column exists in table but not marked in options, update the options
536 $new_data = array_merge(
537 ($is_db_alter_option_exist_array ? Helper::btl_get_option(BETTERLINKS_DB_ALTER_OPTIONS) : []),
538 [ "added_uncloaked_column" => true ]
539 );
540 Helper::btl_update_option(BETTERLINKS_DB_ALTER_OPTIONS, $new_data, !$is_db_alter_option_exist_array, $is_db_alter_option_exist_array);
541 }
542 if(!$is_fixed_missing_terms_relation_for_links){
543 delete_transient(BETTERLINKS_CACHE_LINKS_NAME);
544 $betterlinks_table = $wpdb->prefix . 'betterlinks';
545 $betterlinks_terms_table = $wpdb->prefix . 'betterlinks_terms';
546 $betterlinks_terms_relations_table = $wpdb->prefix . 'betterlinks_terms_relationships';
547 $link_ids = $wpdb->get_col(
548 "SELECT ID FROM {$betterlinks_table}",
549 0
550 );
551 $categories = $wpdb->get_results(
552 $wpdb->prepare( "SELECT ID,term_slug FROM {$betterlinks_terms_table} WHERE term_type = %s", "category" ) ,
553 'ARRAY_A'
554 );
555 $uncategorized_id = false;
556 $cat_ids = [];
557 foreach ($categories as $key => $value) {
558 $cat_ids[] = $value["ID"];
559 if ($value["term_slug"] === "uncategorized") {
560 $uncategorized_id = $value["ID"];
561 }
562 }
563 if(!$uncategorized_id){
564 return false;
565 }
566 foreach ($link_ids as $key => $link_id) {
567 $cat_relation_exist_for_link = false;
568 $matched_terms_for_link = $wpdb->get_col(
569 $wpdb->prepare("SELECT term_id FROM {$betterlinks_terms_relations_table} WHERE link_id = %s", $link_id),
570 0
571 );
572 foreach ($matched_terms_for_link as $key => $term_id) {
573 if (in_array($term_id, $cat_ids)) {
574 $cat_relation_exist_for_link = true;
575 }
576 }
577 if(!$cat_relation_exist_for_link){
578 $result = Helper::insert_terms_relationships($uncategorized_id, $link_id);
579 }
580 }
581 $new_data = array_merge(
582 ($is_db_alter_option_exist_array ? Helper::btl_get_option(BETTERLINKS_DB_ALTER_OPTIONS) : []),
583 [ "fixed_missing_terms_relation_after_ta_one_click_migration" => true ]
584 );
585 Helper::btl_update_option(BETTERLINKS_DB_ALTER_OPTIONS, $new_data, !$is_db_alter_option_exist_array, $is_db_alter_option_exist_array);
586 }
587 }
588
589 public function fix_json_files() {
590 $file = [
591 'base' => BETTERLINKS_UPLOAD_DIR_PATH,
592 'file' => 'settings.json',
593 'content' => '{}',
594 ];
595
596 global $wp_filesystem;
597 if ( empty( $wp_filesystem ) ) {
598 require_once ABSPATH . 'wp-admin/includes/file.php';
599 WP_Filesystem();
600 }
601 $target = trailingslashit($file['base']) . $file['file'];
602 if (wp_mkdir_p($file['base']) && ! file_exists( $target )) {
603 $wp_filesystem->put_contents( $target, $file['content'], FS_CHMOD_FILE );
604 }
605 }
606 }
607