PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.7
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.7
2.7.1 2.7.2 2.7.0 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 All 141 releases
metasync / redirections / class-metasync-redirection-importer.php

class-metasync-redirection-importer.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.7, at redirections/class-metasync-redirection-importer.php

968 lines 33.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Import redirections from other SEO plugins.
5 *
6 * @link https://searchatlas.com
7 * @since 1.0.0
8 * @package Metasync
9 * @subpackage Metasync/redirections
10 * @author Engineering Team <[email protected]>
11 */
12
13 # Abort if this file is accessed directly.
14 if (!defined('ABSPATH')) {
15 exit;
16 }
17
18 class Metasync_Redirection_Importer
19 {
20 private $db_redirection;
21
22 private $redirection_helper = null;
23
24 /**
25 * Supported plugins for import
26 */
27 const SUPPORTED_PLUGINS = [
28 'redirection' => [
29 'name' => 'Redirection',
30 'slug' => 'redirection/redirection.php',
31 'table' => 'redirection_items'
32 ],
33 'yoast' => [
34 'name' => 'Yoast SEO Premium',
35 'slugs' => [
36 'wordpress-seo-premium/wp-seo-premium.php',
37 'wordpress-seo-premium-main/wp-seo-premium.php',
38 'wordpress-seo/wp-seo.php'
39 ],
40 'options' => [
41 'wpseo-premium-redirects-base',
42 'wpseo-premium-redirects-export-plain',
43 'wpseo-premium-redirects-export-regex',
44 'wpseo-premium-redirects',
45 'wpseo-premium-redirects-regex'
46 ]
47 ],
48 'rankmath' => [
49 'name' => 'Rank Math',
50 'slug' => 'seo-by-rank-math/rank-math.php',
51 'table' => 'rank_math_redirections'
52 ],
53 'aioseo' => [
54 'name' => 'All in One SEO',
55 'slug' => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
56 'table' => 'aioseo_redirects'
57 ],
58 'simple301' => [
59 'name' => 'Simple 301 Redirects',
60 'slug' => 'simple-301-redirects/simple-301-redirects.php',
61 'option' => '301_redirects'
62 ]
63 ];
64
65 public function __construct(&$db_redirection)
66 {
67 $this->db_redirection = $db_redirection;
68 }
69
70 /**
71 * Lazy-load the Metasync_Redirection helper used for loop detection.
72 */
73 private function get_redirection_helper()
74 {
75 if ($this->redirection_helper === null) {
76 require_once dirname(__FILE__) . '/class-metasync-redirection.php';
77 $this->redirection_helper = new Metasync_Redirection($this->db_redirection);
78 }
79 return $this->redirection_helper;
80 }
81
82 /**
83 * Get list of available plugins for import
84 *
85 * @return array List of plugins with their status
86 */
87 public function get_available_plugins()
88 {
89 global $wpdb;
90 $available = [];
91
92 foreach (self::SUPPORTED_PLUGINS as $key => $plugin) {
93 $status = [
94 'key' => $key,
95 'name' => $plugin['name'],
96 'installed' => false,
97 'has_data' => false,
98 'count' => 0
99 ];
100
101 # Check if plugin is installed/active
102 if (isset($plugin['slug'])) {
103 $status['installed'] = is_plugin_active($plugin['slug']) || $this->plugin_exists($plugin['slug']);
104 } elseif (isset($plugin['slugs'])) {
105 # Check multiple possible slugs (for Yoast)
106 foreach ($plugin['slugs'] as $slug) {
107 if (is_plugin_active($slug) || $this->plugin_exists($slug)) {
108 $status['installed'] = true;
109 break;
110 }
111 }
112 }
113
114 # Check if data exists
115 if (isset($plugin['table'])) {
116 $table_name = $wpdb->prefix . $plugin['table'];
117 if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name) {
118 $count = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
119 if ($count > 0) {
120 $status['has_data'] = true;
121 $status['count'] = (int)$count;
122 }
123 }
124 } elseif (isset($plugin['option'])) {
125 $option_data = get_option($plugin['option'], []);
126 if (!empty($option_data)) {
127 $status['has_data'] = true;
128 $status['count'] = is_array($option_data) ? count($option_data) : 0;
129 }
130 } elseif (isset($plugin['options'])) {
131 # Check multiple options (for Yoast)
132 $total_count = 0;
133 foreach ($plugin['options'] as $option_name) {
134 $option_data = get_option($option_name, []);
135 if (!empty($option_data)) {
136 $status['has_data'] = true;
137 if (is_array($option_data)) {
138 $total_count += count($option_data);
139 }
140 }
141 }
142 if ($total_count > 0) {
143 $status['count'] = $total_count;
144 }
145 }
146
147 $available[] = $status;
148 }
149
150 return $available;
151 }
152
153 /**
154 * Check if plugin exists (even if not active)
155 */
156 private function plugin_exists($plugin_path)
157 {
158 $plugin_file = WP_PLUGIN_DIR . '/' . $plugin_path;
159 return file_exists($plugin_file);
160 }
161
162 /**
163 * Import redirections from specified plugin
164 *
165 * @param string $plugin Plugin key
166 * @return array Result with success status and message
167 */
168 public function import_from_plugin($plugin)
169 {
170 if (!isset(self::SUPPORTED_PLUGINS[$plugin])) {
171 return [
172 'success' => false,
173 'message' => 'Unknown plugin specified.',
174 'imported' => 0,
175 'skipped' => 0
176 ];
177 }
178
179 $method = "import_from_{$plugin}";
180 if (method_exists($this, $method)) {
181 return $this->$method();
182 }
183
184 return [
185 'success' => false,
186 'message' => 'Import method not implemented.',
187 'imported' => 0,
188 'skipped' => 0
189 ];
190 }
191
192 /**
193 * Import from Redirection plugin
194 */
195 private function import_from_redirection()
196 {
197 global $wpdb;
198 $table_name = $wpdb->prefix . 'redirection_items';
199
200 if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") !== $table_name) {
201 return [
202 'success' => false,
203 'message' => 'Redirection plugin table not found.',
204 'imported' => 0,
205 'skipped' => 0,
206 'loop_skipped' => 0
207 ];
208 }
209
210 $redirects = $wpdb->get_results("
211 SELECT * FROM $table_name
212 WHERE status = 'enabled'
213 ");
214
215 $imported = 0;
216 $skipped = 0;
217 $loop_skipped = 0;
218
219 foreach ($redirects as $redirect) {
220 $http_code = intval($redirect->action_code ?? 301);
221 $target_url = $redirect->action_data ?? '';
222
223 # For 410 and 451, target URL can be empty (they don't redirect)
224 if (empty($target_url) && !in_array($http_code, [410, 451])) {
225 $skipped++;
226 continue;
227 }
228
229 # Check if already exists
230 if ($this->redirection_exists($redirect->url)) {
231 $skipped++;
232 continue;
233 }
234
235 # Map Redirection plugin format to MetaSync format
236 $pattern_type = 'exact';
237 $regex_pattern = null;
238
239 # Check if it's a regex
240 if (isset($redirect->regex) && $redirect->regex == 1) {
241 $pattern_type = 'regex';
242 $regex_pattern = $redirect->url;
243 }
244
245 $sources = [
246 $redirect->url => $pattern_type
247 ];
248
249 $args = [
250 'sources_from' => serialize($sources),
251 'url_redirect_to' => $target_url,
252 'http_code' => $http_code,
253 'hits_count' => $redirect->hits ?? 0,
254 'status' => 'active',
255 'pattern_type' => $pattern_type,
256 'regex_pattern' => $regex_pattern,
257 'description' => 'Imported from Redirection plugin',
258 'created_at' => current_time('mysql'),
259 'updated_at' => current_time('mysql')
260 ];
261
262 if (!in_array($http_code, [410, 451]) && $this->get_redirection_helper()->validate_no_loop($redirect->url, $target_url) !== null) {
263 $loop_skipped++;
264 continue;
265 }
266
267 if ($this->db_redirection->add($args)) {
268 $imported++;
269 }
270 }
271
272 $message = "Successfully imported $imported redirections from Redirection plugin.";
273 if ($loop_skipped > 0) {
274 $message .= " Skipped $loop_skipped redirect(s) that would have created loops.";
275 }
276
277 return [
278 'success' => true,
279 'message' => $message,
280 'imported' => $imported,
281 'skipped' => $skipped,
282 'loop_skipped' => $loop_skipped
283 ];
284 }
285
286 /**
287 * Import from Yoast SEO Premium
288 *
289 * Yoast stores redirects in multiple options:
290 * - wpseo-premium-redirects-base (main storage since v3.1)
291 * - wpseo-premium-redirects-export-plain (plain redirects)
292 * - wpseo-premium-redirects-export-regex (regex redirects)
293 * - wpseo-premium-redirects (legacy plain)
294 * - wpseo-premium-redirects-regex (legacy regex)
295 */
296 private function import_from_yoast()
297 {
298 $imported = 0;
299 $skipped = 0;
300 $errors = [];
301 $loop_skipped = 0;
302
303 # Try to import from base option first (preferred method)
304 $base_redirects = get_option('wpseo-premium-redirects-base', []);
305
306 if (!empty($base_redirects) && is_array($base_redirects)) {
307 foreach ($base_redirects as $redirect) {
308 $result = $this->process_yoast_redirect_base($redirect);
309 if ($result === true) {
310 $imported++;
311 } elseif ($result === 'skipped') {
312 $skipped++;
313 } elseif ($result === 'loop_skipped') {
314 $loop_skipped++;
315 } else {
316 $errors[] = $result;
317 }
318 }
319 }
320
321 # If no base redirects, try export options
322 if ($imported === 0 && $skipped === 0) {
323 # Import plain redirects
324 $plain_redirects = get_option('wpseo-premium-redirects-export-plain', []);
325 if (!empty($plain_redirects) && is_array($plain_redirects)) {
326 foreach ($plain_redirects as $origin => $redirect_data) {
327 $result = $this->process_yoast_redirect_export($origin, $redirect_data, 'plain');
328 if ($result === true) {
329 $imported++;
330 } elseif ($result === 'skipped') {
331 $skipped++;
332 } elseif ($result === 'loop_skipped') {
333 $loop_skipped++;
334 } else {
335 $errors[] = $result;
336 }
337 }
338 }
339
340 # Import regex redirects
341 $regex_redirects = get_option('wpseo-premium-redirects-export-regex', []);
342 if (!empty($regex_redirects) && is_array($regex_redirects)) {
343 foreach ($regex_redirects as $origin => $redirect_data) {
344 $result = $this->process_yoast_redirect_export($origin, $redirect_data, 'regex');
345 if ($result === true) {
346 $imported++;
347 } elseif ($result === 'skipped') {
348 $skipped++;
349 } elseif ($result === 'loop_skipped') {
350 $loop_skipped++;
351 } else {
352 $errors[] = $result;
353 }
354 }
355 }
356 }
357
358 # If still no redirects, try legacy options
359 if ($imported === 0 && $skipped === 0) {
360 # Legacy plain redirects
361 $legacy_plain = get_option('wpseo-premium-redirects', []);
362 if (!empty($legacy_plain) && is_array($legacy_plain)) {
363 foreach ($legacy_plain as $origin => $redirect_data) {
364 $result = $this->process_yoast_redirect_export($origin, $redirect_data, 'plain');
365 if ($result === true) {
366 $imported++;
367 } elseif ($result === 'skipped') {
368 $skipped++;
369 } elseif ($result === 'loop_skipped') {
370 $loop_skipped++;
371 } else {
372 $errors[] = $result;
373 }
374 }
375 }
376
377 # Legacy regex redirects
378 $legacy_regex = get_option('wpseo-premium-redirects-regex', []);
379 if (!empty($legacy_regex) && is_array($legacy_regex)) {
380 foreach ($legacy_regex as $origin => $redirect_data) {
381 $result = $this->process_yoast_redirect_export($origin, $redirect_data, 'regex');
382 if ($result === true) {
383 $imported++;
384 } elseif ($result === 'skipped') {
385 $skipped++;
386 } elseif ($result === 'loop_skipped') {
387 $loop_skipped++;
388 } else {
389 $errors[] = $result;
390 }
391 }
392 }
393 }
394
395 # Return results
396 if ($imported === 0 && $skipped === 0 && $loop_skipped === 0) {
397 return [
398 'success' => false,
399 'message' => 'No Yoast redirections found. ' . (!empty($errors) ? implode(' ', array_slice($errors, 0, 2)) : ''),
400 'imported' => 0,
401 'skipped' => 0,
402 'loop_skipped' => 0,
403 'errors' => $errors
404 ];
405 }
406
407 $message = $imported > 0
408 ? "Successfully imported $imported redirections from Yoast SEO."
409 : "All redirections already exist. Skipped $skipped duplicates.";
410 if ($loop_skipped > 0) {
411 $message .= " Skipped $loop_skipped redirect(s) that would have created loops.";
412 }
413
414 return [
415 'success' => true,
416 'message' => $message,
417 'imported' => $imported,
418 'skipped' => $skipped,
419 'loop_skipped' => $loop_skipped,
420 'errors' => $errors
421 ];
422 }
423
424 /**
425 * Process a redirect from Yoast base option
426 *
427 * @param mixed $redirect Redirect data from base option
428 * @return bool|string True on success, 'skipped' if exists, error message on failure
429 */
430 private function process_yoast_redirect_base($redirect)
431 {
432 try {
433 # Base format: ['origin' => string, 'url' => string, 'type' => int, 'format' => string]
434 if (!is_array($redirect)) {
435 return 'Invalid redirect format';
436 }
437
438 $origin = isset($redirect['origin']) ? trim($redirect['origin']) : '';
439 $target = isset($redirect['url']) ? trim($redirect['url']) : '';
440 $type = isset($redirect['type']) ? $redirect['type'] : 301;
441 $format = isset($redirect['format']) ? $redirect['format'] : 'plain';
442
443 # Validate origin
444 if (empty($origin)) {
445 return 'Empty origin URL';
446 }
447
448 # For 410 and 451, target URL can be empty (they don't redirect)
449 if (empty($target) && !in_array($type, [410, 451])) {
450 return 'Empty target URL for non-410/451 redirect';
451 }
452
453 # Check if already exists
454 if ($this->redirection_exists($origin)) {
455 return 'skipped';
456 }
457
458 # Determine pattern type
459 $pattern_type = ($format === 'regex') ? 'regex' : 'exact';
460 $regex_pattern = ($format === 'regex') ? $origin : null;
461
462 $sources = [
463 $origin => $pattern_type
464 ];
465
466 $args = [
467 'sources_from' => serialize($sources),
468 'url_redirect_to' => $target,
469 'http_code' => intval($type),
470 'hits_count' => 0,
471 'status' => 'active',
472 'pattern_type' => $pattern_type,
473 'regex_pattern' => $regex_pattern,
474 'description' => 'Imported from Yoast SEO',
475 'created_at' => current_time('mysql'),
476 'updated_at' => current_time('mysql')
477 ];
478
479 if (!in_array(intval($type), [410, 451]) && $this->get_redirection_helper()->validate_no_loop($origin, $target) !== null) {
480 return 'loop_skipped';
481 }
482
483 if ($this->db_redirection->add($args)) {
484 return true;
485 }
486
487 return 'Failed to insert redirect';
488
489 } catch (Exception $e) {
490 return 'Exception: ' . $e->getMessage();
491 }
492 }
493
494 /**
495 * Process a redirect from Yoast export options
496 *
497 * @param string $origin Source URL
498 * @param mixed $redirect_data Redirect data from export option
499 * @param string $format Format type ('plain' or 'regex')
500 * @return bool|string True on success, 'skipped' if exists, error message on failure
501 */
502 private function process_yoast_redirect_export($origin, $redirect_data, $format)
503 {
504 try {
505 $origin = trim($origin);
506
507 # Export format: ['url' => string, 'type' => int]
508 if (is_array($redirect_data)) {
509 $target = isset($redirect_data['url']) ? trim($redirect_data['url']) : '';
510 $type = isset($redirect_data['type']) ? $redirect_data['type'] : 301;
511 } elseif (is_string($redirect_data)) {
512 # Simple string format (target URL only)
513 $target = trim($redirect_data);
514 $type = 301;
515 } else {
516 return 'Invalid redirect data format';
517 }
518
519 # Validate origin
520 if (empty($origin)) {
521 return 'Empty origin URL';
522 }
523
524 # For 410 and 451, target URL can be empty (they don't redirect)
525 if (empty($target) && !in_array($type, [410, 451])) {
526 return 'Empty target URL for non-410/451 redirect';
527 }
528
529 # Check if already exists
530 if ($this->redirection_exists($origin)) {
531 return 'skipped';
532 }
533
534 # Determine pattern type
535 $pattern_type = ($format === 'regex') ? 'regex' : 'exact';
536 $regex_pattern = ($format === 'regex') ? $origin : null;
537
538 $sources = [
539 $origin => $pattern_type
540 ];
541
542 $args = [
543 'sources_from' => serialize($sources),
544 'url_redirect_to' => $target,
545 'http_code' => intval($type),
546 'hits_count' => 0,
547 'status' => 'active',
548 'pattern_type' => $pattern_type,
549 'regex_pattern' => $regex_pattern,
550 'description' => 'Imported from Yoast SEO',
551 'created_at' => current_time('mysql'),
552 'updated_at' => current_time('mysql')
553 ];
554
555 if (!in_array(intval($type), [410, 451]) && $this->get_redirection_helper()->validate_no_loop($origin, $target) !== null) {
556 return 'loop_skipped';
557 }
558
559 if ($this->db_redirection->add($args)) {
560 return true;
561 }
562
563 return 'Failed to insert redirect';
564
565 } catch (Exception $e) {
566 return 'Exception: ' . $e->getMessage();
567 }
568 }
569
570 /**
571 * Import from Rank Math
572 */
573 private function import_from_rankmath()
574 {
575 global $wpdb;
576 $table_name = $wpdb->prefix . 'rank_math_redirections';
577
578 if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") !== $table_name) {
579 return [
580 'success' => false,
581 'message' => 'Rank Math redirections table not found.',
582 'imported' => 0,
583 'skipped' => 0,
584 'loop_skipped' => 0,
585 'errors' => ['Table not found: ' . $table_name]
586 ];
587 }
588
589 # Get all redirections (not just active ones, Rank Math uses different status field)
590 $redirects = $wpdb->get_results("SELECT * FROM $table_name");
591
592 if (empty($redirects)) {
593 return [
594 'success' => false,
595 'message' => 'No redirections found in Rank Math.',
596 'imported' => 0,
597 'skipped' => 0,
598 'loop_skipped' => 0,
599 'errors' => []
600 ];
601 }
602
603 $imported = 0;
604 $skipped = 0;
605 $errors = [];
606 $loop_skipped = 0;
607
608 foreach ($redirects as $redirect) {
609 try {
610 # Rank Math uses 'sources' field - it's a serialized PHP array
611 $source_url = '';
612 $comparison_type = 'exact'; // Default to exact
613
614 if (isset($redirect->sources)) {
615 # First, try to unserialize (Rank Math format)
616 $unserialized = @maybe_unserialize($redirect->sources);
617
618 if (is_array($unserialized) && !empty($unserialized)) {
619 # Rank Math format: array of arrays with 'pattern', 'comparison', 'ignore' keys
620 $first_source = reset($unserialized);
621
622 if (is_array($first_source) && isset($first_source['pattern'])) {
623 # Extract the actual URL pattern
624 $source_url = $first_source['pattern'];
625
626 # Extract comparison type (exact, regex, contains, starts, ends)
627 if (isset($first_source['comparison'])) {
628 $comparison_type = $first_source['comparison'];
629 }
630 } elseif (is_string($first_source)) {
631 # Simple string format
632 $source_url = $first_source;
633 }
634 } elseif (is_string($unserialized)) {
635 # Direct string value
636 $source_url = $unserialized;
637 } else {
638 # Try as JSON
639 $parsed = json_decode($redirect->sources, true);
640 if (json_last_error() === JSON_ERROR_NONE && is_array($parsed)) {
641 $source_url = is_array($parsed) ? reset($parsed) : $parsed;
642 } else {
643 # Last resort - use as-is
644 $source_url = $redirect->sources;
645 }
646 }
647 } elseif (isset($redirect->url_from)) {
648 $source_url = $redirect->url_from;
649 }
650
651 # Clean up the source URL
652 $source_url = trim($source_url);
653
654 if (empty($source_url)) {
655 $errors[] = 'Empty source URL in redirect ID: ' . ($redirect->id ?? 'unknown');
656 $skipped++;
657 continue;
658 }
659
660 # Check if already exists
661 if ($this->redirection_exists($source_url)) {
662 $skipped++;
663 continue;
664 }
665
666 # Map Rank Math comparison types to MetaSync pattern types
667 # Rank Math: exact, regex, contains, starts, ends
668 # MetaSync: exact, regex, contain, start, end
669 $pattern_type_map = [
670 'exact' => 'exact',
671 'regex' => 'regex',
672 'contains' => 'contain',
673 'starts' => 'start',
674 'ends' => 'end'
675 ];
676
677 $pattern_type = isset($pattern_type_map[$comparison_type])
678 ? $pattern_type_map[$comparison_type]
679 : 'exact';
680
681 # For regex patterns, store the pattern
682 $regex_pattern = ($pattern_type === 'regex') ? $source_url : null;
683
684 $sources = [
685 $source_url => $pattern_type
686 ];
687
688 $target_url = $redirect->url_to ?? '';
689 $http_code = intval($redirect->header_code ?? 301);
690
691 # For 410 and 451, target URL can be empty (they don't redirect)
692 if (empty($target_url) && !in_array($http_code, [410, 451])) {
693 $errors[] = 'Empty target URL for source: ' . $source_url;
694 $skipped++;
695 continue;
696 }
697
698 $args = [
699 'sources_from' => serialize($sources),
700 'url_redirect_to' => $target_url,
701 'http_code' => $http_code,
702 'hits_count' => $redirect->hits ?? 0,
703 'status' => 'active',
704 'pattern_type' => $pattern_type,
705 'regex_pattern' => $regex_pattern,
706 'description' => 'Imported from Rank Math',
707 'created_at' => current_time('mysql'),
708 'updated_at' => current_time('mysql')
709 ];
710
711 if (!in_array($http_code, [410, 451]) && $this->get_redirection_helper()->validate_no_loop($source_url, $target_url) !== null) {
712 $loop_skipped++;
713 continue;
714 }
715
716 $result = $this->db_redirection->add($args);
717
718 if ($result !== false && $result > 0) {
719 $imported++;
720 } else {
721 $errors[] = 'Failed to insert redirect: ' . $source_url . ' -> ' . $target_url;
722 if ($wpdb->last_error) {
723 $errors[] = 'DB Error: ' . $wpdb->last_error;
724 }
725 }
726
727 } catch (Exception $e) {
728 $errors[] = 'Exception: ' . $e->getMessage();
729 $skipped++;
730 }
731 }
732
733 # Return success if we processed redirections (even if all skipped)
734 $has_results = ($imported + $skipped + $loop_skipped) > 0;
735
736 $message = $imported > 0
737 ? "Successfully imported $imported redirections from Rank Math."
738 : ($skipped > 0
739 ? "All redirections already exist. Skipped $skipped duplicates."
740 : "No redirections found to import. " . (count($errors) > 0 ? implode(' ', array_slice($errors, 0, 2)) : ''));
741 if ($loop_skipped > 0) {
742 $message .= " Skipped $loop_skipped redirect(s) that would have created loops.";
743 }
744
745 return [
746 'success' => $has_results,
747 'message' => $message,
748 'imported' => $imported,
749 'skipped' => $skipped,
750 'loop_skipped' => $loop_skipped,
751 'errors' => $errors
752 ];
753 }
754
755 /**
756 * Import from All in One SEO
757 */
758 private function import_from_aioseo()
759 {
760 global $wpdb;
761 $table_name = $wpdb->prefix . 'aioseo_redirects';
762
763 if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") !== $table_name) {
764 return [
765 'success' => false,
766 'message' => 'All in One SEO redirects table not found.',
767 'imported' => 0,
768 'skipped' => 0,
769 'loop_skipped' => 0
770 ];
771 }
772
773 $redirects = $wpdb->get_results("
774 SELECT * FROM $table_name
775 WHERE enabled = 1
776 ");
777
778 $imported = 0;
779 $skipped = 0;
780 $loop_skipped = 0;
781
782 foreach ($redirects as $redirect) {
783 $http_code = intval($redirect->redirect_code ?? 301);
784 $target_url = $redirect->target_url ?? '';
785
786 # For 410 and 451, target URL can be empty (they don't redirect)
787 if (empty($target_url) && !in_array($http_code, [410, 451])) {
788 $skipped++;
789 continue;
790 }
791
792 # Check if already exists
793 if ($this->redirection_exists($redirect->source_url)) {
794 $skipped++;
795 continue;
796 }
797
798 # Determine pattern type
799 $pattern_type = 'exact';
800 $regex_pattern = null;
801
802 if (isset($redirect->regex) && $redirect->regex == 1) {
803 $pattern_type = 'regex';
804 $regex_pattern = $redirect->source_url;
805 }
806
807 $sources = [
808 $redirect->source_url => $pattern_type
809 ];
810
811 $args = [
812 'sources_from' => serialize($sources),
813 'url_redirect_to' => $target_url,
814 'http_code' => $http_code,
815 'hits_count' => $redirect->hits ?? 0,
816 'status' => 'active',
817 'pattern_type' => $pattern_type,
818 'regex_pattern' => $regex_pattern,
819 'description' => 'Imported from All in One SEO',
820 'created_at' => current_time('mysql'),
821 'updated_at' => current_time('mysql')
822 ];
823
824 if (!in_array($http_code, [410, 451]) && $this->get_redirection_helper()->validate_no_loop($redirect->source_url, $target_url) !== null) {
825 $loop_skipped++;
826 continue;
827 }
828
829 if ($this->db_redirection->add($args)) {
830 $imported++;
831 }
832 }
833
834 $message = "Successfully imported $imported redirections from All in One SEO.";
835 if ($loop_skipped > 0) {
836 $message .= " Skipped $loop_skipped redirect(s) that would have created loops.";
837 }
838
839 return [
840 'success' => true,
841 'message' => $message,
842 'imported' => $imported,
843 'skipped' => $skipped,
844 'loop_skipped' => $loop_skipped
845 ];
846 }
847
848 /**
849 * Import from Simple 301 Redirects
850 */
851 private function import_from_simple301()
852 {
853 # Simple 301 Redirects stores in options
854 $redirects = get_option('301_redirects', []);
855
856 if (empty($redirects)) {
857 return [
858 'success' => false,
859 'message' => 'No Simple 301 Redirects found.',
860 'imported' => 0,
861 'skipped' => 0,
862 'loop_skipped' => 0
863 ];
864 }
865
866 $imported = 0;
867 $skipped = 0;
868 $loop_skipped = 0;
869
870 foreach ($redirects as $old_url => $new_url) {
871 # Skip empty entries
872 if (empty($old_url) || empty($new_url)) {
873 $skipped++;
874 continue;
875 }
876
877 # Check if already exists
878 if ($this->redirection_exists($old_url)) {
879 $skipped++;
880 continue;
881 }
882
883 $sources = [
884 $old_url => 'exact'
885 ];
886
887 $args = [
888 'sources_from' => serialize($sources),
889 'url_redirect_to' => $new_url,
890 'http_code' => 301,
891 'hits_count' => 0,
892 'status' => 'active',
893 'pattern_type' => 'exact',
894 'regex_pattern' => null,
895 'description' => 'Imported from Simple 301 Redirects',
896 'created_at' => current_time('mysql'),
897 'updated_at' => current_time('mysql')
898 ];
899
900 if ($this->get_redirection_helper()->validate_no_loop($old_url, $new_url) !== null) {
901 $loop_skipped++;
902 continue;
903 }
904
905 if ($this->db_redirection->add($args)) {
906 $imported++;
907 }
908 }
909
910 $message = "Successfully imported $imported redirections from Simple 301 Redirects.";
911 if ($loop_skipped > 0) {
912 $message .= " Skipped $loop_skipped redirect(s) that would have created loops.";
913 }
914
915 return [
916 'success' => true,
917 'message' => $message,
918 'imported' => $imported,
919 'skipped' => $skipped,
920 'loop_skipped' => $loop_skipped
921 ];
922 }
923
924 /**
925 * Check if a redirection already exists
926 *
927 * @param string $source_url Source URL to check
928 * @return bool True if exists, false otherwise
929 */
930 private function redirection_exists($source_url)
931 {
932 global $wpdb;
933 $table_name = $wpdb->prefix . Metasync_Redirection_Database::$table_name;
934
935 $count = $wpdb->get_var($wpdb->prepare(
936 "SELECT COUNT(*) FROM $table_name WHERE sources_from LIKE %s",
937 '%' . $wpdb->esc_like($source_url) . '%'
938 ));
939
940 return $count > 0;
941 }
942
943 /**
944 * Get import statistics
945 *
946 * @return array Statistics about imports
947 */
948 public function get_import_stats()
949 {
950 global $wpdb;
951 $table_name = $wpdb->prefix . Metasync_Redirection_Database::$table_name;
952
953 $stats = [
954 'total_redirections' => 0,
955 'imported_redirections' => 0
956 ];
957
958 $stats['total_redirections'] = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
959
960 $stats['imported_redirections'] = $wpdb->get_var($wpdb->prepare(
961 "SELECT COUNT(*) FROM $table_name WHERE description LIKE %s",
962 'Imported from%'
963 ));
964
965 return $stats;
966 }
967 }
968