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 +269 -16 1.0.16 → 1.0.29 View file →
@@ -28,8 +28,9 @@
28 28 *
29 29 * @return void
30 30 */
31 31 public function register_styles_scripts() {
32 + $this->register_global_scripts();
32 33 $this->register_scripts();
33 34 $this->register_styles();
34 35 }
35 36
@@ -35,10 +36,8 @@
35 36
36 37 /**
37 38 * Register scripts
38 39 *
39 - * @param array $scripts
40 - *
41 40 * @return void
42 41 */
43 42 public function register_scripts() {
44 43 $scripts = $this->get_scripts();
@@ -49,17 +48,115 @@
49 48 $version = isset( $script['version'] ) ? $script['version'] : $this->get_version( $script['src'] );
50 49
51 50 $deps = $this->get_dependencies( $script['src'], $deps );
52 51
52 + if ( in_array( 'wp-i18n', $deps, true ) ) {
53 + $deps[] = 'booktics-i18n-loader';
54 + }
55 +
53 56 wp_register_script( $handle, $script['src'], $deps, $version, $in_footer );
57 +
58 + if ( function_exists( 'wp_set_script_translations' ) ) {
59 + wp_set_script_translations( $handle, 'booktics' );
60 + }
61 +
62 + if ( 'booktics-packages' === $handle ) {
63 + wp_add_inline_script(
64 + 'booktics-packages',
65 + '(function(){try{if(window.bookticsPreloadedLocaleData && window.wp && window.wp.i18n && window.wp.i18n.setLocaleData){window.wp.i18n.setLocaleData(window.bookticsPreloadedLocaleData,"booktics");}}catch(e){}})();',
66 + 'before'
67 + );
68 + }
54 69 }
55 70 }
56 71
57 72 /**
73 + * Register global scripts.
74 + *
75 + * @return void
76 + */
77 + private function register_global_scripts() {
78 + $scripts = [
79 + 'booktics-i18n-loader' => [
80 + 'src' => booktics()->assets_url . '/build/js/i18n-loader.js',
81 + 'deps' => [],
82 + 'in_footer' => true,
83 + ],
84 + ];
85 +
86 + foreach ( $scripts as $handle => $script ) {
87 + $deps = isset( $script['deps'] ) ? $script['deps'] : [];
88 + $in_footer = isset( $script['in_footer'] ) ? $script['in_footer'] : true;
89 + $version = isset( $script['version'] ) ? $script['version'] : $this->get_version( $script['src'] );
90 +
91 + $deps = $this->get_dependencies( $script['src'], $deps );
92 +
93 + wp_register_script( $handle, $script['src'], $deps, $version, $in_footer );
94 + }
95 + }
96 +
97 + /**
98 + * Enqueue i18n loader and set its state.
99 + *
100 + * @return void
101 + */
102 + protected function enqueue_i18n_loader() {
103 + $data = [
104 + 'baseUrl' => false,
105 + 'locale' => determine_locale(),
106 + 'domainMap' => [],
107 + 'domainPaths' => [],
108 + 'translationMap' => (object) [],
109 + 'preloaded' => false,
110 + ];
111 +
112 + $lang_dir = WP_LANG_DIR;
113 + $content_dir = WP_CONTENT_DIR;
114 + $abspath = ABSPATH;
115 +
116 + if ( strpos( $lang_dir, $content_dir ) === 0 ) {
117 + $data['baseUrl'] = content_url(
118 + substr( trailingslashit( $lang_dir ), strlen( trailingslashit( $content_dir ) ) )
119 + );
120 + } elseif ( strpos( $lang_dir, $abspath ) === 0 ) {
121 + $data['baseUrl'] = site_url(
122 + substr( trailingslashit( $lang_dir ), strlen( untrailingslashit( $abspath ) ) )
123 + );
124 + }
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 +
135 + wp_enqueue_script( 'booktics-i18n-loader' );
136 +
137 + $data['domainMap'] = (object) $data['domainMap'];
138 + $data['domainPaths'] = (object) $data['domainPaths'];
139 +
140 + $inline = '(function(){var k="bookticsI18nLoader"; if (typeof window[k] === "undefined") { window[k] = {}; } window[k].state = ' . wp_json_encode( $data, JSON_UNESCAPED_SLASHES ) . ';';
141 +
142 + if ( ! empty( $locale_data ) ) {
143 + $inline .= ' window[k].preloadedLocaleData = window.bookticsPreloadedLocaleData = '
144 + . wp_json_encode( $locale_data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) . ';';
145 + }
146 +
147 + $inline .= '})();';
148 +
149 + wp_add_inline_script(
150 + 'booktics-i18n-loader',
151 + $inline,
152 + 'before'
153 + );
154 + }
155 +
156 + /**
58 157 * Register styles
59 158 *
60 - * @param array $styles
61 - *
62 159 * @return void
63 160 */
64 161 public function register_styles() {
65 162 $styles = $this->get_styles();
@@ -108,9 +205,9 @@
108 205 return $assets;
109 206 }
110 207
111 208 /**
112 - * Get script file version
209 + * Get script file version from .asset.php (generated by DependencyExtractionWebpackPlugin).
113 210 *
114 211 * @param string $file_name
115 212 *
116 213 * @return string
@@ -125,37 +222,193 @@
125 222 * Get file path from url
126 223 *
127 224 * @param string $url
128 225 *
129 - * @return string
226 + * @return string|false
130 227 */
131 228 private function get_file_path( $url ) {
132 - // Check if the URL is valid
133 229 if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
134 230 return false;
135 231 }
136 232
137 - // Parse the URL
138 233 $url_parts = wp_parse_url( $url );
139 234
140 - // Check if the URL has a path component
141 235 if ( ! isset( $url_parts['path'] ) ) {
142 - return false; // URL does not contain a path
236 + return false;
143 237 }
144 238
145 - $clean_path = str_replace( '.js', '.asset.php', $url_parts['path'] );
239 + $path = $url_parts['path'];
240 + $path = preg_replace( '/\.js$/', '.asset.php', $path );
146 241
147 - // Get the file path from the URL path.
148 242 if ( isset( $_SERVER['DOCUMENT_ROOT'] ) ) {
149 - $file_path = sanitize_text_field( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) ) . $clean_path;
243 + $file_path = sanitize_text_field( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) ) . $path;
150 244 } else {
151 - $file_path = ''; // Or another appropriate fallback.
245 + $file_path = '';
152 246 }
153 247
154 - // Check if the file exists
155 248 if ( ! file_exists( $file_path ) ) {
156 - return false; // File does not exist
249 + return false;
157 250 }
158 251
159 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 + ];
160 413 }
161 414 }