PluginProbe
404 Solution / 4.3.0
404 Solution v4.3.0
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / import / CrossPluginImporter.php

CrossPluginImporter.php in 404 Solution 4.3.0, at includes/import/CrossPluginImporter.php

153 lines 5.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * Cross-plugin redirect importer.
9 *
10 * Public entry point for the cross-plugin import feature. It detects installed
11 * redirect plugins, previews available redirects, and imports them into the
12 * 404 Solution redirects table. Reading and normalizing the foreign sources is
13 * delegated to {@see ABJ_404_Solution_ForeignRedirectSourceReader} (data
14 * access); this class owns only the business logic of turning a normalized row
15 * into a 404 Solution redirect (status/type resolution, RedirectSpec creation).
16 *
17 * Supported sources:
18 * - Rank Math (rank_math_redirections)
19 * - Yoast SEO Premium (yoast_seo_redirects)
20 * - AIOSEO (aioseo_redirects)
21 * - Safe Redirect Manager (redirect_rule CPT)
22 * - Redirection plugin (redirection_items)
23 */
24 class ABJ_404_Solution_CrossPluginImporter {
25
26 /** @var ABJ_404_Solution_RedirectsRepositoryInterface */
27 private $redirectsRepository;
28
29 /** @var ABJ_404_Solution_Logging */
30 private $logger;
31
32 /** @var ABJ_404_Solution_ForeignRedirectSourceReader */
33 private $sourceReader;
34
35 /**
36 * @param ABJ_404_Solution_RedirectsRepositoryInterface $redirectsRepository
37 * @param ABJ_404_Solution_Logging $logger
38 * @param ABJ_404_Solution_DatabaseQueryInterface|null $dbQuery
39 */
40 public function __construct($redirectsRepository, $logger, $dbQuery = null) {
41 $this->redirectsRepository = $redirectsRepository;
42 $this->logger = $logger;
43 $this->sourceReader = new ABJ_404_Solution_ForeignRedirectSourceReader(
44 $redirectsRepository,
45 $logger,
46 $dbQuery
47 );
48 }
49
50 /**
51 * Detect which source plugins are installed by checking for their DB tables
52 * (or, for Safe Redirect Manager, by checking for the CPT).
53 *
54 * @return array<string, bool> e.g. ['rankmath' => true, 'redirection' => false, ...]
55 */
56 public function detectInstalledPlugins(): array {
57 return $this->sourceReader->detectInstalledPlugins();
58 }
59
60 /**
61 * Return a preview of redirects available from the given source plugin.
62 *
63 * @param string $source One of 'rankmath', 'yoast', 'aioseo', 'safe-redirect-manager', 'redirection'
64 * @param int $previewLimit Maximum rows to return for preview
65 * @return array<int, array<string, mixed>>
66 */
67 public function getImportPreview(string $source, int $previewLimit = 10): array {
68 $rows = $this->sourceReader->readSource($source);
69 return array_slice($rows, 0, $previewLimit);
70 }
71
72 /**
73 * Import all redirects from the given source plugin.
74 * Returns the number of redirects successfully imported.
75 *
76 * @param string $source
77 * @return int
78 */
79 public function importFrom(string $source): int {
80 $rows = $this->sourceReader->readSource($source);
81
82 if (empty($rows)) {
83 return 0;
84 }
85
86 $imported = 0;
87 foreach ($rows as $row) {
88 $sourceUrl = isset($row['source_url']) && is_string($row['source_url']) ? $row['source_url'] : '';
89 $destUrl = isset($row['dest_url']) && is_string($row['dest_url']) ? $row['dest_url'] : '';
90 $code = isset($row['code']) && is_numeric($row['code']) ? (int)$row['code'] : 301;
91 $isRegex = isset($row['is_regex']) && (bool)$row['is_regex'];
92
93 if ($sourceUrl === '' || $destUrl === '') {
94 continue;
95 }
96
97 $status = $isRegex ? ABJ404_STATUS_REGEX : ABJ404_STATUS_MANUAL;
98
99 // Determine destination type and resolve internal paths to post IDs.
100 $resolved = $this->resolveDestinationType($destUrl);
101 $type = $resolved['type'];
102 $destUrl = $resolved['dest'];
103
104 $result = $this->redirectsRepository->setupRedirect(
105 ABJ_404_Solution_RedirectSpec::create(
106 $sourceUrl,
107 (string)$status,
108 (string)$type,
109 $destUrl,
110 (string)$code,
111 0
112 )
113 );
114
115 if ($result !== 0 && $result !== false) {
116 $imported++;
117 }
118 }
119
120 $this->logger->infoMessage(
121 'CrossPluginImporter: imported ' . $imported . ' redirect(s) from "' . $source . '".'
122 );
123
124 return $imported;
125 }
126
127 /**
128 * Resolve the redirect type and final destination for a given URL.
129 *
130 * External URLs (http/https) use ABJ404_TYPE_EXTERNAL with the URL as-is.
131 * Internal paths are resolved via url_to_postid() — if a post ID is found,
132 * ABJ404_TYPE_POST is used with the numeric ID. Otherwise ABJ404_TYPE_EXTERNAL
133 * is used so the path is preserved and used as-is by the redirect pipeline.
134 *
135 * @param string $destUrl
136 * @return array{type: int, dest: string}
137 */
138 private function resolveDestinationType(string $destUrl): array {
139 if (preg_match('/^https?:\/\//i', $destUrl)) {
140 return array('type' => ABJ404_TYPE_EXTERNAL, 'dest' => $destUrl);
141 }
142
143 if (function_exists('url_to_postid') && function_exists('home_url')) {
144 $postId = url_to_postid(home_url($destUrl));
145 if ($postId > 0) {
146 return array('type' => ABJ404_TYPE_POST, 'dest' => (string)$postId);
147 }
148 }
149
150 return array('type' => ABJ404_TYPE_EXTERNAL, 'dest' => $destUrl);
151 }
152 }
153