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

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