PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.3
Yatra – Travel Booking & Tour Operator Software v3.0.3
3.0.15 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 All 83 releases
yatra / app / Blocks / BlockEditorScript.php

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

244 lines 8.0 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, yatra/trip-category.
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 // Keep hyphens for on-disk paths (e.g. trip-category/block.json). Editor bundles still use
88 // letters-only slugs in resolveEditorBundle() (e.g. tripcategory.js).
89 $slug = strtolower((string) $slug);
90 $slug = preg_replace('/[^a-z0-9_-]/', '', $slug);
91
92 return YATRA_PLUGIN_PATH . 'resources/js/blocks/' . $slug . '/block.json';
93 }
94
95 /**
96 * Drop a prior registration of the same block name so this plugin can register the full definition.
97 * Legacy add-ons or partial loads sometimes register yatra/* blocks without editor assets, which hides
98 * them from the inserter or leaves them broken.
99 *
100 * @param string $name Full block name, e.g. yatra/tour
101 */
102 public static function reclaimBlockName(string $name): void
103 {
104 if (!apply_filters('yatra_reclaim_block_registration', true, $name)) {
105 return;
106 }
107
108 if (\WP_Block_Type_Registry::get_instance()->is_registered($name)) {
109 unregister_block_type($name);
110 }
111 }
112
113 /**
114 * @param list<string> $deps
115 */
116 public static function register(string $handle, string $slug, array $deps): bool
117 {
118 $bundle = self::resolveEditorBundle($slug);
119 if ($bundle === null) {
120 /**
121 * Last resort: try legacy path even if resolve failed (e.g. open_basedir quirks).
122 */
123 $slugClean = preg_replace('/[^a-z]/', '', strtolower($slug)) ?? '';
124 if ($slugClean !== '') {
125 $legacyPath = YATRA_PLUGIN_PATH . 'resources/js/blocks/' . $slugClean . '/block.js';
126 if (file_exists($legacyPath)) {
127 $bundle = [
128 'url' => YATRA_PLUGIN_URL . 'resources/js/blocks/' . $slugClean . '/block.js',
129 'module' => false,
130 ];
131 }
132 }
133 }
134 if ($bundle === null) {
135
136
137 return false;
138 }
139
140 wp_register_script(
141 $handle,
142 $bundle['url'],
143 $deps,
144 YATRA_VERSION,
145 true
146 );
147
148 if ($bundle['module']) {
149 wp_script_add_data($handle, 'type', 'module');
150 self::$esModuleHandles[$handle] = true;
151 self::ensureEsModuleAttributesFilter();
152 }
153
154 if (function_exists('wp_set_script_translations')) {
155 wp_set_script_translations($handle, 'yatra', YATRA_PLUGIN_PATH . 'i18n/languages');
156 }
157
158 if (! in_array($handle, self::$registeredEditorHandles, true)) {
159 self::$registeredEditorHandles[] = $handle;
160 }
161 self::ensureEnqueueBlockEditorAssetsHook();
162
163 return true;
164 }
165
166 /**
167 * Ensure Yatra block editor scripts are enqueued in Gutenberg (defensive; core usually does this).
168 */
169 private static function ensureEnqueueBlockEditorAssetsHook(): void
170 {
171 if (self::$enqueueHookAdded) {
172 return;
173 }
174 self::$enqueueHookAdded = true;
175 add_action('enqueue_block_editor_assets', [self::class, 'enqueueRegisteredEditorScripts'], 20);
176 }
177
178 public static function enqueueRegisteredEditorScripts(): void
179 {
180 foreach (self::$registeredEditorHandles as $handle) {
181 if (wp_script_is($handle, 'registered')) {
182 wp_enqueue_script($handle);
183 }
184 }
185 }
186
187 private static function ensureEsModuleAttributesFilter(): void
188 {
189 if (self::$scriptAttributesFilterAdded) {
190 return;
191 }
192 self::$scriptAttributesFilterAdded = true;
193 add_filter('wp_script_attributes', [self::class, 'filterEsModuleScriptAttributes'], 20, 1);
194 // Core builds $attr before merging wp_script_add_data(…, 'type', 'module'); the attributes filter
195 // should add type, but some hosts/plugins strip it — ensure the final <script> tag is a module.
196 add_filter('script_loader_tag', [self::class, 'filterScriptLoaderTag'], 20, 3);
197 }
198
199 /**
200 * @param array<string, string|bool> $attributes
201 * @return array<string, string|bool>
202 */
203 public static function filterEsModuleScriptAttributes(array $attributes): array
204 {
205 $id = isset($attributes['id']) && is_string($attributes['id']) ? $attributes['id'] : '';
206 if ($id === '' || substr($id, -3) !== '-js') {
207 return $attributes;
208 }
209
210 $handle = substr($id, 0, -3);
211 if ($handle === '' || empty(self::$esModuleHandles[$handle])) {
212 return $attributes;
213 }
214
215 if (!empty($attributes['type'])) {
216 return $attributes;
217 }
218
219 $attributes['type'] = 'module';
220
221 return $attributes;
222 }
223
224 /**
225 * Fallback: guarantee type="module" on the printed tag (see class docblock).
226 *
227 * @param string $tag Full script tag HTML.
228 * @param string $handle Script handle.
229 * @param string $src Source URL (unused).
230 */
231 public static function filterScriptLoaderTag(string $tag, string $handle, string $src): string
232 {
233 unset($src);
234 if ($handle === '' || empty(self::$esModuleHandles[$handle])) {
235 return $tag;
236 }
237 if (preg_match('/\btype\s*=\s*["\']?module["\']?/i', $tag)) {
238 return $tag;
239 }
240
241 return (string) preg_replace('/<script\b/i', '<script type="module"', $tag, 1);
242 }
243 }
244