| 1 |
<?php |
| 2 |
/** |
| 3 |
* Site Editor Views module (spec 054-site-editor-views). |
| 4 |
* |
| 5 |
* Makes imported content legible where it lives: records where each imported |
| 6 |
* item came from, and — on a host that supports contributed screen |
| 7 |
* configuration — surfaces that on the Site Editor's Pages screen as three |
| 8 |
* fields and two saved views. |
| 9 |
* |
| 10 |
* The module has TWO halves and they are gated differently on purpose: |
| 11 |
* |
| 12 |
* - **Recording** runs everywhere, down to the advertised WordPress floor. It |
| 13 |
* has no host requirement at all. |
| 14 |
* - **Presenting** attaches only where `site-editor-view-config` resolves. |
| 15 |
* |
| 16 |
* That is why the gate is a SUB-FEATURE gate rather than `get_requirements()`. |
| 17 |
* A module-level requirement would stop the module booting below WP 7.1, which |
| 18 |
* would stop provenance being RECORDED there — manufacturing exactly the |
| 19 |
* backfill problem spec 054 rules out (FR-005). A site that upgrades later |
| 20 |
* finds its post-feature imports already legible, and that only works if the |
| 21 |
* recording half never depended on the host. |
| 22 |
* |
| 23 |
* Scope, and the reason it is narrow: v1 contributes to the PAGES screen only |
| 24 |
* (FR-009). The Templates and Parts screens list the host's own template |
| 25 |
* entities, and Templately theme-builder templates are a separate post type, so |
| 26 |
* fields contributed there could never render. The Patterns screen fails the |
| 27 |
* mirror test — catalog patterns are registered on demand, not stored as user |
| 28 |
* pattern posts. An earlier plan claimed otherwise; it was wrong. |
| 29 |
* |
| 30 |
* It DOES ship JavaScript, contrary to what this docblock claimed until |
| 31 |
* 2026-08-04. PHP decides which fields and views a screen offers; what each |
| 32 |
* field IS — its label, and how to read its value off a record — is defined on |
| 33 |
* the client through `wp.editor.registerEntityField()`. Contribute ids without |
| 34 |
* that and the views render and then filter nothing. See |
| 35 |
* `enqueue_field_definitions()` below. |
| 36 |
* |
| 37 |
* PHP 7.2 SYNTAX ONLY (Constitution XV). |
| 38 |
* |
| 39 |
* @package Templately |
| 40 |
*/ |
| 41 |
|
| 42 |
namespace Templately\Modules\SiteEditorViews; |
| 43 |
|
| 44 |
use Templately\Core\Module_Base; |
| 45 |
|
| 46 |
defined( 'ABSPATH' ) || exit; |
| 47 |
|
| 48 |
class Module extends Module_Base { |
| 49 |
|
| 50 |
/** |
| 51 |
* @var Provenance |
| 52 |
*/ |
| 53 |
protected $provenance; |
| 54 |
|
| 55 |
public function get_name(): string { |
| 56 |
return 'site-editor-views'; |
| 57 |
} |
| 58 |
|
| 59 |
/** |
| 60 |
* The presentation half's gate. |
| 61 |
* |
| 62 |
* NOT `get_requirements()` — see the class docblock. An unmet gate leaves |
| 63 |
* the module booted and only detaches what it guards. |
| 64 |
* |
| 65 |
* @return array<string, string> |
| 66 |
*/ |
| 67 |
public function get_capability_gates(): array { |
| 68 |
return [ |
| 69 |
'screen-views' => 'site-editor-view-config', |
| 70 |
]; |
| 71 |
} |
| 72 |
|
| 73 |
protected function init_hooks(): void { |
| 74 |
$this->provenance = new Provenance(); |
| 75 |
|
| 76 |
// Recording — unconditional, every host. |
| 77 |
add_action( 'init', [ $this->provenance, 'register' ] ); |
| 78 |
add_filter( 'templately_fsi_item_meta', [ $this->provenance, 'contribute' ], 10, 3 ); |
| 79 |
// Single-template imports have no pipeline bag to contribute to; they announce |
| 80 |
// themselves through the producer-neutral action instead (spec 060 FR-002a). |
| 81 |
add_action( 'templately_blocks_imported', [ $this->provenance, 'stamp_single' ], 10, 2 ); |
| 82 |
|
| 83 |
// Presenting — only where the host can. Nothing below is registered |
| 84 |
// otherwise, so there is no filter to misbehave, no notice, and no |
| 85 |
// dead UI (FR-010). |
| 86 |
if ( ! $this->has_gate( 'screen-views' ) ) { |
| 87 |
return; |
| 88 |
} |
| 89 |
|
| 90 |
$this->add_component( 'view-config', new ViewConfig( $this->provenance ) ); |
| 91 |
|
| 92 |
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_field_definitions' ] ); |
| 93 |
} |
| 94 |
|
| 95 |
/** |
| 96 |
* Register the field DEFINITIONS on the client. |
| 97 |
* |
| 98 |
* The server-side configuration only references field ids; what each field |
| 99 |
* IS — its label and how to read its value — is defined through the |
| 100 |
* editor's client registrar. Contributing ids without this produces views |
| 101 |
* that render and then filter nothing, which is what an E2E run on 7.1 |
| 102 |
* caught after the PHP-only implementation looked complete. |
| 103 |
* |
| 104 |
* Enqueued on the Site Editor screen only. It has no job anywhere else, and |
| 105 |
* the SPA bundle never loads there. |
| 106 |
* |
| 107 |
* @param string $hook The current admin page. |
| 108 |
* @return void |
| 109 |
*/ |
| 110 |
public function enqueue_field_definitions( $hook ): void { |
| 111 |
if ( 'site-editor.php' !== $hook ) { |
| 112 |
return; |
| 113 |
} |
| 114 |
|
| 115 |
$handle = 'templately-site-editor-views'; |
| 116 |
$asset = TEMPLATELY_PATH . 'assets/js/site-editor-views.asset.php'; |
| 117 |
|
| 118 |
// These two are declared by hand and MERGED with whatever the build |
| 119 |
// extracted, never replaced by it. The script reads `wp.editor` and |
| 120 |
// `wp.i18n` off the global rather than importing the packages, so |
| 121 |
// dependency extraction correctly finds nothing to extract — and an |
| 122 |
// empty dependency list means WordPress is free to run this before |
| 123 |
// `wp-editor` exists, at which point the registrar is undefined and |
| 124 |
// nothing registers. That failure is silent: the views still render, |
| 125 |
// and then filter nothing. It cost an E2E run to find once already. |
| 126 |
$deps = [ 'wp-editor', 'wp-i18n' ]; |
| 127 |
$version = TEMPLATELY_VERSION; |
| 128 |
|
| 129 |
if ( file_exists( $asset ) ) { |
| 130 |
$meta = include $asset; |
| 131 |
|
| 132 |
if ( isset( $meta['dependencies'] ) && is_array( $meta['dependencies'] ) ) { |
| 133 |
$deps = array_values( array_unique( array_merge( $deps, $meta['dependencies'] ) ) ); |
| 134 |
} |
| 135 |
|
| 136 |
$version = isset( $meta['version'] ) ? $meta['version'] : $version; |
| 137 |
} |
| 138 |
|
| 139 |
wp_enqueue_script( |
| 140 |
$handle, |
| 141 |
TEMPLATELY_URL . 'assets/js/site-editor-views.js', |
| 142 |
$deps, |
| 143 |
$version, |
| 144 |
true |
| 145 |
); |
| 146 |
} |
| 147 |
} |
| 148 |
|