PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.27
Booktics – Appointment Booking Calendar for Service Businesses v1.0.27
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 +414 -159 1.0.1 → 1.0.27 View file →
@@ -1,159 +1,414 @@
1 -<?php
2 -namespace Booktics\Assets;
3 -
4 -use Booktics\Contracts\Hookable_Service_Contract;
5 -
6 -/**
7 - * Main assets management class
8 - */
9 -abstract class Base_Assets implements Hookable_Service_Contract {
10 - /**
11 - * Get all scripts
12 - *
13 - * @return array list of script to register
14 - */
15 - abstract public function get_scripts(): array;
16 -
17 - /**
18 - * Get all styles
19 - *
20 - * @return array list of styles to regiser
21 - */
22 - abstract public function get_styles(): array;
23 -
24 - /**
25 - * Register scripts and styles
26 - *
27 - * @return void
28 - */
29 - public function register_styles_scripts() {
30 - $this->register_scripts();
31 - $this->register_styles();
32 - }
33 -
34 - /**
35 - * Register scripts
36 - *
37 - * @param array $scripts
38 - *
39 - * @return void
40 - */
41 - public function register_scripts() {
42 - $scripts = $this->get_scripts();
43 -
44 - foreach ( $scripts as $handle => $script ) {
45 - $deps = isset( $script['deps'] ) ? $script['deps'] : [];
46 - $in_footer = isset( $script['in_footer'] ) ? $script['in_footer'] : true;
47 - $version = isset( $script['version'] ) ? $script['version'] : $this->get_version( $script['src'] );
48 -
49 - $deps = $this->get_dependencies( $script['src'], $deps );
50 -
51 - wp_register_script( $handle, $script['src'], $deps, $version, $in_footer );
52 - }
53 - }
54 -
55 - /**
56 - * Register styles
57 - *
58 - * @param array $styles
59 - *
60 - * @return void
61 - */
62 - public function register_styles() {
63 - $styles = $this->get_styles();
64 -
65 - foreach ( $styles as $handle => $style ) {
66 - $deps = isset( $style['deps'] ) ? $style['deps'] : false;
67 - $version = booktics()->version;
68 -
69 - wp_register_style( $handle, $style['src'], $deps, $version );
70 - }
71 - }
72 -
73 - /**
74 - * Get script and style file dependencies
75 - *
76 - * @param string $file_name
77 - * @param array $deps
78 - *
79 - * @return array
80 - */
81 - private function get_dependencies( $file_name, $deps = [] ) {
82 - $assets = $this->get_file_assets( $file_name );
83 -
84 - $assets_deps = ! empty( $assets['dependencies'] ) ? $assets['dependencies'] : [];
85 -
86 - $merged_deps = array_merge( $assets_deps, $deps );
87 -
88 - return $merged_deps;
89 - }
90 -
91 - /**
92 - * Get file assets
93 - *
94 - * @param string $file_name
95 - *
96 - * @return array
97 - */
98 - private function get_file_assets( $file_url ) {
99 - $file = $this->get_file_path( $file_url );
100 - $assets = [];
101 -
102 - if ( file_exists( $file ) ) {
103 - $assets = include $file;
104 - }
105 -
106 - return $assets;
107 - }
108 -
109 - /**
110 - * Get script file version
111 - *
112 - * @param string $file_name
113 - *
114 - * @return string
115 - */
116 - private function get_version( $file_name ) {
117 - $assets = $this->get_file_assets( $file_name );
118 - $assets_vers = ! empty( $assets['version'] ) ? $assets['version'] : booktics()->version;
119 - return $assets_vers;
120 - }
121 -
122 - /**
123 - * Get file path from url
124 - *
125 - * @param string $url
126 - *
127 - * @return string
128 - */
129 - private function get_file_path( $url ) {
130 - // Check if the URL is valid
131 - if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
132 - return false;
133 - }
134 -
135 - // Parse the URL
136 - $url_parts = wp_parse_url( $url );
137 -
138 - // Check if the URL has a path component
139 - if ( ! isset( $url_parts['path'] ) ) {
140 - return false; // URL does not contain a path
141 - }
142 -
143 - $clean_path = str_replace( '.js', '.asset.php', $url_parts['path'] );
144 -
145 - // Get the file path from the URL path.
146 - if ( isset( $_SERVER['DOCUMENT_ROOT'] ) ) {
147 - $file_path = sanitize_text_field( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) ) . $clean_path;
148 - } else {
149 - $file_path = ''; // Or another appropriate fallback.
150 - }
151 -
152 - // Check if the file exists
153 - if ( ! file_exists( $file_path ) ) {
154 - return false; // File does not exist
155 - }
156 -
157 - return $file_path;
158 - }
159 -}
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 +}