PluginProbe
404 Solution / trunk
404 Solution vtrunk
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 / wpcli / WPCLICommands.php

WPCLICommands.php in 404 Solution trunk, at includes/wpcli/WPCLICommands.php

273 lines 10.1 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 * WP-CLI command group for 404 Solution.
9 *
10 * Registered as: wp abj404 <subcommand>
11 */
12 class ABJ_404_Solution_WPCLICommands extends \WP_CLI_Command {
13
14 /** @var ABJ_404_Solution_WPCLIImportExportCommandService|null Injected service; null => container/new fallback. */
15 private $injectedImportExportService;
16
17 /**
18 * @param ABJ_404_Solution_WPCLIImportExportCommandService|null $importExportService Optional injected
19 * import/export application service. WP-CLI instantiates this command class with no constructor
20 * arguments, so the parameter is optional and production wiring is unchanged. The seam exists so
21 * tests (and any embedder) can drive the import/export workflow against an explicit DataAccess
22 * without mutating global container state.
23 */
24 public function __construct($importExportService = null) {
25 $this->injectedImportExportService =
26 $importExportService instanceof ABJ_404_Solution_WPCLIImportExportCommandService
27 ? $importExportService
28 : null;
29 }
30
31 /**
32 * List redirects.
33 *
34 * @subcommand list
35 *
36 * @param array<int, string> $args
37 * @param array<string, string> $assocArgs
38 * @return void
39 */
40 public function list_redirects($args, $assocArgs) {
41 $this->presenter()->present($this->redirectService()->listRedirects(
42 $this->assocString($assocArgs, 'status', ''),
43 $this->assocString($assocArgs, 'format', 'table')
44 ));
45 }
46
47 /**
48 * Create a manual redirect.
49 *
50 * @param array<int, string> $args
51 * @param array<string, string> $assocArgs
52 * @return void
53 */
54 public function create($args, $assocArgs) {
55 $this->presenter()->present($this->redirectService()->createRedirect(
56 isset($assocArgs['from']) ? trim((string)$assocArgs['from']) : '',
57 isset($assocArgs['to']) ? trim((string)$assocArgs['to']) : '',
58 isset($assocArgs['code']) ? (int)$assocArgs['code'] : 301,
59 isset($assocArgs['regex'])
60 ));
61 }
62
63 /**
64 * Move a redirect to the trash.
65 *
66 * @param array<int, string> $args
67 * @param array<string, string> $assocArgs
68 * @return void
69 */
70 public function delete($args, $assocArgs) {
71 $this->presenter()->present($this->redirectService()->deleteRedirect(
72 isset($args[0]) ? trim((string)$args[0]) : ''
73 ));
74 }
75
76 /**
77 * Show summary statistics.
78 *
79 * @param array<int, string> $args
80 * @param array<string, string> $assocArgs
81 * @return void
82 */
83 public function stats($args, $assocArgs) {
84 $container = ABJ_404_Solution_ServiceContainer::getInstance();
85 $statsRepository = null;
86 if (!$container->has('stats_repository') && $container->has('data_access')) {
87 $candidate = $container->get('data_access');
88 if (is_object($candidate) && method_exists($candidate, 'getStatsDashboardSnapshot')) {
89 $statsRepository = $candidate;
90 }
91 }
92 if ($statsRepository === null) {
93 $statsRepository = ABJ_404_Solution_StatsRepositoryResolver::resolve(__CLASS__);
94 }
95 $snapshot = $statsRepository->getStatsDashboardSnapshot(false);
96 $data = is_array($snapshot['data']) ? $snapshot['data'] : array();
97
98 /** @var array<string, mixed> $dataAsStrMap */
99 $dataAsStrMap = $data;
100 $redirects = isset($dataAsStrMap['redirects']) && is_array($dataAsStrMap['redirects']) ? $dataAsStrMap['redirects'] : array();
101 $captured = isset($dataAsStrMap['captured']) && is_array($dataAsStrMap['captured']) ? $dataAsStrMap['captured'] : array();
102
103 $this->presenter()->present(array(
104 'type' => 'format',
105 'format' => 'table',
106 'rows' => array(
107 array('metric' => 'Auto (301)', 'count' => intval($redirects['auto301'] ?? 0)),
108 array('metric' => 'Auto (302)', 'count' => intval($redirects['auto302'] ?? 0)),
109 array('metric' => 'Manual (301)', 'count' => intval($redirects['manual301'] ?? 0)),
110 array('metric' => 'Manual (302)', 'count' => intval($redirects['manual302'] ?? 0)),
111 array('metric' => 'Trashed', 'count' => intval($redirects['trashed'] ?? 0)),
112 array('metric' => 'Captured 404s', 'count' => intval($captured['captured'] ?? 0)),
113 array('metric' => 'Ignored', 'count' => intval($captured['ignored'] ?? 0)),
114 array('metric' => 'Captured trash', 'count' => intval($captured['trashed'] ?? 0)),
115 ),
116 'fields' => array('metric', 'count'),
117 ));
118 }
119
120 /**
121 * Purge captured 404s.
122 *
123 * @param array<int, string> $args
124 * @param array<string, string> $assocArgs
125 * @return void
126 */
127 public function purge($args, $assocArgs) {
128 $prepared = $this->redirectService()->preparePurge(
129 isset($args[0]) ? strtolower(trim((string)$args[0])) : ''
130 );
131 if (($prepared['type'] ?? '') !== 'confirm') {
132 $this->presenter()->present($prepared);
133 return;
134 }
135
136 $count = isset($prepared['count']) && is_numeric($prepared['count']) ? (int)$prepared['count'] : 0;
137 $this->presenter()->confirm(
138 "This will permanently delete {$count} captured 404 entr" . ($count === 1 ? 'y' : 'ies') . '. Continue?',
139 $assocArgs
140 );
141 $this->presenter()->present($this->redirectService()->purgeCaptured($count));
142 }
143
144 /**
145 * Import redirects from a CSV file.
146 *
147 * @subcommand import
148 *
149 * @param array<int, string> $args
150 * @param array<string, string> $assocArgs
151 * @return void
152 */
153 public function import_redirects($args, $assocArgs) {
154 $this->presenter()->present($this->importExportService()->importRedirects(
155 isset($args[0]) ? (string)$args[0] : '',
156 isset($assocArgs['dry-run'])
157 ));
158 }
159
160 /**
161 * Export redirects to stdout or a file.
162 *
163 * @subcommand export
164 *
165 * @param array<int, string> $args
166 * @param array<string, string> $assocArgs
167 * @return void
168 */
169 public function export_redirects($args, $assocArgs) {
170 $this->presenter()->present($this->importExportService()->exportRedirects(
171 $this->assocString($assocArgs, 'format', 'native'),
172 isset($assocArgs['output']) ? trim((string)$assocArgs['output']) : ''
173 ));
174 }
175
176 /**
177 * Flush one or more internal caches.
178 *
179 * @subcommand flush-cache
180 *
181 * @param array<int, string> $args
182 * @param array<string, string> $assocArgs
183 * @return void
184 */
185 public function flush_cache($args, $assocArgs) {
186 $type = $this->assocString($assocArgs, 'type', 'all');
187 $validTypes = array('spelling', 'ngram', 'permalink', 'all');
188 if (!in_array($type, $validTypes, true)) {
189 $this->presenter()->present(array('type' => 'error', 'message' => 'Invalid type. Choose one of: ' . implode(', ', $validTypes)));
190 return;
191 }
192
193 $dbCore = abj_service('db_core');
194 $contentRepository = abj_service('content_repository');
195 $flushed = array();
196
197 if ($type === 'spelling' || $type === 'all') {
198 $contentRepository->deleteSpellingCache();
199 $flushed[] = 'spelling';
200 }
201
202 if ($type === 'permalink' || $type === 'all') {
203 $contentRepository->truncatePermalinkCacheTable();
204 $flushed[] = 'permalink';
205 }
206
207 if ($type === 'ngram' || $type === 'all') {
208 $ngramTable = $dbCore->doTableNameReplacements('{wp_abj404_ngram_cache}');
209 $dbCore->queryAndGetResults(
210 "TRUNCATE TABLE `{$ngramTable}`",
211 array('skip_repair' => true)
212 );
213 delete_option('abj404_ngram_cache_initialized');
214 delete_option('abj404_ngram_rebuild_offset');
215 $flushed[] = 'ngram';
216 }
217
218 $this->presenter()->present(array('type' => 'success', 'message' => 'Flushed caches: ' . implode(', ', $flushed)));
219 }
220
221 /**
222 * Test which stored redirect would fire for a given URL.
223 *
224 * @subcommand test
225 *
226 * @param array<int, string> $args
227 * @param array<string, string> $assocArgs
228 * @return void
229 */
230 public function test_redirect($args, $assocArgs) {
231 $this->presenter()->present($this->redirectService()->testRedirect(
232 isset($args[0]) ? trim((string)$args[0]) : ''
233 ));
234 }
235
236 private function presenter(): ABJ_404_Solution_WPCLICommandPresenter {
237 $presenter = ABJ_404_Solution_ServiceContainer::safeGet('wpcli_command_presenter');
238 if ($presenter instanceof ABJ_404_Solution_WPCLICommandPresenter) {
239 return $presenter;
240 }
241 return new ABJ_404_Solution_WPCLICommandPresenter();
242 }
243
244 private function redirectService(): ABJ_404_Solution_WPCLIRedirectCommandService {
245 $service = ABJ_404_Solution_ServiceContainer::safeGet('wpcli_redirect_command_service');
246 if ($service instanceof ABJ_404_Solution_WPCLIRedirectCommandService) {
247 return $service;
248 }
249 return new ABJ_404_Solution_WPCLIRedirectCommandService();
250 }
251
252 private function importExportService(): ABJ_404_Solution_WPCLIImportExportCommandService {
253 if ($this->injectedImportExportService instanceof ABJ_404_Solution_WPCLIImportExportCommandService) {
254 return $this->injectedImportExportService;
255 }
256 $service = ABJ_404_Solution_ServiceContainer::safeGet('wpcli_import_export_command_service');
257 if ($service instanceof ABJ_404_Solution_WPCLIImportExportCommandService) {
258 return $service;
259 }
260 return new ABJ_404_Solution_WPCLIImportExportCommandService();
261 }
262
263 /**
264 * @param array<string, string> $assocArgs
265 * @param string $key
266 * @param string $default
267 * @return string
268 */
269 private function assocString(array $assocArgs, string $key, string $default): string {
270 return isset($assocArgs[$key]) ? strtolower(trim((string)$assocArgs[$key])) : $default;
271 }
272 }
273