| 1 |
<?php |
| 2 |
/** |
| 3 |
* Full-site-import module (spec 025-full-site-import). |
| 4 |
* |
| 5 |
* The FSI engine — a 10-runner sequential pipeline, SSE-streamed progress, session |
| 6 |
* management via WordPress options, an exception hierarchy for retry/skip/fatal error |
| 7 |
* handling, and 11 admin-ajax actions registered as `wp_ajax_templately_pack_*`. |
| 8 |
* Relocated (git mv) from includes/Core/Importer/ — behavior byte-identical (same |
| 9 |
* ajax actions, params, option keys, runner order). |
| 10 |
* |
| 11 |
* Activation mechanism (distinct from the REST-route pattern most modules use): this |
| 12 |
* module has almost NO `templately/v1` REST routes of its own — with TWO exceptions |
| 13 |
* (FR-018), both replacing a `wp_ajax_templately_pack_*` action as the primary transport |
| 14 |
* while the old ajax action stays registered as a deprecated shim (browsers with an |
| 15 |
* already-cached JS bundle referencing the old action name would otherwise break), |
| 16 |
* registered exactly as before via `add_ajax_action()`: |
| 17 |
* - `POST /templately/v1/import/global-settings` (`REST/GlobalSettings.php`, backlog B8, |
| 18 |
* 2026-07-16) — the settings-only import mode. |
| 19 |
* - `GET /templately/v1/import/info` (`REST/PackInfo.php`, 2026-07-16) — the pack |
| 20 |
* import-info fetch. GET because the handler is a pure read (no session/option writes); |
| 21 |
* it matches the spec's ajax→REST mapping table. |
| 22 |
* Both routes share the upstream `v2/import/info/pack/{id}` fetch via |
| 23 |
* `Utils\PackInfoFetcher` (de-duplicated from the two former private copies). The FSI |
| 24 |
* module used to have |
| 25 |
* an FSI-adjacent stub REST route (`GET /templately/v1/site-import/{id}` via a legacy |
| 26 |
* `includes/API\FullSiteImport` class) — deleted during Phase 3 consolidation |
| 27 |
* (2026-07-05): the class was never actually instantiated anywhere (confirmed via a |
| 28 |
* full-repo grep), so the route was never even registered, and its callback method |
| 29 |
* didn't exist either (would have fataled had it somehow been reachable). Pure dead |
| 30 |
* code with zero consumers (no frontend caller referenced it) — removed rather than |
| 31 |
* migrated. The FSI PHP activates purely by CONSTRUCTING `FullSiteImport` — its |
| 32 |
* constructor wires up all the `wp_ajax_templately_pack_*` action hooks. So the |
| 33 |
* module's job is simply to call `FullSiteImport::get_instance()` (plus, now, register |
| 34 |
* the one REST route from `register_rest_routes()`, the standard lazy `rest_api_init` |
| 35 |
* pattern every module-owned REST class uses). |
| 36 |
* |
| 37 |
* We do that from `init_hooks()` — the one lifecycle method that runs during module |
| 38 |
* construction (at `plugins_loaded`, via Modules_Manager::boot()). This is the exact |
| 39 |
* moment the old `Plugin::plugins_loaded()` used to call `FullSiteImport::get_instance()`, |
| 40 |
* and it is the semantically correct "the module is now active, wire up its runtime |
| 41 |
* behavior" point (constructor-time side effects — NOT REST registration, which must |
| 42 |
* wait for `rest_api_init`). This mirrors the developer-tools module, which likewise |
| 43 |
* boots its `Developer::get_instance()` singleton from `init_hooks()`. |
| 44 |
* |
| 45 |
* `get_dependencies()` was intentionally EMPTY until 2026-07-26: the FSI constructor |
| 46 |
* instantiates only this module's own `Ajax\*` controllers and registers ajax hooks — |
| 47 |
* it needs no other module booted first, and cross-module class references (e.g. the AI |
| 48 |
* merger) autoload lazily at request time. The original monolith had no |
| 49 |
* module-dependency gating; adding one risks the module being skipped on a dep-name |
| 50 |
* mismatch. It now declares exactly one dependency, `mcp-core`, for the agent |
| 51 |
* capabilities in `Abilities/` — see the note on the method itself for why that one is |
| 52 |
* safe to name and why the rest stay undeclared. |
| 53 |
* |
| 54 |
* @package Templately |
| 55 |
*/ |
| 56 |
|
| 57 |
namespace Templately\Modules\FullSiteImport; |
| 58 |
|
| 59 |
use Templately\Core\Module_Base; |
| 60 |
use Templately\Modules\FullSiteImport\Abilities\AgentCapabilities; |
| 61 |
use Templately\Modules\FullSiteImport\Cleanup\CleanupTasks; |
| 62 |
use Templately\Modules\FullSiteImport\REST\GlobalSettings; |
| 63 |
use Templately\Modules\FullSiteImport\REST\PackInfo; |
| 64 |
|
| 65 |
class Module extends Module_Base { |
| 66 |
|
| 67 |
public function get_name(): string { |
| 68 |
return 'full-site-import'; |
| 69 |
} |
| 70 |
|
| 71 |
/** |
| 72 |
* The ONE declared dependency, and a narrow one: `Abilities/` contributes this |
| 73 |
* module's five agent capabilities to `McpCore\Registry\ToolRegistry` and reads |
| 74 |
* access levels off `McpCore\Registry\ToolDescriptor`. |
| 75 |
* |
| 76 |
* This does not reintroduce the boot-ordering risk the docblock above warns |
| 77 |
* about. `mcp-core` registers no hooks, declares no requirements and overrides |
| 78 |
* no `is_active()` — it cannot fail to boot, so naming it cannot cause this |
| 79 |
* module to be skipped. Every OTHER cross-module reference here is still |
| 80 |
* lazy-autoloaded at request time and still deliberately undeclared. |
| 81 |
*/ |
| 82 |
public function get_dependencies(): array { |
| 83 |
// `utilities` owns the cleanup engine this module contributes tasks to, |
| 84 |
// and the guarded remover those tasks delete through (spec 052). The |
| 85 |
// arrow points one way: utilities never names this module. |
| 86 |
// |
| 87 |
// `pro-plugin-provisioning` (spec 059) is deliberately NOT listed, though the |
| 88 |
// runner reads its catalog. Declaring it makes Modules_Manager SKIP this whole |
| 89 |
// module whenever that one is inactive — the exact opposite of "behaves as |
| 90 |
// before when disabled". The reads are soft (`class_exists` + `is_live()`), so |
| 91 |
// it is baselined as an intentional reach-in instead. |
| 92 |
return [ 'mcp-core', 'utilities' ]; |
| 93 |
} |
| 94 |
|
| 95 |
protected function init_hooks(): void { |
| 96 |
// Constructing the singleton wires the wp_ajax_templately_pack_* action hooks |
| 97 |
// (and the independent Ajax\* controllers) — the FSI engine's activation point. |
| 98 |
FullSiteImport::get_instance(); |
| 99 |
|
| 100 |
// The agent-facing surface over that engine (spec 045). Registration only — |
| 101 |
// nothing here runs unless a transport actually invokes a capability. |
| 102 |
AgentCapabilities::register(); |
| 103 |
|
| 104 |
// This module's contribution to the shared cleanup service (spec 052): |
| 105 |
// expired session/AI records, abandoned working and preview directories, |
| 106 |
// and old import logs. Hands over class names only — nothing is |
| 107 |
// constructed unless a sweep actually runs. |
| 108 |
CleanupTasks::register(); |
| 109 |
} |
| 110 |
|
| 111 |
public function register_rest_routes(): void { |
| 112 |
// FR-018 / backlog B8: the settings-only import mode's REST transport. |
| 113 |
GlobalSettings::get_instance()->register_routes(); |
| 114 |
// FR-018 (§9 ajax→REST): pack import-info fetch (GET /import/info). Both routes |
| 115 |
// share the upstream fetch via Utils\PackInfoFetcher; the matching ajax actions |
| 116 |
// (import_info / import_global_settings) stay registered as deprecated shims. |
| 117 |
PackInfo::get_instance()->register_routes(); |
| 118 |
} |
| 119 |
} |
| 120 |
|