PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.8
Yatra – Travel Booking & Tour Operator Software v3.0.2.8
3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 2.0.10 2.0.11 All 82 releases
yatra / app / Blocks / BlockEditorScript.php

BlockEditorScript.php in Yatra – Travel Booking & Tour Operator Software 3.0.2.8, at app/Blocks/BlockEditorScript.php

250 lines 8.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Yatra\Blocks;
6
7 /**
8 * Resolves the Gutenberg editor script URL for yatra/tour, yatra/activity, yatra/destination.
9 *
10 * Prefer Vite output under assets/dist/blocks/ (present after npm run build). Fall back to
11 * legacy IIFE scripts under resources/js/blocks/{slug}/block.js (dev or when dist is missing).
12 */
13 final class BlockEditorScript
14 {
15 /**
16 * Script handles registered for Yatra blocks (used to force-enqueue in the block editor).
17 *
18 * @var list<string>
19 */
20 private static array $registeredEditorHandles = [];
21
22 private static bool $enqueueHookAdded = false;
23
24 /**
25 * Editor script handles built as ESM (Vite dist). WP_Scripts::do_item() does not pass
26 * wp_script_add_data( $h, 'type', 'module' ) into wp_get_script_attributes(), so we add
27 * type="module" via the wp_script_attributes filter.
28 *
29 * @var array<string, true>
30 */
31 private static array $esModuleHandles = [];
32
33 private static bool $scriptAttributesFilterAdded = false;
34
35 /**
36 * @return array{url: string, module: bool}|null
37 */
38 public static function resolveEditorBundle(string $slug): ?array
39 {
40 $slug = preg_replace('/[^a-z]/', '', strtolower($slug));
41 if ($slug === '') {
42 return null;
43 }
44
45 $distPath = YATRA_PLUGIN_PATH . 'assets/dist/blocks/' . $slug . '.js';
46 if (file_exists($distPath) && is_readable($distPath)) {
47 // Built by `node scripts/build-blocks.mjs` as a single IIFE (no `import`), so a normal script tag works.
48 return [
49 'url' => YATRA_PLUGIN_URL . 'assets/dist/blocks/' . $slug . '.js',
50 'module' => false,
51 ];
52 }
53
54 $legacyPath = YATRA_PLUGIN_PATH . 'resources/js/blocks/' . $slug . '/block.js';
55 if (file_exists($legacyPath) && is_readable($legacyPath)) {
56 return [
57 'url' => YATRA_PLUGIN_URL . 'resources/js/blocks/' . $slug . '/block.js',
58 'module' => false,
59 ];
60 }
61
62 return null;
63 }
64
65 /**
66 * Dependencies for block editor scripts. Include wp-data / wp-hooks so the editor runtime
67 * matches what @wordpress/block-editor and @wordpress/components expect when scripts load.
68 *
69 * @return list<string>
70 */
71 public static function editorDependencies(): array
72 {
73 return [
74 'wp-blocks',
75 'wp-element',
76 'wp-block-editor',
77 'wp-components',
78 'wp-i18n',
79 'wp-data',
80 'wp-hooks',
81 'wp-server-side-render',
82 ];
83 }
84
85 public static function blockJsonPath(string $slug): string
86 {
87 $slug = preg_replace('/[^a-z]/', '', strtolower($slug));
88
89 return YATRA_PLUGIN_PATH . 'resources/js/blocks/' . $slug . '/block.json';
90 }
91
92 /**
93 * Drop a prior registration of the same block name so this plugin can register the full definition.
94 * Legacy add-ons or partial loads sometimes register yatra/* blocks without editor assets, which hides
95 * them from the inserter or leaves them broken.
96 *
97 * @param string $name Full block name, e.g. yatra/tour
98 */
99 public static function reclaimBlockName(string $name): void
100 {
101 if (!apply_filters('yatra_reclaim_block_registration', true, $name)) {
102 return;
103 }
104
105 if (\WP_Block_Type_Registry::get_instance()->is_registered($name)) {
106 unregister_block_type($name);
107 }
108 }
109
110 /**
111 * @param list<string> $deps
112 */
113 public static function register(string $handle, string $slug, array $deps): bool
114 {
115 $bundle = self::resolveEditorBundle($slug);
116 if ($bundle === null) {
117 /**
118 * Last resort: try legacy path even if resolve failed (e.g. open_basedir quirks).
119 */
120 $slugClean = preg_replace('/[^a-z]/', '', strtolower($slug)) ?? '';
121 if ($slugClean !== '') {
122 $legacyPath = YATRA_PLUGIN_PATH . 'resources/js/blocks/' . $slugClean . '/block.js';
123 if (file_exists($legacyPath)) {
124 $bundle = [
125 'url' => YATRA_PLUGIN_URL . 'resources/js/blocks/' . $slugClean . '/block.js',
126 'module' => false,
127 ];
128 }
129 }
130 }
131 if ($bundle === null) {
132 if (defined('WP_DEBUG') && WP_DEBUG) {
133 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
134 error_log(
135 sprintf(
136 '[Yatra] Block editor script not found for slug "%s" (handle "%s"). Run npm run build in the plugin directory.',
137 $slug,
138 $handle
139 )
140 );
141 }
142
143 return false;
144 }
145
146 wp_register_script(
147 $handle,
148 $bundle['url'],
149 $deps,
150 YATRA_VERSION,
151 true
152 );
153
154 if ($bundle['module']) {
155 wp_script_add_data($handle, 'type', 'module');
156 self::$esModuleHandles[$handle] = true;
157 self::ensureEsModuleAttributesFilter();
158 }
159
160 if (function_exists('wp_set_script_translations')) {
161 wp_set_script_translations($handle, 'yatra', YATRA_PLUGIN_PATH . 'i18n/languages');
162 }
163
164 if (! in_array($handle, self::$registeredEditorHandles, true)) {
165 self::$registeredEditorHandles[] = $handle;
166 }
167 self::ensureEnqueueBlockEditorAssetsHook();
168
169 return true;
170 }
171
172 /**
173 * Ensure Yatra block editor scripts are enqueued in Gutenberg (defensive; core usually does this).
174 */
175 private static function ensureEnqueueBlockEditorAssetsHook(): void
176 {
177 if (self::$enqueueHookAdded) {
178 return;
179 }
180 self::$enqueueHookAdded = true;
181 add_action('enqueue_block_editor_assets', [self::class, 'enqueueRegisteredEditorScripts'], 20);
182 }
183
184 public static function enqueueRegisteredEditorScripts(): void
185 {
186 foreach (self::$registeredEditorHandles as $handle) {
187 if (wp_script_is($handle, 'registered')) {
188 wp_enqueue_script($handle);
189 }
190 }
191 }
192
193 private static function ensureEsModuleAttributesFilter(): void
194 {
195 if (self::$scriptAttributesFilterAdded) {
196 return;
197 }
198 self::$scriptAttributesFilterAdded = true;
199 add_filter('wp_script_attributes', [self::class, 'filterEsModuleScriptAttributes'], 20, 1);
200 // Core builds $attr before merging wp_script_add_data(…, 'type', 'module'); the attributes filter
201 // should add type, but some hosts/plugins strip it — ensure the final <script> tag is a module.
202 add_filter('script_loader_tag', [self::class, 'filterScriptLoaderTag'], 20, 3);
203 }
204
205 /**
206 * @param array<string, string|bool> $attributes
207 * @return array<string, string|bool>
208 */
209 public static function filterEsModuleScriptAttributes(array $attributes): array
210 {
211 $id = isset($attributes['id']) && is_string($attributes['id']) ? $attributes['id'] : '';
212 if ($id === '' || substr($id, -3) !== '-js') {
213 return $attributes;
214 }
215
216 $handle = substr($id, 0, -3);
217 if ($handle === '' || empty(self::$esModuleHandles[$handle])) {
218 return $attributes;
219 }
220
221 if (!empty($attributes['type'])) {
222 return $attributes;
223 }
224
225 $attributes['type'] = 'module';
226
227 return $attributes;
228 }
229
230 /**
231 * Fallback: guarantee type="module" on the printed tag (see class docblock).
232 *
233 * @param string $tag Full script tag HTML.
234 * @param string $handle Script handle.
235 * @param string $src Source URL (unused).
236 */
237 public static function filterScriptLoaderTag(string $tag, string $handle, string $src): string
238 {
239 unset($src);
240 if ($handle === '' || empty(self::$esModuleHandles[$handle])) {
241 return $tag;
242 }
243 if (preg_match('/\btype\s*=\s*["\']?module["\']?/i', $tag)) {
244 return $tag;
245 }
246
247 return (string) preg_replace('/<script\b/i', '<script type="module"', $tag, 1);
248 }
249 }
250