hooks = $hooks; } /** * Queues the service's WordPress hooks via the hook loader. * * @return void */ public function register(): void { $this->hooks->add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_editor_assets' ) ); $this->hooks->add_filter( 'the_posts', array( $this, 'scan_for_visibility' ) ); $this->hooks->add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_assets' ) ); } /** * Enqueues the editor script and stylesheet. * * @return void */ public function enqueue_editor_assets(): void { $script_path = BLOCKSIEVE_PATH . 'build/index.js'; if ( ! is_readable( $script_path ) ) { return; // Editor UI not built yet — nothing to enqueue. } $asset = $this->script_asset(); wp_enqueue_script( self::EDITOR_SCRIPT_HANDLE, BLOCKSIEVE_URL . 'build/index.js', $asset['dependencies'], $asset['version'], true ); /* * Point the editor bundle at the plugin's JED translation files so the * inspector UI is translatable alongside the PHP strings. */ wp_set_script_translations( self::EDITOR_SCRIPT_HANDLE, 'blocksieve', BLOCKSIEVE_PATH . 'languages' ); $this->enqueue_editor_style(); } /** * Scans queried posts for a serialised Device condition. * * Sets the front-end flag so the stylesheet is only loaded when a block on * the page can actually use it. Accepts `mixed` because `the_posts` is a * public filter another plugin may feed something other than an array. * * @param mixed $posts Queried posts. * @return mixed The posts, unchanged. */ public function scan_for_visibility( $posts ) { if ( $this->needs_frontend_assets || is_admin() || ! is_array( $posts ) ) { return $posts; } foreach ( $posts as $post ) { if ( ! $post instanceof \WP_Post ) { continue; } $content = (string) $post->post_content; if ( false !== strpos( $content, self::VISIBILITY_MARKER ) && false !== strpos( $content, self::DEVICE_MARKER ) ) { $this->needs_frontend_assets = true; break; } } return $posts; } /** * Enqueues the front-end stylesheet when the page uses visibility. * * @return void */ public function enqueue_frontend_assets(): void { if ( ! $this->needs_frontend_assets ) { return; // Lazy: skip on pages without visibility-configured blocks. } $path = BLOCKSIEVE_PATH . 'assets/css/front.css'; if ( ! is_readable( $path ) ) { return; } wp_enqueue_style( self::FRONT_STYLE_HANDLE, BLOCKSIEVE_URL . 'assets/css/front.css', array(), // Bust caches whenever the stylesheet changes, not only on a version bump. BLOCKSIEVE_VERSION . '-' . (string) filemtime( $path ) ); } /** * Reads the generated dependency map for the editor script. * * Falls back to a version-only map when the asset file is missing or malformed. * * @return array{dependencies:array,version:string} */ private function script_asset(): array { $asset_file = BLOCKSIEVE_PATH . 'build/index.asset.php'; $fallback = array( 'dependencies' => array(), 'version' => BLOCKSIEVE_VERSION, ); if ( ! is_readable( $asset_file ) ) { return $fallback; } $asset = require $asset_file; if ( ! is_array( $asset ) || ! isset( $asset['dependencies'], $asset['version'] ) ) { return $fallback; } return array( 'dependencies' => is_array( $asset['dependencies'] ) ? $asset['dependencies'] : array(), 'version' => is_string( $asset['version'] ) ? $asset['version'] : $fallback['version'], ); } /** * Enqueues the editor stylesheet when it has been compiled. * * @return void */ private function enqueue_editor_style(): void { $style_path = BLOCKSIEVE_PATH . 'build/style-index.css'; if ( ! is_readable( $style_path ) ) { return; } wp_enqueue_style( self::EDITOR_STYLE_HANDLE, BLOCKSIEVE_URL . 'build/style-index.css', array(), // Bust caches on every rebuild, not only on a version bump. BLOCKSIEVE_VERSION . '-' . (string) filemtime( $style_path ) ); // The build ships a mirrored stylesheet for right-to-left locales. wp_style_add_data( self::EDITOR_STYLE_HANDLE, 'rtl', 'replace' ); } }