PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.28
Booktics – Appointment Booking Calendar for Service Businesses v1.0.28
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
booktics / core / assets / base-assets.php

base-assets.php in Booktics – Appointment Booking Calendar for Service Businesses 1.0.28, at core/assets/base-assets.php

415 lines 13.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Booktics\Assets;
3
4 if ( ! defined( 'ABSPATH' ) ) exit;
5
6 use Booktics\Contracts\Hookable_Service_Contract;
7
8 /**
9 * Main assets management class
10 */
11 abstract class Base_Assets implements Hookable_Service_Contract {
12 /**
13 * Get all scripts
14 *
15 * @return array list of script to register
16 */
17 abstract public function get_scripts(): array;
18
19 /**
20 * Get all styles
21 *
22 * @return array list of styles to regiser
23 */
24 abstract public function get_styles(): array;
25
26 /**
27 * Register scripts and styles
28 *
29 * @return void
30 */
31 public function register_styles_scripts() {
32 $this->register_global_scripts();
33 $this->register_scripts();
34 $this->register_styles();
35 }
36
37 /**
38 * Register scripts
39 *
40 * @return void
41 */
42 public function register_scripts() {
43 $scripts = $this->get_scripts();
44
45 foreach ( $scripts as $handle => $script ) {
46 $deps = isset( $script['deps'] ) ? $script['deps'] : [];
47 $in_footer = isset( $script['in_footer'] ) ? $script['in_footer'] : true;
48 $version = isset( $script['version'] ) ? $script['version'] : $this->get_version( $script['src'] );
49
50 $deps = $this->get_dependencies( $script['src'], $deps );
51
52 if ( in_array( 'wp-i18n', $deps, true ) ) {
53 $deps[] = 'booktics-i18n-loader';
54 }
55
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 }
69 }
70 }
71
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 /**
157 * Register styles
158 *
159 * @return void
160 */
161 public function register_styles() {
162 $styles = $this->get_styles();
163
164 foreach ( $styles as $handle => $style ) {
165 $deps = isset( $style['deps'] ) ? $style['deps'] : false;
166 $version = booktics()->version;
167
168 wp_register_style( $handle, $style['src'], $deps, $version );
169 }
170 }
171
172 /**
173 * Get script and style file dependencies
174 *
175 * @param string $file_name
176 * @param array $deps
177 *
178 * @return array
179 */
180 private function get_dependencies( $file_name, $deps = [] ) {
181 $assets = $this->get_file_assets( $file_name );
182
183 $assets_deps = ! empty( $assets['dependencies'] ) ? $assets['dependencies'] : [];
184
185 $merged_deps = array_merge( $assets_deps, $deps );
186
187 return $merged_deps;
188 }
189
190 /**
191 * Get file assets
192 *
193 * @param string $file_name
194 *
195 * @return array
196 */
197 private function get_file_assets( $file_url ) {
198 $file = $this->get_file_path( $file_url );
199 $assets = [];
200
201 if ( file_exists( $file ) ) {
202 $assets = include $file;
203 }
204
205 return $assets;
206 }
207
208 /**
209 * Get script file version from .asset.php (generated by DependencyExtractionWebpackPlugin).
210 *
211 * @param string $file_name
212 *
213 * @return string
214 */
215 private function get_version( $file_name ) {
216 $assets = $this->get_file_assets( $file_name );
217 $assets_vers = ! empty( $assets['version'] ) ? $assets['version'] : booktics()->version;
218 return $assets_vers;
219 }
220
221 /**
222 * Get file path from url
223 *
224 * @param string $url
225 *
226 * @return string|false
227 */
228 private function get_file_path( $url ) {
229 if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
230 return false;
231 }
232
233 $url_parts = wp_parse_url( $url );
234
235 if ( ! isset( $url_parts['path'] ) ) {
236 return false;
237 }
238
239 $path = $url_parts['path'];
240 $path = preg_replace( '/\.js$/', '.asset.php', $path );
241
242 if ( isset( $_SERVER['DOCUMENT_ROOT'] ) ) {
243 $file_path = sanitize_text_field( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) ) . $path;
244 } else {
245 $file_path = '';
246 }
247
248 if ( ! file_exists( $file_path ) ) {
249 return false;
250 }
251
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 ];
413 }
414 }
415