PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.9
Yatra – Travel Booking & Tour Operator Software v3.0.2.9
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.2.9, at app/Blocks/BlockEditorScript.php

241 lines 7.7 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
133
134 return false;
135 }
136
137 wp_register_script(
138 $handle,
139 $bundle['url'],
140 $deps,
141 YATRA_VERSION,
142 true
143 );
144
145 if ($bundle['module']) {
146 wp_script_add_data($handle, 'type', 'module');
147 self::$esModuleHandles[$handle] = true;
148 self::ensureEsModuleAttributesFilter();
149 }
150
151 if (function_exists('wp_set_script_translations')) {
152 wp_set_script_translations($handle, 'yatra', YATRA_PLUGIN_PATH . 'i18n/languages');
153 }
154
155 if (! in_array($handle, self::$registeredEditorHandles, true)) {
156 self::$registeredEditorHandles[] = $handle;
157 }
158 self::ensureEnqueueBlockEditorAssetsHook();
159
160 return true;
161 }
162
163 /**
164 * Ensure Yatra block editor scripts are enqueued in Gutenberg (defensive; core usually does this).
165 */
166 private static function ensureEnqueueBlockEditorAssetsHook(): void
167 {
168 if (self::$enqueueHookAdded) {
169 return;
170 }
171 self::$enqueueHookAdded = true;
172 add_action('enqueue_block_editor_assets', [self::class, 'enqueueRegisteredEditorScripts'], 20);
173 }
174
175 public static function enqueueRegisteredEditorScripts(): void
176 {
177 foreach (self::$registeredEditorHandles as $handle) {
178 if (wp_script_is($handle, 'registered')) {
179 wp_enqueue_script($handle);
180 }
181 }
182 }
183
184 private static function ensureEsModuleAttributesFilter(): void
185 {
186 if (self::$scriptAttributesFilterAdded) {
187 return;
188 }
189 self::$scriptAttributesFilterAdded = true;
190 add_filter('wp_script_attributes', [self::class, 'filterEsModuleScriptAttributes'], 20, 1);
191 // Core builds $attr before merging wp_script_add_data(…, 'type', 'module'); the attributes filter
192 // should add type, but some hosts/plugins strip it — ensure the final <script> tag is a module.
193 add_filter('script_loader_tag', [self::class, 'filterScriptLoaderTag'], 20, 3);
194 }
195
196 /**
197 * @param array<string, string|bool> $attributes
198 * @return array<string, string|bool>
199 */
200 public static function filterEsModuleScriptAttributes(array $attributes): array
201 {
202 $id = isset($attributes['id']) && is_string($attributes['id']) ? $attributes['id'] : '';
203 if ($id === '' || substr($id, -3) !== '-js') {
204 return $attributes;
205 }
206
207 $handle = substr($id, 0, -3);
208 if ($handle === '' || empty(self::$esModuleHandles[$handle])) {
209 return $attributes;
210 }
211
212 if (!empty($attributes['type'])) {
213 return $attributes;
214 }
215
216 $attributes['type'] = 'module';
217
218 return $attributes;
219 }
220
221 /**
222 * Fallback: guarantee type="module" on the printed tag (see class docblock).
223 *
224 * @param string $tag Full script tag HTML.
225 * @param string $handle Script handle.
226 * @param string $src Source URL (unused).
227 */
228 public static function filterScriptLoaderTag(string $tag, string $handle, string $src): string
229 {
230 unset($src);
231 if ($handle === '' || empty(self::$esModuleHandles[$handle])) {
232 return $tag;
233 }
234 if (preg_match('/\btype\s*=\s*["\']?module["\']?/i', $tag)) {
235 return $tag;
236 }
237
238 return (string) preg_replace('/<script\b/i', '<script type="module"', $tag, 1);
239 }
240 }
241