PluginProbe
Timetics – Appointment Booking Calendar & Scheduling / 1.0.64
Timetics – Appointment Booking Calendar & Scheduling v1.0.64
1.0.64 1.0.62 1.0.63 1.0.61 1.0.60 1.0.59 1.0.58 1.0.57 1.0.56 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.19 1.0.2 1.0.20 1.0.21 All 65 releases
← All changes | bootstrap.php +140 -4 1.0.56 → 1.0.64 View file →
@@ -228,13 +228,15 @@
228 228
229 229 // Register scripts and styles first
230 230 if ( $this->is_request( 'admin' ) ) {
231 231 add_action( 'admin_enqueue_scripts', [ $this, 'admin_scripts' ] );
232 + add_action( 'admin_enqueue_scripts', [ $this, 'preload_js_translations' ], 5 );
232 233 add_action( 'admin_footer', [ $this, 'admin_helpscout_beacon' ] );
233 234 }
234 235
235 236 if ( $this->is_request( 'frontend' ) ) {
236 237 add_action( 'wp_enqueue_scripts', array( $this, 'frontend_scripts' ) );
238 + add_action( 'wp_enqueue_scripts', [ $this, 'preload_js_translations' ], 5 );
237 239 }
238 240
239 241 // Register admin menu
240 242 Core\Admin\Menu::instance()->init();
@@ -261,8 +263,109 @@
261 263 return "https://arraytics.com/timetics/";
262 264 }
263 265
264 266 /**
267 + * Preload PHP-loaded translations into wp.i18n before any bundle runs.
268 + *
269 + * Why: webpack bundles collapse many source files into one. wp_set_script_translations()
270 + * looks up a JSON file hashed from the registered bundle URL, but Loco and wp i18n make-json
271 + * shard JSON by source-file path. The hashes never match, so per-script JSON loading silently
272 + * fails. Reading the textdomain that load_text_domain() already populated and injecting it via
273 + * setLocaleData() bypasses the hash mismatch entirely — every __() call across every bundle
274 + * resolves from in-memory data set before any React component renders.
275 + */
276 + public function preload_js_translations() {
277 + $locale = determine_locale();
278 + $plural_forms = 'nplurals=2; plural=(n != 1);';
279 +
280 + // Read .mo first — canonical, always-fresh source that Loco rewrites on every save.
281 + // .l10n.php cache often lags behind .mo because Loco does not regenerate it.
282 + $messages = $this->extract_mo_messages( $locale, $plural_forms );
283 +
284 + // Merge .l10n.php on top (covers WP 6.5+ system-translated plugins where only .l10n.php exists).
285 + foreach ( [
286 + WP_LANG_DIR . '/loco/plugins/timetics-' . $locale . '.l10n.php',
287 + TIMETICS_PLUGIN_DIR . 'languages/timetics-' . $locale . '.l10n.php',
288 + WP_LANG_DIR . '/plugins/timetics-' . $locale . '.l10n.php',
289 + ] as $file ) {
290 + if ( ! is_readable( $file ) ) {
291 + continue;
292 + }
293 + $data = include $file;
294 + if ( ! is_array( $data ) || empty( $data['messages'] ) ) {
295 + continue;
296 + }
297 + $messages = array_merge( $messages, $data['messages'] );
298 + if ( ! empty( $data['plural-forms'] ) ) {
299 + $plural_forms = $data['plural-forms'];
300 + }
301 + }
302 +
303 + if ( empty( $messages ) ) {
304 + return;
305 + }
306 +
307 + $locale_data = [
308 + '' => [
309 + 'domain' => 'timetics',
310 + 'lang' => $locale,
311 + 'plural-forms' => $plural_forms,
312 + ],
313 + ];
314 +
315 + foreach ( $messages as $msgid => $msgstr ) {
316 + if ( is_string( $msgid ) && $msgid !== '' && is_string( $msgstr ) && $msgstr !== '' ) {
317 + $locale_data[ $msgid ] = [ $msgstr ];
318 + }
319 + }
320 +
321 + wp_add_inline_script(
322 + 'wp-i18n',
323 + sprintf(
324 + 'wp.i18n.setLocaleData( %s, "timetics" );',
325 + wp_json_encode( $locale_data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES )
326 + ),
327 + 'after'
328 + );
329 + }
330 +
331 + /**
332 + * Fallback: parse .mo file to extract all translations.
333 + */
334 + private function extract_mo_messages( $locale, &$plural_forms ) {
335 + $candidates = [
336 + WP_LANG_DIR . '/loco/plugins/timetics-' . $locale . '.mo',
337 + TIMETICS_PLUGIN_DIR . 'languages/timetics-' . $locale . '.mo',
338 + WP_LANG_DIR . '/plugins/timetics-' . $locale . '.mo',
339 + ];
340 +
341 + $messages = [];
342 +
343 + foreach ( $candidates as $mofile ) {
344 + if ( ! is_readable( $mofile ) ) {
345 + continue;
346 + }
347 + if ( ! class_exists( 'MO' ) ) {
348 + require_once ABSPATH . WPINC . '/pomo/mo.php';
349 + }
350 + $mo = new \MO();
351 + if ( ! $mo->import_from_file( $mofile ) ) {
352 + continue;
353 + }
354 + if ( ! empty( $mo->headers['Plural-Forms'] ) ) {
355 + $plural_forms = $mo->headers['Plural-Forms'];
356 + }
357 + foreach ( $mo->entries as $msgid => $entry ) {
358 + if ( ! empty( $entry->translations[0] ) ) {
359 + $messages[ $msgid ] = $entry->translations[0];
360 + }
361 + }
362 + }
363 +
364 + return $messages;
365 + }
366 +
367 + /**
265 368 * Register scripts and styles for admin
266 369 *
267 370 * @return void
268 371 */
@@ -302,8 +405,9 @@
302 405 }
303 406
304 407 wp_enqueue_script( 'timetics-dashboard-scripts', TIMETICS_ASSETS_URL . 'js/dashboard.js', [ 'wp-plugins', 'wp-i18n', 'wp-element', 'wp-dom', 'wp-data', 'wp-color-picker' ], TIMETICS_VERSION, true );
305 408
409 + wp_set_script_translations( 'timetics-dashboard-scripts', 'timetics', TIMETICS_PLUGIN_DIR . 'languages/' );
306 410
307 411 $localize_obj = array(
308 412 'site_url' => site_url(),
309 413 'admin_url' => admin_url(),
@@ -321,8 +425,11 @@
321 425 'current_user_id' => get_current_user_id(),
322 426 'timeticsPro' => class_exists('TimeticsPro') ? true : false,
323 427 'demo_url' => $this->demo_url(),
324 428 'current_user' => timetics_get_current_user(),
429 + 'aisentic' => $this->get_aisentic_state(),
430 + 'theme_mode' => Core\Admin\Theme_Mode::get_for_localize(),
431 + 'dark_mode_enabled' => Core\Admin\Theme_Mode::is_enabled(),
325 432 );
326 433
327 434 $localize_obj = apply_filters( 'timetics_admin_localize_data', $localize_obj );
328 435
@@ -329,8 +436,37 @@
329 436 wp_localize_script( 'timetics-dashboard-scripts', 'timetics', $localize_obj );
330 437 }
331 438
332 439 /**
440 + * Aisentic connect state for the onboarding consent box and dashboard banner.
441 + *
442 + * Both surfaces show the email before the user opts in, so the value comes
443 + * from the same resolver the connect request uses.
444 + *
445 + * @return array Aisentic identity plus active/registered flags.
446 + */
447 + private function get_aisentic_state() {
448 + $identity = timetics_aisentic_identity();
449 +
450 + return [
451 + 'name' => $identity['name'],
452 + 'email' => $identity['email'],
453 + 'active' => class_exists( 'Aisentic\Init' ),
454 + 'registered' => timetics_aisentic_is_registered(),
455 + 'icon' => \Timetics\Core\Addon\Extension_Icon::get( 'aisentic' ),
456 + /*
457 + * Aisentic builds before the registration handshake have no
458 + * listener for our hook. Connecting would fail silently there, so
459 + * the UI hides the offer instead of promising something dead.
460 + */
461 + 'supports_registration' => class_exists( 'Aisentic\Api\Services\Registration_Service' ),
462 + 'terms_url' => 'https://arraytics.com/terms-of-service/',
463 + // Only people who could install Aisentic by hand get the setup dialog.
464 + 'can_setup' => current_user_can( 'manage_options' ) && current_user_can( 'install_plugins' ),
465 + ];
466 + }
467 +
468 + /**
333 469 * Register scripts and styles for frontend
334 470 *
335 471 * @return void
336 472 */
@@ -361,10 +497,10 @@
361 497
362 498 wp_register_script( 'timetics-frontend-scripts', TIMETICS_ASSETS_URL . 'js/frontend.js', [ 'jquery', 'wp-plugins', 'wp-i18n', 'wp-element', 'wp-dom', 'wp-data' ], TIMETICS_VERSION, true );
363 499
364 500 $calendar_locale = timetics_get_option('calendar_locale');
365 - if (!empty($calendar_locale)) {
366 - wp_register_script( 'calendar-locale', "https://npmcdn.com/flatpickr/dist/l10n/{$calendar_locale}.js", [ 'jquery', 'wp-plugins', 'wp-i18n', 'wp-element', 'wp-dom', 'wp-data' ], TIMETICS_VERSION, true );
501 + if (!empty($calendar_locale) && file_exists( TIMETICS_PLUGIN_DIR . "assets/js/local/{$calendar_locale}.js" ) ) {
502 + wp_register_script( 'calendar-locale', TIMETICS_ASSETS_URL . "js/local/{$calendar_locale}.js", [ 'jquery', 'wp-plugins', 'wp-i18n', 'wp-element', 'wp-dom', 'wp-data' ], TIMETICS_VERSION, true );
367 503 }
368 504
369 505 // load text domain
370 506 wp_set_script_translations( 'timetics-frontend-scripts', 'timetics', TIMETICS_PLUGIN_DIR . 'languages/' );
@@ -463,9 +599,9 @@
463 599
464 600 \Wpmet\Libs\Banner::instance('timetics')
465 601 ->is_test(true)
466 602 ->set_filter(ltrim($filter_string, ','))
467 - ->set_api_url('https://banner.themefunction.com/public/jhanda')
603 + ->set_api_url('https://banner.arraytics.com/public/jhanda')
468 604 ->set_plugin_screens('timetics')
469 605 ->set_plugin_screens('toplevel_page_timetics')
470 606 ->call();
471 607 // show get-help and upgrade-to-premium menu.
@@ -503,9 +639,9 @@
503 639 color: "#0060e5",
504 640 });
505 641 window.Beacon('init', '4be24aa2-ee50-483c-a90c-db4216664d65');
506 642 window.Beacon('on', 'ready', function(){
507 - window.Beacon('show');
643 + window.Beacon('close');
508 644 });
509 645
510 646 </script>
511 647 <?php