PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.29
Booktics – Appointment Booking Calendar for Service Businesses v1.0.29
1.0.29 1.0.28 1.0.27 1.0.26 1.0.25 1.0.24 1.0.23 1.0.22 1.0.21 1.0.20 1.0.19 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.2 1.0.3 All 31 releases
← All changes | core/assets/base-assets.php +186 -59 1.0.18 → 1.0.29 View file →
@@ -28,9 +28,8 @@
28 28 *
29 29 * @return void
30 30 */
31 31 public function register_styles_scripts() {
32 - // Register i18n loader early so other scripts can depend on it.
33 32 $this->register_global_scripts();
34 33 $this->register_scripts();
35 34 $this->register_styles();
36 35 }
@@ -37,10 +36,8 @@
37 36
38 37 /**
39 38 * Register scripts
40 39 *
41 - * @param array $scripts
42 - *
43 40 * @return void
44 41 */
45 42 public function register_scripts() {
46 43 $scripts = $this->get_scripts();
@@ -51,9 +48,8 @@
51 48 $version = isset( $script['version'] ) ? $script['version'] : $this->get_version( $script['src'] );
52 49
53 50 $deps = $this->get_dependencies( $script['src'], $deps );
54 51
55 - // Mirror wp-cafe: if script uses wp-i18n, ensure our i18n loader runs first.
56 52 if ( in_array( 'wp-i18n', $deps, true ) ) {
57 53 $deps[] = 'booktics-i18n-loader';
58 54 }
59 55
@@ -58,16 +54,12 @@
58 54 }
59 55
60 56 wp_register_script( $handle, $script['src'], $deps, $version, $in_footer );
61 57
62 - // Enable JS translations for bundles that use wp.i18n.
63 58 if ( function_exists( 'wp_set_script_translations' ) ) {
64 - // Don't force a path: translations are commonly in WP_LANG_DIR/plugins.
65 59 wp_set_script_translations( $handle, 'booktics' );
66 60 }
67 61
68 - // Ensure preloaded locale data is actually registered in wp.i18n for
69 - // pages that never trigger per-chunk JSON downloads.
70 62 if ( 'booktics-packages' === $handle ) {
71 63 wp_add_inline_script(
72 64 'booktics-packages',
73 65 '(function(){try{if(window.bookticsPreloadedLocaleData && window.wp && window.wp.i18n && window.wp.i18n.setLocaleData){window.wp.i18n.setLocaleData(window.bookticsPreloadedLocaleData,"booktics");}}catch(e){}})();',
@@ -108,12 +100,14 @@
108 100 * @return void
109 101 */
110 102 protected function enqueue_i18n_loader() {
111 103 $data = [
112 - 'baseUrl' => false,
113 - 'locale' => determine_locale(),
114 - 'domainMap' => [],
115 - 'domainPaths' => [],
104 + 'baseUrl' => false,
105 + 'locale' => determine_locale(),
106 + 'domainMap' => [],
107 + 'domainPaths' => [],
108 + 'translationMap' => (object) [],
109 + 'preloaded' => false,
116 110 ];
117 111
118 112 $lang_dir = WP_LANG_DIR;
119 113 $content_dir = WP_CONTENT_DIR;
@@ -128,48 +122,27 @@
128 122 substr( trailingslashit( $lang_dir ), strlen( untrailingslashit( $abspath ) ) )
129 123 );
130 124 }
131 125
126 + $locale_data = $this->get_preloaded_locale_data( $data['locale'] );
127 + $data['preloaded'] = ! empty( $locale_data );
128 +
129 + // The consolidated locale data already covers every string in the bundles,
130 + // so the per-chunk hash map is only needed when it could not be built.
131 + if ( ! $data['preloaded'] ) {
132 + $data['translationMap'] = (object) $this->get_translation_map();
133 + }
134 +
132 135 wp_enqueue_script( 'booktics-i18n-loader' );
133 136
134 137 $data['domainMap'] = (object) $data['domainMap'];
135 138 $data['domainPaths'] = (object) $data['domainPaths'];
136 139
137 - // If a consolidated .l10n.php file exists, preload all messages to avoid
138 - // relying on per-chunk JSON requests (which may be missing).
139 - $preload = null;
140 - $l10n_file = trailingslashit( WP_LANG_DIR ) . 'plugins/booktics-' . $data['locale'] . '.l10n.php';
141 - $l10n_file_alt = trailingslashit( WP_LANG_DIR ) . 'plugins/booktics-' . str_replace( '-', '_', $data['locale'] ) . '.l10n.php';
142 - $l10n_file_legacy = trailingslashit( WP_LANG_DIR ) . 'plugins/booktics-' . str_replace( '_', '-', $data['locale'] ) . '.l10n.php';
143 - $l10n_candidates = array_unique( array_filter( [ $l10n_file, $l10n_file_alt, $l10n_file_legacy ] ) );
144 -
145 - foreach ( $l10n_candidates as $candidate ) {
146 - if ( file_exists( $candidate ) ) {
147 - $preload = include $candidate;
148 - break;
149 - }
150 - }
151 -
152 140 $inline = '(function(){var k="bookticsI18nLoader"; if (typeof window[k] === "undefined") { window[k] = {}; } window[k].state = ' . wp_json_encode( $data, JSON_UNESCAPED_SLASHES ) . ';';
153 141
154 - if ( is_array( $preload ) && ! empty( $preload['messages'] ) && is_array( $preload['messages'] ) ) {
155 - $messages = [];
156 - foreach ( $preload['messages'] as $msgid => $msgstr ) {
157 - if ( is_string( $msgid ) && is_string( $msgstr ) ) {
158 - $messages[ $msgid ] = [ $msgstr ];
159 - }
160 - }
161 -
162 - $locale_data = [
163 - '' => [
164 - 'domain' => 'booktics',
165 - 'lang' => $data['locale'],
166 - 'plural-forms' => isset( $preload['plural-forms'] ) ? $preload['plural-forms'] : 'nplurals=2; plural=n != 1;',
167 - ],
168 - ] + $messages;
169 -
170 - $encoded_locale_data = wp_json_encode( $locale_data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
171 - $inline .= ' window[k].preloadedLocaleData = ' . $encoded_locale_data . '; window.bookticsPreloadedLocaleData = ' . $encoded_locale_data . ';';
142 + if ( ! empty( $locale_data ) ) {
143 + $inline .= ' window[k].preloadedLocaleData = window.bookticsPreloadedLocaleData = '
144 + . wp_json_encode( $locale_data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) . ';';
172 145 }
173 146
174 147 $inline .= '})();';
175 148
@@ -182,10 +155,8 @@
182 155
183 156 /**
184 157 * Register styles
185 158 *
186 - * @param array $styles
187 - *
188 159 * @return void
189 160 */
190 161 public function register_styles() {
191 162 $styles = $this->get_styles();
@@ -234,9 +205,9 @@
234 205 return $assets;
235 206 }
236 207
237 208 /**
238 - * Get script file version
209 + * Get script file version from .asset.php (generated by DependencyExtractionWebpackPlugin).
239 210 *
240 211 * @param string $file_name
241 212 *
242 213 * @return string
@@ -251,37 +222,193 @@
251 222 * Get file path from url
252 223 *
253 224 * @param string $url
254 225 *
255 - * @return string
226 + * @return string|false
256 227 */
257 228 private function get_file_path( $url ) {
258 - // Check if the URL is valid
259 229 if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
260 230 return false;
261 231 }
262 232
263 - // Parse the URL
264 233 $url_parts = wp_parse_url( $url );
265 234
266 - // Check if the URL has a path component
267 235 if ( ! isset( $url_parts['path'] ) ) {
268 - return false; // URL does not contain a path
236 + return false;
269 237 }
270 238
271 - $clean_path = str_replace( '.js', '.asset.php', $url_parts['path'] );
239 + $path = $url_parts['path'];
240 + $path = preg_replace( '/\.js$/', '.asset.php', $path );
272 241
273 - // Get the file path from the URL path.
274 242 if ( isset( $_SERVER['DOCUMENT_ROOT'] ) ) {
275 - $file_path = sanitize_text_field( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) ) . $clean_path;
243 + $file_path = sanitize_text_field( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) ) . $path;
276 244 } else {
277 - $file_path = ''; // Or another appropriate fallback.
245 + $file_path = '';
278 246 }
279 247
280 - // Check if the file exists
281 248 if ( ! file_exists( $file_path ) ) {
282 - return false; // File does not exist
249 + return false;
283 250 }
284 251
285 252 return $file_path;
253 + }
254 +
255 + /**
256 + * Build the Jed locale data for every translated string in the text domain.
257 + *
258 + * Script translations are normally split into one JSON file per bundled
259 + * script, keyed by an md5 of that script's path. Async chunk filenames carry
260 + * a content hash and change on every build, so those JSON files go stale as
261 + * soon as the plugin is updated. Registering the whole domain up front keeps
262 + * the React apps translated regardless of how the bundles were chunked.
263 + *
264 + * @param string $locale Locale being rendered.
265 + *
266 + * @return array Jed formatted locale data, empty when there is nothing to preload.
267 + */
268 + private function get_preloaded_locale_data( $locale ) {
269 + static $cache = [];
270 +
271 + if ( isset( $cache[ $locale ] ) ) {
272 + return $cache[ $locale ];
273 + }
274 +
275 + $cache[ $locale ] = [];
276 +
277 + if ( 'en_US' === $locale ) {
278 + return $cache[ $locale ];
279 + }
280 +
281 + if ( ! is_textdomain_loaded( 'booktics' ) ) {
282 + // Registers the plugin's own /languages path. Just-in-time loading only
283 + // finds WP.org language packs, so without this a bundled .mo is ignored.
284 + // phpcs:ignore PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound
285 + load_plugin_textdomain( 'booktics', false, dirname( plugin_basename( BOOKTICS_FILE ) ) . '/languages' );
286 + }
287 +
288 + $translations = get_translations_for_domain( 'booktics' );
289 +
290 + // WP_Translations (WP 6.5+, .l10n.php files) does not extend Translations,
291 + // so check for the entries it exposes rather than the class.
292 + if ( ! is_object( $translations ) || $translations instanceof \NOOP_Translations ) {
293 + return $cache[ $locale ];
294 + }
295 +
296 + $entries = (array) $translations->entries;
297 + $messages = [];
298 +
299 + foreach ( $entries as $entry ) {
300 + if ( ! $entry instanceof \Translation_Entry || ! is_string( $entry->singular ) || '' === $entry->singular ) {
301 + continue;
302 + }
303 +
304 + $entry_translations = array_values( array_filter(
305 + (array) $entry->translations,
306 + function ( $translation ) {
307 + return is_string( $translation ) && '' !== $translation;
308 + }
309 + ) );
310 +
311 + if ( empty( $entry_translations ) ) {
312 + continue;
313 + }
314 +
315 + // wp.i18n keys contextual strings as "contextsingular".
316 + $key = ( is_string( $entry->context ) && '' !== $entry->context )
317 + ? $entry->context . "\4" . $entry->singular
318 + : $entry->singular;
319 +
320 + $messages[ $key ] = $entry_translations;
321 + }
322 +
323 + if ( empty( $messages ) ) {
324 + return $cache[ $locale ];
325 + }
326 +
327 + $headers = (array) $translations->headers;
328 +
329 + $locale_data = [
330 + '' => [
331 + 'domain' => 'booktics',
332 + 'lang' => $locale,
333 + 'plural-forms' => ! empty( $headers['Plural-Forms'] ) ? $headers['Plural-Forms'] : 'nplurals=2; plural=n != 1;',
334 + ],
335 + ] + $messages;
336 +
337 + /**
338 + * Filter the locale data preloaded for the React apps.
339 + *
340 + * Useful to trim the payload down to the strings a site actually renders.
341 + *
342 + * @param array $locale_data Jed formatted locale data.
343 + * @param string $locale Locale being rendered.
344 + */
345 + $cache[ $locale ] = apply_filters( 'booktics_preloaded_locale_data', $locale_data, $locale );
346 +
347 + return $cache[ $locale ];
348 + }
349 +
350 + /**
351 + * Get map of translation file hashes to their URLs.
352 + *
353 + * @return array Hash => URL map.
354 + */
355 + private function get_translation_map() {
356 + $map = [];
357 + $locale = determine_locale();
358 + $domain = 'booktics';
359 +
360 + $locale_variants = array_unique( array_filter( [
361 + $locale,
362 + str_replace( '-', '_', $locale ),
363 + str_replace( '_', '-', $locale ),
364 + ] ) );
365 +
366 + foreach ( $this->get_translation_directories() as $dir ) {
367 + if ( ! is_dir( $dir['path'] ) || ! is_readable( $dir['path'] ) ) {
368 + continue;
369 + }
370 +
371 + foreach ( $locale_variants as $variant ) {
372 + $files = glob( $dir['path'] . $domain . '-' . $variant . '-*.json' );
373 + if ( ! $files ) {
374 + continue;
375 + }
376 +
377 + foreach ( $files as $file ) {
378 + $filename = basename( $file );
379 + $variant_prefix = $domain . '-' . $variant . '-';
380 + if ( strpos( $filename, $variant_prefix ) === 0 ) {
381 + $hash = substr( $filename, strlen( $variant_prefix ), -5 );
382 + if ( $hash ) {
383 + $map[ $hash ] = trailingslashit( $dir['url'] ) . $filename;
384 + }
385 + }
386 + }
387 + }
388 + }
389 +
390 + return $map;
391 + }
392 +
393 + /**
394 + * Return translation directory paths and their corresponding URLs.
395 + *
396 + * @return array<int, array{path: string, url: string}>
397 + */
398 + private function get_translation_directories() {
399 + return [
400 + [
401 + 'path' => WP_CONTENT_DIR . '/languages/plugins/',
402 + 'url' => content_url( 'languages/plugins/' ),
403 + ],
404 + [
405 + 'path' => WP_CONTENT_DIR . '/languages/loco/plugins/',
406 + 'url' => content_url( 'languages/loco/plugins/' ),
407 + ],
408 + [
409 + 'path' => booktics()->plugin_directory . '/languages/',
410 + 'url' => booktics()->plugin_url . '/languages/',
411 + ],
412 + ];
286 413 }
287 414 }