PluginProbe
Timetics – Appointment Booking Calendar & Scheduling / 1.0.62
Timetics – Appointment Booking Calendar & Scheduling v1.0.62
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 1.0.22 1.0.23 1.0.24 All 62 releases
timetics / bootstrap.php

bootstrap.php in Timetics – Appointment Booking Calendar & Scheduling 1.0.62, at bootstrap.php

658 lines 22.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Bootstrap Class
4 *
5 * @package Timetics
6 */
7 namespace Timetics;
8
9 defined( 'ABSPATH' ) || exit;
10
11 /**
12 * Bootstrap class.
13 *
14 * @since 1.0.0
15 */
16 final class Bootstrap {
17 /**
18 * @var Bootstrap The Actual Timetics instance
19 * @since 1.0.0
20 */
21 private static $instance;
22
23
24 private $file;
25 public $version;
26 private $has_pro;
27
28 /**
29 * Throw Error While Trying To Clone Object
30 *
31 * @since 1.0.0
32 * @return void
33 */
34 public function __clone() {
35 _doing_it_wrong( __FUNCTION__, esc_html__( 'Cloning is forbidden.', 'timetics' ), '1.0.0' );
36 }
37
38 /**
39 * Disabling Un-serialization Of This Class
40 */
41 public function __wakeup() {
42 _doing_it_wrong( __FUNCTION__, esc_html__( 'Unserializing instances of this class is forbidden.', 'timetics' ), '1.0.0' );
43 }
44
45 /**
46 * The actual TimeTics instance
47 *
48 * @since 1.0.0
49 * @param string $file
50 * @return void
51 */
52 public static function instantiate( $file = '' ) {
53
54 // Return if already instantiated
55 if ( self::instantiated() ) {
56 return self::$instance;
57 }
58
59 self::prepare_instance( $file );
60
61 self::$instance->initialize_constants();
62 self::$instance->define_tables();
63 self::$instance->include_files();
64 self::$instance->initialize_components();
65
66 return self::$instance;
67 }
68
69 /**
70 * Return If The Main Class has Already Been Instantiated Or Not
71 *
72 * @since 1.0.0
73 * @return boolean
74 */
75 private static function instantiated() {
76 if ( ( null !== self::$instance ) && ( self::$instance instanceof Bootstrap ) ) {
77 return true;
78 }
79
80 return false;
81 }
82
83 /**
84 * Prepare Singleton Instance
85 *
86 * @since 1.0.0
87 * @param string $file
88 * @return void
89 */
90 private static function prepare_instance( $file = '' ) {
91 self::$instance = new self();
92 self::$instance->file = $file;
93 self::$instance->version = \Timetics::get_version();
94 }
95
96 /**
97 * Assets Directory URL
98 *
99 * @since 1.0.0
100 * @return void
101 */
102 public function get_assets_url() {
103 return trailingslashit( TIMETICS_PLUGIN_URL . 'assets' );
104 }
105
106 /**
107 * Assets Directory Path
108 *
109 * @since 1.0.0
110 * @return void
111 */
112 public function get_assets_dir() {
113 return trailingslashit( TIMETICS_PLUGIN_DIR . 'assets' );
114 }
115
116 /**
117 * Plugin Directory URL
118 *
119 * @return void
120 */
121 public function get_plugin_url() {
122 return trailingslashit( plugin_dir_url( TIMETICS_PLUGIN_FILE ) );
123 }
124
125 /**
126 * Plugin Directory Path
127 *
128 * @return void
129 */
130 public function get_plugin_dir() {
131 return \Timetics::get_plugin_dir();
132 }
133
134 /**
135 * Plugin Basename
136 *
137 * @return void
138 */
139 public function get_plugin_basename() {
140 return plugin_basename( TIMETICS_PLUGIN_FILE );
141 }
142
143 /**
144 * Setup Plugin Constants
145 *
146 * @since 1.0.0
147 * @return void
148 */
149 private function initialize_constants() {
150 // Plugin Version
151 self::$instance->define( 'TIMETICS_VERSION', \Timetics::get_version() );
152
153 // Plugin Main File
154 self::$instance->define( 'TIMETICS_PLUGIN_FILE', $this->file );
155
156 // Plugin File Basename
157 self::$instance->define( 'TIMETICS_PLUGIN_BASE', $this->get_plugin_basename() );
158
159 // Plugin Main Directory Path
160 self::$instance->define( 'TIMETICS_PLUGIN_DIR', $this->get_plugin_dir() );
161
162 // Plugin Main Directory URL
163 self::$instance->define( 'TIMETICS_PLUGIN_URL', $this->get_plugin_url() );
164
165 // Plugin Assets Directory URL
166 self::$instance->define( 'TIMETICS_ASSETS_URL', $this->get_assets_url() );
167
168 // Plugin Assets Directory Path
169 self::$instance->define( 'TIMETICS_ASSETS_DIR', $this->get_assets_dir() );
170
171
172 }
173
174 /**
175 * Define constant if not already set.
176 *
177 * @since 1.0.0
178 * @param string $timetics_constant_name Constant name.
179 * @param string|bool $value Constant value.
180 */
181 private function define( $timetics_constant_name, $value ) {
182 if ( ! defined( $timetics_constant_name ) ) {
183 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.VariableConstantNameFound -- Constant name is dynamic but prefixed by caller
184 define( $timetics_constant_name, $value );
185 }
186 }
187
188 /**
189 * Define DB Tables Required For This Plugin
190 *
191 * @since 1.0.0
192 * @return void
193 */
194 private function define_tables() {
195 // To Be Implemented
196 }
197
198 /**
199 * Include All Required Files
200 *
201 * @since 1.0.0
202 * @return void
203 */
204 private function include_files() {
205 // require_once TIMETICS_PLUGIN_DIR . 'includes/post-types.php';
206 // require_once TIMETICS_PLUGIN_DIR . 'includes/widgets/manifest.php';
207 // require_once TIMETICS_PLUGIN_DIR . 'includes/template-functions.php';
208 // require_once TIMETICS_PLUGIN_DIR . 'includes/shortcodes.php';
209 // require_once TIMETICS_PLUGIN_DIR . 'includes/install.php';
210
211 /**
212 * Core helpers.
213 */
214 require_once TIMETICS_PLUGIN_DIR . 'utils/global-helper.php';
215 require_once TIMETICS_PLUGIN_DIR . 'core/staffs/calendars/CalendarSyncFacade.php';
216 require_once TIMETICS_PLUGIN_DIR . 'core/staffs/calendars/CalendarSyncFactory.php';
217 require_once TIMETICS_PLUGIN_DIR . 'core/staffs/calendars/CalendarSyncInterface.php';
218 require_once TIMETICS_PLUGIN_DIR . 'core/staffs/calendars/GoogleCalendarSync.php';
219 }
220
221 /**
222 * Initialize All Components
223 *
224 * @since 1.0.0
225 * @return void
226 */
227 private function initialize_components() {
228
229 // Register scripts and styles first
230 if ( $this->is_request( 'admin' ) ) {
231 add_action( 'admin_enqueue_scripts', [ $this, 'admin_scripts' ] );
232 add_action( 'admin_enqueue_scripts', [ $this, 'preload_js_translations' ], 5 );
233 add_action( 'admin_footer', [ $this, 'admin_helpscout_beacon' ] );
234 }
235
236 if ( $this->is_request( 'frontend' ) ) {
237 add_action( 'wp_enqueue_scripts', array( $this, 'frontend_scripts' ) );
238 add_action( 'wp_enqueue_scripts', [ $this, 'preload_js_translations' ], 5 );
239 }
240
241 // Register admin menu
242 Core\Admin\Menu::instance()->init();
243 Core\Base::instance()->init();
244 Core\Services\Hooks::instance()->init();
245 Base\Custom_Endpoint::instance()->init();
246
247 global $current_screen;
248 $this->has_pro = class_exists('TimeticsPro');
249
250 $this->handle_buy_pro_module();
251
252 if ( function_exists('WC') ) {
253 Core\Integrations\Woocommerce\Hooks::instance()->init();
254 }
255 }
256
257 /**
258 * demo url for set pro button
259 *
260 * @return void
261 */
262 public function demo_url(){
263 return "https://arraytics.com/timetics/";
264 }
265
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 /**
368 * Register scripts and styles for admin
369 *
370 * @return void
371 */
372 public function admin_scripts( $handle ) {
373
374 if('plugins.php' === $handle){
375
376 wp_register_script( 'timetics-feedback-modal', TIMETICS_ASSETS_URL . 'js/feedback-modal.js', [ 'wp-plugins', 'wp-i18n', 'wp-element', 'wp-dom', 'wp-data' ], TIMETICS_VERSION, true );
377 wp_enqueue_script( 'timetics-feedback-modal', TIMETICS_ASSETS_URL . 'js/feedback-modal.js', [], TIMETICS_VERSION, true );
378 wp_register_style( 'timetics-feedback-modal', TIMETICS_ASSETS_URL . 'css/feedback-modal.css', [], TIMETICS_VERSION, 'all' );
379 wp_enqueue_style( 'timetics-feedback-modal', TIMETICS_ASSETS_URL . 'css/feedback-modal.css', [], TIMETICS_VERSION, 'all' );
380
381 $feedback_obj = array(
382 'site_url' => site_url(),
383 'admin_email' => get_option( 'admin_email' )
384 );
385
386 wp_localize_script( 'timetics-feedback-modal', 'timetics_feedback', $feedback_obj );
387
388 }
389
390 if ( 'toplevel_page_timetics' !== $handle) {
391 return;
392 }
393 global $wp_locale;
394
395 wp_enqueue_media();
396 wp_enqueue_style( 'wp-color-picker' );
397 wp_enqueue_style( 'timetics-admin-style', TIMETICS_ASSETS_URL . 'css/admin.css', [], TIMETICS_VERSION, 'all' );
398 wp_enqueue_style( 'timetics-vendor', TIMETICS_ASSETS_URL . 'css/vendor.css', [], TIMETICS_VERSION, 'all' );
399 wp_enqueue_script( 'timetics-flatpickr-scripts', TIMETICS_ASSETS_URL . 'js/vendors/flatpickr.js', [ 'wp-plugins', 'wp-i18n', 'wp-element', 'wp-dom', 'wp-data' ], TIMETICS_VERSION, true );
400 wp_enqueue_script( 'timetics-packages', TIMETICS_ASSETS_URL . 'js/packages.js', [ 'timetics-flatpickr-scripts' ], TIMETICS_VERSION, true );
401
402 $calendar_locale = timetics_get_option('calendar_locale');
403 if (!empty($calendar_locale)) {
404 wp_enqueue_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 );
405 }
406
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 );
408
409 wp_set_script_translations( 'timetics-dashboard-scripts', 'timetics', TIMETICS_PLUGIN_DIR . 'languages/' );
410
411 $localize_obj = array(
412 'site_url' => site_url(),
413 'admin_url' => admin_url(),
414 'nonce' => wp_create_nonce( 'wp_rest' ),
415 'timezone' => timetics_get_timezone_list(),
416 'stripe_pub_key' => timetics_get_option( 'stripe_pub_key' ),
417 'date_format' => get_option( 'date_format' ),
418 'date_format_string' => date_i18n( get_option( 'date_format' ) ),
419 'time_format' => get_option( 'time_format' ),
420 'time_format_string' => date_i18n( get_option( 'time_format' ) ),
421 'start_of_week' => get_option( 'start_of_week', 0 ),
422
423 'default_timezone' => timetics_get_default_timezone(),
424 'currency_list' => timetics_get_currencies(),
425 'current_user_id' => get_current_user_id(),
426 'timeticsPro' => class_exists('TimeticsPro') ? true : false,
427 'demo_url' => $this->demo_url(),
428 'current_user' => timetics_get_current_user(),
429 'aisentic' => $this->get_aisentic_state(),
430 );
431
432 $localize_obj = apply_filters( 'timetics_admin_localize_data', $localize_obj );
433
434 wp_localize_script( 'timetics-dashboard-scripts', 'timetics', $localize_obj );
435 }
436
437 /**
438 * Aisentic connect state for the onboarding consent box and dashboard banner.
439 *
440 * Both surfaces show the email before the user opts in, so the value comes
441 * from the same resolver the connect request uses.
442 *
443 * @return array Aisentic identity plus active/registered flags.
444 */
445 private function get_aisentic_state() {
446 $identity = timetics_aisentic_identity();
447
448 return [
449 'name' => $identity['name'],
450 'email' => $identity['email'],
451 'active' => class_exists( 'Aisentic\Init' ),
452 'registered' => timetics_aisentic_is_registered(),
453 'icon' => \Timetics\Core\Addon\Extension_Icon::get( 'aisentic' ),
454 /*
455 * Aisentic builds before the registration handshake have no
456 * listener for our hook. Connecting would fail silently there, so
457 * the UI hides the offer instead of promising something dead.
458 */
459 'supports_registration' => class_exists( 'Aisentic\Api\Services\Registration_Service' ),
460 'terms_url' => 'https://arraytics.com/terms-of-service/',
461 ];
462 }
463
464 /**
465 * Register scripts and styles for frontend
466 *
467 * @return void
468 */
469 public function frontend_scripts() {
470
471 wp_register_style( 'timetics-vendor', TIMETICS_ASSETS_URL . 'css/vendor.css', [], TIMETICS_VERSION, 'all' );
472 wp_register_style( 'timetics-frontend', TIMETICS_ASSETS_URL . 'css/frontend.css', [], TIMETICS_VERSION, 'all' );
473
474 global $post;
475 if ( $post ) {
476 $is_timetics_cpt = ( 'timetics-appointment' === get_post_type( $post ) );
477 $timetics_shortcodes = [ 'timetics-booking-form', 'timetics-meeting-list', 'timetics-user-dashboard', 'timetics-category' ];
478 $has_timetics_shortcode = false;
479 foreach ( $timetics_shortcodes as $shortcode ) {
480 if ( has_shortcode( $post->post_content, $shortcode ) ) {
481 $has_timetics_shortcode = true;
482 break;
483 }
484 }
485 if ( $is_timetics_cpt || $has_timetics_shortcode ) {
486 wp_enqueue_style( 'timetics-vendor' );
487 wp_enqueue_style( 'timetics-frontend' );
488 }
489 }
490
491 wp_register_script( 'timetics-flatpickr-scripts', TIMETICS_ASSETS_URL . 'js/vendors/flatpickr.js', [ 'wp-plugins', 'wp-i18n', 'wp-element', 'wp-dom', 'wp-data' ], TIMETICS_VERSION, true );
492 wp_enqueue_script( 'timetics-packages', TIMETICS_ASSETS_URL . 'js/packages.js', [ 'timetics-flatpickr-scripts' ], TIMETICS_VERSION, true );
493
494 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 );
495
496 $calendar_locale = timetics_get_option('calendar_locale');
497 if (!empty($calendar_locale) && file_exists( TIMETICS_PLUGIN_DIR . "assets/js/local/{$calendar_locale}.js" ) ) {
498 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 );
499 }
500
501 // load text domain
502 wp_set_script_translations( 'timetics-frontend-scripts', 'timetics', TIMETICS_PLUGIN_DIR . 'languages/' );
503
504 global $wp_locale;
505
506 $stripe_pub_key = timetics_get_option( 'stripe_pub_key' );
507 $stripe_secret_key = timetics_get_option( 'stripe_secret_key' );
508 $stripe_configure = $stripe_pub_key && $stripe_secret_key;
509
510 $localize_obj = array(
511 'site_url' => site_url(),
512 'nonce' => wp_create_nonce( 'wp_rest' ),
513 'date_format' => get_option( 'date_format' ),
514 'time_format' => get_option( 'time_format' ),
515 'stripe_pub_key' => $stripe_pub_key,
516 'currency' => apply_filters( 'timetics_currency', timetics_get_option( 'currency', 'USD' ) ),
517 'stripe_configure' => $stripe_configure,
518 'current_user_id' => get_current_user_id(),
519 'start_of_week' => get_option( 'start_of_week', 0 ),
520 'currency_list' => timetics_get_currencies(),
521 'locale_name' => strtolower( str_replace( '_', '-', get_locale() ) )
522
523 );
524
525 $localize_obj = apply_filters( 'timetics_frontned_localize_data', $localize_obj );
526
527 wp_localize_script( 'timetics-frontend-scripts', 'timetics', $localize_obj );
528 }
529
530 /**
531 * What type of request
532 *
533 * @param string $type admin,frontend, ajax, cron
534 * @return boolean
535 */
536 private function is_request( $type ) {
537 switch ( $type ) {
538 case 'admin':
539 return is_admin();
540 case 'ajax':
541 return defined( 'DOING_AJAX' );
542 case 'cron':
543 return defined( 'DOING_CRON' );
544 case 'frontend':
545 return ( ! is_admin() || defined( 'DOING_AJAX' ) ) && ! defined( 'DOING_CRON' ) && ! $this->is_rest_api_request();
546 }
547 }
548
549 /**
550 * Returns if the request is non-legacy REST API request.
551 *
552 * @return boolean
553 */
554 private function is_rest_api_request() {
555 $server_request = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : false;
556
557 if ( ! $server_request ) {
558 return false;
559 }
560
561 $rest_prefix = trailingslashit( rest_get_url_prefix() );
562 $is_rest_request = ( false !== strpos( $server_request, $rest_prefix ) );
563
564 return apply_filters( 'timetics_is_rest_api_request', $is_rest_request );
565 }
566
567
568 /**
569 * Show buy-pro menu if pro plugin not active
570 *
571 * @return void
572 */
573 public function handle_buy_pro_module() {
574 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only page parameter check, no form processing
575 $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
576
577
578 if ( 'timetics' !== $page ) {
579 return;
580 }
581
582
583 /**
584 * Show banner (codename: jhanda)
585 */
586 $filter_string = 'timetics,timetics-free-only';
587
588 if( $this->has_pro ) {
589
590 $filter_string .= ',timetics-pro';
591 $filter_string = str_replace(',timetics-free-only', '', $filter_string);
592
593 }
594
595
596 \Wpmet\Libs\Banner::instance('timetics')
597 ->is_test(true)
598 ->set_filter(ltrim($filter_string, ','))
599 ->set_api_url('https://banner.arraytics.com/public/jhanda')
600 ->set_plugin_screens('timetics')
601 ->set_plugin_screens('toplevel_page_timetics')
602 ->call();
603 // show get-help and upgrade-to-premium menu.
604 // $this->handle_get_help_and_upgrade_menu();
605 }
606
607 /**
608 * Add HelpScout script to admin footer
609 *
610 * @return void
611 */
612 public function admin_helpscout_beacon() {
613 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
614 $is_timetics_screen = false;
615
616 if ( $screen ) {
617 $screen_id = isset( $screen->id ) ? $screen->id : '';
618
619 // Check: only load on Timetics top-level admin page
620 if ( 'toplevel_page_timetics' === $screen_id ) {
621 $is_timetics_screen = true;
622 }
623 }
624
625 // Allow overriding detection via filter.
626 $is_timetics_screen = apply_filters( 'timetics_is_admin_screen', $is_timetics_screen, $screen );
627
628 if ( ! $is_timetics_screen ) {
629 return;
630 }
631 ?>
632 <script type="text/javascript">!function(e,t,n){function a(){var e=t.getElementsByTagName("script")[0],n=t.createElement("script");n.type="text/javascript",n.async=!0,n.src="https://beacon-v2.helpscout.net",e.parentNode.insertBefore(n,e)}if(e.Beacon=n=function(t,n,a){e.Beacon.readyQueue.push({method:t,options:n,data:a})},n.readyQueue=[],"complete"===t.readyState)a();else if(e.attachEvent)e.attachEvent("onload",a);else e.addEventListener("load",a,!1)}(window,document,window.Beacon||function(){});</script>
633 <script type="text/javascript">
634 window.Beacon('config', {
635 color: "#0060e5",
636 });
637 window.Beacon('init', '4be24aa2-ee50-483c-a90c-db4216664d65');
638 window.Beacon('on', 'ready', function(){
639 window.Beacon('close');
640 });
641
642 </script>
643 <?php
644 }
645
646 }
647
648 /**
649 * Returns The Instance Of Timetics.
650 * The main function that is responsible for returning Timetics instance.
651 *
652 * @since 1.0.0
653 * @return Timetics
654 */
655 function timetics() {
656 return Bootstrap::instantiate();
657 }
658