| 1 |
<?php |
| 2 |
|
| 3 |
|
| 4 |
if (!defined('ABSPATH')) { |
| 5 |
exit; |
| 6 |
} |
| 7 |
|
| 8 |
/* the glue that holds it together / everything else. */ |
| 9 |
|
| 10 |
require_once __DIR__ . '/PluginLogicUrlNormalization.php'; |
| 11 |
require_once __DIR__ . '/PluginLogicAdminActions.php'; |
| 12 |
require_once __DIR__ . '/PluginLogicImportExport.php'; |
| 13 |
require_once __DIR__ . '/PluginLogicSettingsUpdate.php'; |
| 14 |
require_once __DIR__ . '/PluginLogicPageOrdering.php'; |
| 15 |
require_once __DIR__ . '/PluginLogicLifecycle.php'; |
| 16 |
require_once __DIR__ . '/PluginLogicDefaults.php'; |
| 17 |
require_once __DIR__ . '/PluginLogicInterface.php'; |
| 18 |
require_once __DIR__ . '/../settings/StorageOptionContracts.php'; |
| 19 |
require_once __DIR__ . '/PluginLogicOptionsResolver.php'; |
| 20 |
require_once __DIR__ . '/PluginLogicVersionUpgrader.php'; |
| 21 |
require_once __DIR__ . '/../services/NotFoundResponseService.php'; |
| 22 |
require_once __DIR__ . '/../services/RequestIgnoreNormalizer.php'; |
| 23 |
require_once __DIR__ . '/../services/PreviousRequestCookieTracker.php'; |
| 24 |
|
| 25 |
/** |
| 26 |
* @phpstan-type PageObject object{id: int, post_parent: int, depth: int, post_type: string, post_title: string} |
| 27 |
*/ |
| 28 |
class ABJ_404_Solution_PluginLogic implements ABJ_404_Solution_PluginLogicInterface { |
| 29 |
|
| 30 |
/** @var ABJ_404_Solution_Functions */ |
| 31 |
private $f = null; |
| 32 |
|
| 33 |
/** @var ABJ_404_Solution_DataAccess */ |
| 34 |
private $dao = null; |
| 35 |
|
| 36 |
/** @var ABJ_404_Solution_Logging */ |
| 37 |
private $logger = null; |
| 38 |
|
| 39 |
/** @var ABJ_404_Solution_RedirectsRepositoryInterface */ |
| 40 |
private $redirectsRepo; |
| 41 |
|
| 42 |
/** @var ABJ_404_Solution_ViewReadServiceInterface */ |
| 43 |
private $viewRead; |
| 44 |
|
| 45 |
/** @var ABJ_404_Solution_ContentRepositoryInterface */ |
| 46 |
private $contentRepo; |
| 47 |
|
| 48 |
/** @var ABJ_404_Solution_StatsRepositoryInterface */ |
| 49 |
private $statsRepo; |
| 50 |
|
| 51 |
/** @var ABJ_404_Solution_DatabaseCoreInterface&ABJ_404_Solution_DatabaseQueryInterface */ |
| 52 |
private $dbCore; |
| 53 |
|
| 54 |
/** |
| 55 |
* @var array<string, mixed>|null Legacy test seam: reflection-based tests |
| 56 |
* seed runtime options by setting this property and resetting the |
| 57 |
* PluginLogicOptionsResolver singleton. Read by PluginLogicOptionsResolver::legacyPluginLogicOptionsOverride(). |
| 58 |
*/ |
| 59 |
private $options = null; |
| 60 |
|
| 61 |
/** @var ABJ_404_Solution_ExportService|null */ |
| 62 |
private $exportService = null; |
| 63 |
|
| 64 |
/** @var ABJ_404_Solution_ImportService|null */ |
| 65 |
private $importService = null; |
| 66 |
|
| 67 |
/** @var string|null */ |
| 68 |
private $urlHomeDirectory = null; |
| 69 |
|
| 70 |
/** @var int|null */ |
| 71 |
private $urlHomeDirectoryLength = null; |
| 72 |
|
| 73 |
/** @var self|null */ |
| 74 |
private static $instance = null; |
| 75 |
|
| 76 |
/** @var ABJ_404_Solution_PluginLogicUrlNormalization */ |
| 77 |
private $urlNormalization; |
| 78 |
|
| 79 |
/** @var ABJ_404_Solution_PluginLogicAdminActions */ |
| 80 |
private $adminActions; |
| 81 |
|
| 82 |
/** @var ABJ_404_Solution_PluginLogicImportExport */ |
| 83 |
private $importExport; |
| 84 |
|
| 85 |
/** @var ABJ_404_Solution_PluginLogicSettingsUpdate */ |
| 86 |
private $settingsUpdate; |
| 87 |
|
| 88 |
/** @var ABJ_404_Solution_PluginLogicPageOrdering */ |
| 89 |
private $pageOrdering; |
| 90 |
|
| 91 |
// (no instance-level options resolver cache; resolved via the service |
| 92 |
// container on each call — see optionsResolver()) |
| 93 |
|
| 94 |
/** |
| 95 |
* Non-resolving accessor for the cached singleton. Returns whatever is |
| 96 |
* currently stored in self::$instance without falling back to the |
| 97 |
* service container or building a new instance. Used by abj_service() |
| 98 |
* to honor a test-installed singleton override (or any other code that |
| 99 |
* has populated $instance directly) without forcing the container to |
| 100 |
* cache a stale binding. |
| 101 |
* |
| 102 |
* @return self|null |
| 103 |
*/ |
| 104 |
public static function peekInstance() { |
| 105 |
return self::$instance; |
| 106 |
} |
| 107 |
|
| 108 |
/** |
| 109 |
* Install a singleton instance directly, bypassing the service container |
| 110 |
* and the deferred construction in `getInstance()`. Symmetric with |
| 111 |
* `peekInstance()`; the canonical seam for tests that need to swap in |
| 112 |
* a test double, and for callers that have already constructed a fully |
| 113 |
* configured instance. Pass `null` to clear the cached singleton so the |
| 114 |
* next `getInstance()` falls through the container / constructor path. |
| 115 |
* |
| 116 |
* @param self|null $instance |
| 117 |
* @return void |
| 118 |
*/ |
| 119 |
public static function setInstance($instance) { |
| 120 |
self::$instance = $instance; |
| 121 |
} |
| 122 |
|
| 123 |
/** @return ABJ_404_Solution_PluginLogic The singleton instance of the class. */ |
| 124 |
public static function getInstance() { |
| 125 |
if (self::$instance !== null) { |
| 126 |
return self::$instance; |
| 127 |
} |
| 128 |
|
| 129 |
// If the DI container is initialized, prefer it. |
| 130 |
if (class_exists('ABJ_404_Solution_ServiceContainer')) { |
| 131 |
$resolved = ABJ_404_Solution_ServiceContainer::safeGet('plugin_logic'); |
| 132 |
if ($resolved instanceof self) { |
| 133 |
self::$instance = $resolved; |
| 134 |
return self::$instance; |
| 135 |
} |
| 136 |
} |
| 137 |
|
| 138 |
self::$instance = new ABJ_404_Solution_PluginLogic(); |
| 139 |
|
| 140 |
// these filters allow non-admins to have admin access to the plugin. |
| 141 |
add_filter( 'user_has_cap', |
| 142 |
'ABJ_404_Solution_PluginAdminAccessPolicy::wpUserHasCapFilter', 10, 4 ); |
| 143 |
|
| 144 |
return self::$instance; |
| 145 |
} |
| 146 |
|
| 147 |
/** |
| 148 |
* Constructor with dependency injection. |
| 149 |
* |
| 150 |
* @param ABJ_404_Solution_Functions|null $functions String manipulation utilities |
| 151 |
* @param ABJ_404_Solution_DataAccess|null $dataAccess Data access layer |
| 152 |
* @param ABJ_404_Solution_Logging|null $logging Logging service |
| 153 |
* @param ABJ_404_Solution_StatsRepositoryInterface|null $statsRepository Stats repository |
| 154 |
*/ |
| 155 |
function __construct($functions = null, $dataAccess = null, $logging = null, $statsRepository = null) { |
| 156 |
$this->f = $functions !== null ? $functions : abj_service('functions'); |
| 157 |
$this->dao = $dataAccess !== null ? $dataAccess : abj_service('data_access'); |
| 158 |
$this->logger = $logging !== null ? $logging : abj_service('logging'); |
| 159 |
|
| 160 |
if ($this->dao instanceof ABJ_404_Solution_DataAccess && get_class($this->dao) === ABJ_404_Solution_DataAccess::class) { |
| 161 |
$this->redirectsRepo = $this->dao->getRedirectsRepo(); |
| 162 |
$this->viewRead = $this->dao->getViewReadService(); |
| 163 |
$this->contentRepo = $this->dao->getContentRepo(); |
| 164 |
$this->statsRepo = $this->resolveStatsRepository($statsRepository); |
| 165 |
$this->dbCore = $this->dao->getDbCore(); |
| 166 |
} else { |
| 167 |
$this->statsRepo = $this->resolveStatsRepository($statsRepository); |
| 168 |
$this->resolveDaoAccessorsForTestMock(); |
| 169 |
} |
| 170 |
|
| 171 |
$urlPath = parse_url(get_home_url(), PHP_URL_PATH); |
| 172 |
// Fix MEDIUM #1 (5th review): Distinguish between parse failure (false) and no path (null) |
| 173 |
if ($urlPath === false) { |
| 174 |
$this->logger->debugMessage("Malformed home URL detected while initializing PluginLogic: " . get_home_url()); |
| 175 |
$this->logger->warn("Malformed home URL detected: " . get_home_url()); |
| 176 |
$urlPath = ''; |
| 177 |
} else if ($urlPath === null) { |
| 178 |
$urlPath = ''; |
| 179 |
} |
| 180 |
|
| 181 |
// Fix HIGH #2 (4th review): Decode subdirectory for consistency with runtime processing |
| 182 |
$decodedPath = abj_service('sanitizer')->normalizeUrlString(rtrim($urlPath, '/')); |
| 183 |
if (!is_string($decodedPath)) { |
| 184 |
$decodedPath = ''; |
| 185 |
} |
| 186 |
// Fix HIGH #3 (4th review): Remove null bytes and control characters for security |
| 187 |
$cleaned = preg_replace('/[\x00-\x1F\x7F]/', '', $decodedPath); |
| 188 |
$this->urlHomeDirectory = is_string($cleaned) ? $cleaned : $decodedPath; |
| 189 |
$this->urlHomeDirectoryLength = $this->f->strlen($this->urlHomeDirectory); |
| 190 |
|
| 191 |
// Initialize standalone composition classes |
| 192 |
$this->urlNormalization = new ABJ_404_Solution_PluginLogicUrlNormalization( |
| 193 |
$this->f, $this->urlHomeDirectory, $this->urlHomeDirectoryLength |
| 194 |
); |
| 195 |
|
| 196 |
$self = $this; |
| 197 |
$this->importExport = new ABJ_404_Solution_PluginLogicImportExport( |
| 198 |
function() use ($self) { return $self->getExportService(); }, |
| 199 |
function() use ($self) { return $self->getImportService(); } |
| 200 |
); |
| 201 |
|
| 202 |
$this->settingsUpdate = new ABJ_404_Solution_PluginLogicSettingsUpdate( |
| 203 |
$this->f, $this->logger, $this->contentRepo, $this, |
| 204 |
null, |
| 205 |
new ABJ_404_Solution_SettingsFieldValidator() |
| 206 |
); |
| 207 |
|
| 208 |
$this->pageOrdering = new ABJ_404_Solution_PluginLogicPageOrdering( |
| 209 |
$this->f, |
| 210 |
$this->logger, |
| 211 |
$this->contentRepo, |
| 212 |
$this->statsRepo, |
| 213 |
$this->urlNormalization, |
| 214 |
abj_service('not_found_response') |
| 215 |
); |
| 216 |
|
| 217 |
$this->adminActions = new ABJ_404_Solution_PluginLogicAdminActions( |
| 218 |
new ABJ_404_Solution_AdminActionsDependencies( |
| 219 |
$this->f, $this->logger, $this->redirectsRepo, $this->viewRead, |
| 220 |
$this->contentRepo, $this->dbCore, $this->dao, $this->urlNormalization, $this |
| 221 |
) |
| 222 |
); |
| 223 |
} |
| 224 |
|
| 225 |
/** |
| 226 |
* Polymorphic test-mock fallback: $this->dao may be a mock implementing the repo |
| 227 |
* getters, OR a mock that stands in for individual repos directly. Either shape is |
| 228 |
* accepted at runtime; PHPStan cannot follow this without per-assignment markers. |
| 229 |
* @return void |
| 230 |
*/ |
| 231 |
private function resolveDaoAccessorsForTestMock(): void { |
| 232 |
$accessors = [ |
| 233 |
'redirectsRepo' => 'getRedirectsRepo', |
| 234 |
'viewRead' => 'getViewReadService', |
| 235 |
'contentRepo' => 'getContentRepo', |
| 236 |
'dbCore' => 'getDbCore', |
| 237 |
]; |
| 238 |
foreach ($accessors as $property => $method) { |
| 239 |
if ($property === 'redirectsRepo' |
| 240 |
&& $this->daoOverridesAny([ |
| 241 |
'setupRedirect', |
| 242 |
'moveRedirectsToTrash', |
| 243 |
'deleteRedirect', |
| 244 |
'updateRedirectTypeStatus', |
| 245 |
])) { |
| 246 |
$this->{$property} = $this->dao; |
| 247 |
continue; |
| 248 |
} |
| 249 |
$value = (is_object($this->dao) && method_exists($this->dao, $method)) |
| 250 |
? $this->dao->{$method}() |
| 251 |
: $this->dao; |
| 252 |
// @phpstan-ignore-next-line assign.propertyType |
| 253 |
$this->{$property} = $value; |
| 254 |
} |
| 255 |
} |
| 256 |
|
| 257 |
/** @param ABJ_404_Solution_StatsRepositoryInterface|null $provided */ |
| 258 |
private function resolveStatsRepository($provided): ABJ_404_Solution_StatsRepositoryInterface { |
| 259 |
if ($provided instanceof ABJ_404_Solution_StatsRepositoryInterface) { |
| 260 |
return $provided; |
| 261 |
} |
| 262 |
|
| 263 |
$service = class_exists('ABJ_404_Solution_ServiceContainer') |
| 264 |
? ABJ_404_Solution_ServiceContainer::safeGet('stats_repository') |
| 265 |
: null; |
| 266 |
if ($service instanceof ABJ_404_Solution_StatsRepositoryInterface) { |
| 267 |
return $service; |
| 268 |
} |
| 269 |
|
| 270 |
return ABJ_404_Solution_StatsRepositoryResolver::resolve(__CLASS__); |
| 271 |
} |
| 272 |
|
| 273 |
/** @param array<int, string> $methods */ |
| 274 |
private function daoOverridesAny(array $methods): bool { |
| 275 |
if (!is_object($this->dao)) { |
| 276 |
return false; |
| 277 |
} |
| 278 |
foreach ($methods as $method) { |
| 279 |
if (!method_exists($this->dao, $method)) { |
| 280 |
continue; |
| 281 |
} |
| 282 |
$reflection = new ReflectionMethod($this->dao, $method); |
| 283 |
if ($reflection->getDeclaringClass()->getName() !== ABJ_404_Solution_DataAccess::class) { |
| 284 |
return true; |
| 285 |
} |
| 286 |
} |
| 287 |
return false; |
| 288 |
} |
| 289 |
|
| 290 |
/** @return ABJ_404_Solution_PluginLogicUrlNormalization */ |
| 291 |
public function urlNormalization() { |
| 292 |
return $this->urlNormalization; |
| 293 |
} |
| 294 |
|
| 295 |
/** @return ABJ_404_Solution_PluginLogicAdminActions */ |
| 296 |
public function adminActions() { |
| 297 |
return $this->adminActions; |
| 298 |
} |
| 299 |
|
| 300 |
/** @return ABJ_404_Solution_PluginLogicImportExport */ |
| 301 |
public function importExport() { |
| 302 |
return $this->importExport; |
| 303 |
} |
| 304 |
|
| 305 |
/** @return ABJ_404_Solution_PluginLogicSettingsUpdate */ |
| 306 |
public function settingsUpdate() { |
| 307 |
return $this->settingsUpdate; |
| 308 |
} |
| 309 |
|
| 310 |
/** @return ABJ_404_Solution_PluginLogicPageOrdering */ |
| 311 |
public function pageOrdering() { |
| 312 |
return $this->pageOrdering; |
| 313 |
} |
| 314 |
|
| 315 |
/** |
| 316 |
* Access the composed options resolver. Returns whichever object is |
| 317 |
* registered as abj_service('options_repository') (the same instance is |
| 318 |
* exposed there for auth-time callers that must avoid the plugin_logic |
| 319 |
* resolution cycle). Duck-typed at runtime: any object responding to |
| 320 |
* getOptions()/updateOptions() is accepted so tests can install lightweight |
| 321 |
* stubs without subclassing the concrete resolver. The declared return |
| 322 |
* type is the concrete class so PHPStan can resolve caller chains. |
| 323 |
* |
| 324 |
* @return ABJ_404_Solution_PluginLogicOptionsResolver |
| 325 |
*/ |
| 326 |
public function optionsResolver() { |
| 327 |
// No instance-level cache: tests can swap the registered service |
| 328 |
// mid-run, and the service container singleton already deduplicates. |
| 329 |
$candidate = abj_service_optional('options_repository'); |
| 330 |
if (is_object($candidate) && method_exists($candidate, 'getOptions')) { |
| 331 |
return $candidate; |
| 332 |
} |
| 333 |
// Fallback when the container is uninitialised (very-early boot, |
| 334 |
// self-healing recovery from a broken install): construct the |
| 335 |
// concrete resolver inline so callers always receive a usable |
| 336 |
// collaborator instead of null. Routed through new rather than |
| 337 |
// ::getInstance() so lint-getinstance-callers does not flag this |
| 338 |
// bootstrap fallback. |
| 339 |
return new ABJ_404_Solution_PluginLogicOptionsResolver(); |
| 340 |
} |
| 341 |
|
| 342 |
/** |
| 343 |
* Access the composed not-found response service. The frontend |
| 344 |
* dispatcher methods (sendTo404Page, forceRedirect, |
| 345 |
* thereIsAUserSpecified404Page, getCommentPartAndQueryPartOfRequest) |
| 346 |
* extracted from PluginLogic live here. |
| 347 |
* |
| 348 |
* Returns whichever object is registered as |
| 349 |
* abj_service('not_found_response'). Duck-typed at runtime so tests |
| 350 |
* can install lightweight doubles without subclassing the concrete |
| 351 |
* service. |
| 352 |
* |
| 353 |
* @return ABJ_404_Solution_NotFoundResponseService |
| 354 |
*/ |
| 355 |
public function notFoundResponse() { |
| 356 |
$candidate = abj_service_optional('not_found_response'); |
| 357 |
if (is_object($candidate) && method_exists($candidate, 'sendTo404Page')) { |
| 358 |
return $candidate; |
| 359 |
} |
| 360 |
return new ABJ_404_Solution_NotFoundResponseService(); |
| 361 |
} |
| 362 |
|
| 363 |
/** |
| 364 |
* Access the composed request-ignore normalizer. The |
| 365 |
* initializeIgnoreValues() and tryNormalPostQuery() methods |
| 366 |
* extracted from PluginLogic live here. |
| 367 |
* |
| 368 |
* @return ABJ_404_Solution_RequestIgnoreNormalizer |
| 369 |
*/ |
| 370 |
public function requestIgnoreNormalizer() { |
| 371 |
$candidate = abj_service_optional('request_ignore_normalizer'); |
| 372 |
if (is_object($candidate) && method_exists($candidate, 'tryNormalPostQuery')) { |
| 373 |
return $candidate; |
| 374 |
} |
| 375 |
return new ABJ_404_Solution_RequestIgnoreNormalizer(); |
| 376 |
} |
| 377 |
|
| 378 |
/** |
| 379 |
* Access the composed previous-request cookie tracker. The |
| 380 |
* readCookieWithPreviousRqeuestShort() and |
| 381 |
* setCookieWithPreviousRequest() methods extracted from PluginLogic |
| 382 |
* live here. |
| 383 |
* |
| 384 |
* @return ABJ_404_Solution_PreviousRequestCookieTracker |
| 385 |
*/ |
| 386 |
public function previousRequestCookieTracker() { |
| 387 |
$candidate = abj_service_optional('previous_request_cookie_tracker'); |
| 388 |
if (is_object($candidate) && method_exists($candidate, 'setCookieWithPreviousRequest')) { |
| 389 |
return $candidate; |
| 390 |
} |
| 391 |
return new ABJ_404_Solution_PreviousRequestCookieTracker(); |
| 392 |
} |
| 393 |
|
| 394 |
/** |
| 395 |
* Alias accessor for the primary 404 dispatcher service. The |
| 396 |
* original parent task asked for a single PluginLogicRequestDispatcher |
| 397 |
* accessor; the extraction split the responsibilities across three |
| 398 |
* single-responsibility services instead. NotFoundResponseService is |
| 399 |
* the one that actually dispatches the 404 response, so this alias |
| 400 |
* returns it. Prefer notFoundResponse() / requestIgnoreNormalizer() / |
| 401 |
* previousRequestCookieTracker() at new call sites. |
| 402 |
* |
| 403 |
* Why: per [[feedback_no_trait_extractions]], the project rejects |
| 404 |
* grab-bag extractions and prefers SRP-aligned classes; combining |
| 405 |
* the three responsibilities behind one wrapper would be a |
| 406 |
* pass-through facade flagged by [[modularity-extraction]]. |
| 407 |
* |
| 408 |
* @return ABJ_404_Solution_NotFoundResponseService |
| 409 |
*/ |
| 410 |
public function requestDispatcher() { |
| 411 |
return $this->notFoundResponse(); |
| 412 |
} |
| 413 |
|
| 414 |
/** |
| 415 |
* Access the composed version upgrader. Returns whichever object is |
| 416 |
* registered as abj_service('version_upgrade'). Duck-typed at runtime: |
| 417 |
* any object responding to upgradeIfNeeded()/runUpgradeAction()/ |
| 418 |
* stampDbVersion() is accepted so tests can install lightweight stubs |
| 419 |
* without subclassing the concrete upgrader. The declared return type |
| 420 |
* is the concrete class so PHPStan can resolve caller chains. |
| 421 |
* |
| 422 |
* @return ABJ_404_Solution_PluginLogicVersionUpgrader |
| 423 |
*/ |
| 424 |
public function versionUpgrader() { |
| 425 |
$candidate = abj_service('version_upgrade'); |
| 426 |
if (is_object($candidate) && method_exists($candidate, 'upgradeIfNeeded')) { |
| 427 |
return $candidate; |
| 428 |
} |
| 429 |
// Fallback when the `version_upgrade` container key is unavailable |
| 430 |
// (very-early boot, self-healing recovery from a broken install). |
| 431 |
// Construct via `new` with individual services resolved so we route |
| 432 |
// through the container's dependency wiring without going through |
| 433 |
// the singleton entry (lint-getinstance-callers). |
| 434 |
$dao = abj_service('data_access'); |
| 435 |
$dbCore = is_object($dao) && method_exists($dao, 'getDbCore') ? $dao->getDbCore() : $dao; |
| 436 |
return new ABJ_404_Solution_PluginLogicVersionUpgrader( |
| 437 |
abj_service('functions'), |
| 438 |
abj_service('logging'), |
| 439 |
is_object($dbCore) ? $dbCore : (object)[] |
| 440 |
); |
| 441 |
} |
| 442 |
|
| 443 |
/** @return ABJ_404_Solution_ExportService */ |
| 444 |
private function getExportService() { |
| 445 |
if ($this->exportService !== null) { |
| 446 |
return $this->exportService; |
| 447 |
} |
| 448 |
|
| 449 |
if (!class_exists('ABJ_404_Solution_ExportService')) { |
| 450 |
require_once __DIR__ . '/../import/ExportService.php'; |
| 451 |
} |
| 452 |
|
| 453 |
$this->exportService = new ABJ_404_Solution_ExportService( |
| 454 |
abj_service('view_read_service'), |
| 455 |
$this->logger, |
| 456 |
abj_service('redirects_repository') |
| 457 |
); |
| 458 |
return $this->exportService; |
| 459 |
} |
| 460 |
|
| 461 |
/** @return ABJ_404_Solution_ImportService */ |
| 462 |
private function getImportService() { |
| 463 |
if ($this->importService !== null) { |
| 464 |
return $this->importService; |
| 465 |
} |
| 466 |
|
| 467 |
if (!class_exists('ABJ_404_Solution_ImportService')) { |
| 468 |
require_once __DIR__ . '/../import/ImportService.php'; |
| 469 |
} |
| 470 |
|
| 471 |
$this->importService = new ABJ_404_Solution_ImportService( |
| 472 |
abj_service('redirects_repository'), |
| 473 |
abj_service('content_repository'), |
| 474 |
$this->logger |
| 475 |
); |
| 476 |
return $this->importService; |
| 477 |
} |
| 478 |
|
| 479 |
/** Instance counterpart used by the Data layer through PluginLogicInterface. */ |
| 480 |
public function registerCrons(): void { |
| 481 |
ABJ_404_Solution_PluginLogicLifecycle::doRegisterCrons(); |
| 482 |
} |
| 483 |
|
| 484 |
} |
| 485 |
|