'default', 'calendar' => 'gregorian', 'time_format' => '24', 'use_site_tz' => 1, 'custom_timezone' => 'UTC', 'minimal_mode' => 0, 'time_color' => '#2ECC71', 'date_color' => '#E74C3C', 'clickable_clock' => 1, 'floating_clock' => 0, // Default OFF to keep classic toolbar behaviour. 'display_mode' => 'digital', 'analog_theme' => 'tomanify', 'analog_show_date' => 1, 'analog_size' => 'small', ]; } /** * Retrieve a single option (with defaults). * * @param string $key Option key. * @return mixed|null */ function atlc_get_option( $key ) { $all = wp_parse_args( get_option( ATLC_OPTION_NAME, [] ), atlc_default_options() ); return $all[ $key ] ?? null; } /** * Determine whether the floating clock should be used. * * Floating is required for Analog mode (the admin bar is too small). * * @return bool */ function atlc_should_use_floating_clock() { if ( 'analog' === atlc_get_option( 'display_mode' ) ) { return true; } return ( (int) atlc_get_option( 'floating_clock' ) === 1 ); } /** * Return the site’s timezone (IANA or UTC±offset). * * @return string */ function atlc_get_site_timezone() { $tz_string = get_option( 'timezone_string' ); if ( $tz_string ) { return $tz_string; } $offset = get_option( 'gmt_offset' ); if ( ! $offset ) { return 'UTC'; } $hours = floor( $offset ); $minutes = abs( ( $offset - $hours ) * 60 ); return sprintf( 'UTC%+03d:%02d', $hours, $minutes ); } /** * Return the selected timezone based on settings. * * @return string */ function atlc_get_selected_timezone() { return atlc_get_option( 'use_site_tz' ) ? atlc_get_site_timezone() : ( atlc_get_option( 'custom_timezone' ) ?: 'UTC' ); } /** * Settings URL. * * @return string */ function atlc_get_settings_url() { return admin_url( 'options-general.php?page=' . ATLC_MENU_SLUG ); } /* -------------------------------------------------------------------------- */ /* Internationalisation */ /* -------------------------------------------------------------------------- */ /** * Scan /languages for available locales. * * @return array code => label */ function atlc_available_languages() { static $cache = null; if ( null !== $cache ) { return $cache; } $cache = [ 'default' => __( 'Default (Dashboard)', 'admin-toolbar-live-clock' ) ]; foreach ( glob( plugin_dir_path( __FILE__ ) . 'languages/' . ATLC_DOMAIN . '-*.mo' ) as $file ) { $code = str_replace( [ ATLC_DOMAIN . '-', '.mo' ], '', basename( $file ) ); $label = ''; if ( function_exists( 'locale_get_display_language' ) ) { $label = locale_get_display_language( $code, $code ); } if ( ! $label ) { $map = [ 'hi_IN' => 'हिन्दी', 'th_TH' => 'ไทย', ]; $label = $map[ $code ] ?? $code; } if ( preg_match( '/^[a-z]/i', $label ) ) { $label = ucwords( $label ); } $cache[ $code ] = sprintf( '%s (%s)', $label, $code ); } $cache['en_US'] = $cache['en_US'] ?? 'English (en_US)'; return $cache; } add_filter( 'plugin_locale', static function ( $locale, $domain ) { if ( ATLC_DOMAIN !== $domain ) { return $locale; } $sel = atlc_get_option( 'language' ); return ( $sel && 'default' !== $sel ) ? $sel : $locale; }, 10, 2 ); add_action( 'plugins_loaded', static function () { $sel = atlc_get_option( 'language' ); if ( $sel && 'default' !== $sel ) { unload_textdomain( ATLC_DOMAIN ); if ( 'en_US' === $sel ) { return; } $mofiles = [ plugin_dir_path( __FILE__ ) . 'languages/' . ATLC_DOMAIN . "-$sel.mo", WP_LANG_DIR . '/plugins/' . ATLC_DOMAIN . "-$sel.mo", ]; foreach ( $mofiles as $mofile ) { if ( is_readable( $mofile ) && load_textdomain( ATLC_DOMAIN, $mofile ) ) { return; } } } }, 0 ); /* -------------------------------------------------------------------------- */ /* Sanitisation */ /* -------------------------------------------------------------------------- */ /** * Validate & sanitise settings. * * @param array $raw Raw options. * @return array */ function atlc_sanitize_options( $raw ) { $clean = atlc_default_options(); $raw = (array) $raw; $langs = array_keys( atlc_available_languages() ); $calendars = [ 'gregorian', 'persian', 'islamic', 'hebrew', 'buddhist', 'japanese' ]; $display_modes = [ 'digital', 'analog' ]; $themes = [ 'modern', 'material', 'tomanify' ]; $analog_sizes = [ 'large', 'small' ]; $clean['minimal_mode'] = empty( $raw['minimal_mode'] ) ? 0 : 1; if ( isset( $raw['display_mode'] ) && in_array( $raw['display_mode'], $display_modes, true ) ) { $clean['display_mode'] = $raw['display_mode']; } if ( isset( $raw['analog_theme'] ) && in_array( $raw['analog_theme'], $themes, true ) ) { $clean['analog_theme'] = $raw['analog_theme']; } $clean['analog_show_date'] = empty( $raw['analog_show_date'] ) ? 0 : 1; if ( isset( $raw['analog_size'] ) && in_array( $raw['analog_size'], $analog_sizes, true ) ) { $clean['analog_size'] = $raw['analog_size']; } if ( isset( $raw['language'] ) && in_array( $raw['language'], $langs, true ) ) { $clean['language'] = $raw['language']; } if ( isset( $raw['calendar'] ) && in_array( $raw['calendar'], $calendars, true ) ) { $clean['calendar'] = $raw['calendar']; } $clean['time_format'] = ( isset( $raw['time_format'] ) && '12' === $raw['time_format'] ) ? '12' : '24'; $clean['use_site_tz'] = empty( $raw['use_site_tz'] ) ? 0 : 1; $clean['clickable_clock'] = empty( $raw['clickable_clock'] ) ? 0 : 1; $clean['floating_clock'] = empty( $raw['floating_clock'] ) ? 0 : 1; if ( isset( $raw['custom_timezone'] ) && in_array( $raw['custom_timezone'], timezone_identifiers_list(), true ) ) { $clean['custom_timezone'] = $raw['custom_timezone']; } foreach ( [ 'time_color', 'date_color' ] as $c ) { $hex = sanitize_hex_color( $raw[ $c ] ?? '' ); if ( $hex ) { $clean[ $c ] = strtoupper( $hex ); } } if ( 1 === (int) $clean['minimal_mode'] ) { // Minimal (v1-like) mode: hide advanced features and revert to defaults. $clean['display_mode'] = 'digital'; $clean['floating_clock'] = 0; $clean['clickable_clock'] = 0; // Default colours. $clean['time_color'] = '#2ECC71'; $clean['date_color'] = '#E74C3C'; // Keep analog-related options in safe defaults (not used while digital). $clean['analog_theme'] = 'modern'; $clean['analog_show_date'] = 1; $clean['analog_size'] = 'large'; } if ( 'analog' === $clean['display_mode'] ) { $clean['floating_clock'] = 1; } return $clean; } /* -------------------------------------------------------------------------- */ /* Settings page */ /* -------------------------------------------------------------------------- */ add_action( 'admin_menu', static function () { add_options_page( __( 'Admin Toolbar Live Clock', 'admin-toolbar-live-clock' ), __( 'Toolbar Clock', 'admin-toolbar-live-clock' ), 'manage_options', ATLC_MENU_SLUG, 'atlc_render_settings_page' ); } ); add_action( 'admin_init', static function () { register_setting( ATLC_OPTION_GROUP, ATLC_OPTION_NAME, 'atlc_sanitize_options' ); // Tabs (sections) for a cleaner settings UI. add_settings_section( 'atlc_general', '', '__return_false', ATLC_MENU_SLUG ); add_settings_section( 'atlc_style', '', '__return_false', ATLC_MENU_SLUG ); add_settings_section( 'atlc_about', '', '__return_false', ATLC_MENU_SLUG ); $general_fields = [ 'minimal_mode' => __( 'Minimal mode', 'admin-toolbar-live-clock' ), 'calendar' => __( 'Calendar system', 'admin-toolbar-live-clock' ), 'time_format' => __( 'Clock format', 'admin-toolbar-live-clock' ), 'use_site_tz' => __( 'Use site timezone', 'admin-toolbar-live-clock' ), 'custom_timezone' => __( 'Custom timezone', 'admin-toolbar-live-clock' ), 'language' => __( 'Plugin language', 'admin-toolbar-live-clock' ), 'clickable_clock' => __( 'Make clock clickable', 'admin-toolbar-live-clock' ), ]; $style_fields = [ 'display_mode' => __( 'Clock style', 'admin-toolbar-live-clock' ), 'analog_theme' => __( 'Analog theme', 'admin-toolbar-live-clock' ), 'analog_size' => __( 'Analog size', 'admin-toolbar-live-clock' ), 'analog_show_date' => __( 'Show date', 'admin-toolbar-live-clock' ), 'time_color' => __( 'Clock colour', 'admin-toolbar-live-clock' ), 'date_color' => __( 'Date colour', 'admin-toolbar-live-clock' ), 'floating_clock' => __( 'Enable floating (draggable) clock', 'admin-toolbar-live-clock' ), ]; foreach ( $general_fields as $id => $label ) { add_settings_field( $id, $label, "atlc_field_{$id}", ATLC_MENU_SLUG, 'atlc_general' ); } foreach ( $style_fields as $id => $label ) { add_settings_field( $id, $label, "atlc_field_{$id}", ATLC_MENU_SLUG, 'atlc_style' ); } } ); /* ---------- field callbacks ----------- */ function atlc_field_language() { $val = atlc_get_option( 'language' ); ?> __( 'Gregorian', 'admin-toolbar-live-clock' ), 'persian' => __( 'Jalali (Persian)', 'admin-toolbar-live-clock' ), 'islamic' => __( 'Hijri (Islamic)', 'admin-toolbar-live-clock' ), 'hebrew' => __( 'Hebrew', 'admin-toolbar-live-clock' ), 'buddhist' => __( 'Buddhist (Thai)', 'admin-toolbar-live-clock' ), 'japanese' => __( 'Japanese', 'admin-toolbar-live-clock' ), ]; ?> __( '24-hour', 'admin-toolbar-live-clock' ), '12' => __( '12-hour AM/PM', 'admin-toolbar-live-clock' ), ]; ?>

/>

/> />

/> />

', esc_attr( ATLC_OPTION_NAME ), esc_attr( $key ), esc_attr( (string) atlc_get_option( $key ) ) ); } function atlc_field_time_color() { atlc_color_picker_input( 'time_color' ); } function atlc_field_date_color() { atlc_color_picker_input( 'date_color' ); } /** * Render settings page. */ function atlc_render_settings_page() { $active_tab = 'general'; // Tab selection is a UI-only query var (no state changes). Nonce is not required. // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['atlc_tab'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $maybe = sanitize_key( wp_unslash( $_GET['atlc_tab'] ) ); if ( in_array( $maybe, [ 'general', 'style', 'about' ], true ) ) { $active_tab = $maybe; } } ?>

%s

', esc_html( sprintf( /* translators: 1: year (YYYY), 2: author name. */ __( '© %1$s %2$s – Released under the GPL 2.0 or later license.', 'admin-toolbar-live-clock' ), gmdate( 'Y' ), 'Amin Raoufi' ) ) ); ?>
atlc_get_option( 'time_format' ), 'calendar' => atlc_get_option( 'calendar' ), 'time_color' => atlc_get_option( 'time_color' ), 'date_color' => atlc_get_option( 'date_color' ), 'timezone' => atlc_get_selected_timezone(), 'display_mode' => atlc_get_option( 'display_mode' ), 'analog_theme' => atlc_get_option( 'analog_theme' ), 'analog_show_date' => atlc_get_option( 'analog_show_date' ), 'analog_size' => atlc_get_option( 'analog_size' ), ] ); if ( atlc_should_use_floating_clock() ) { wp_enqueue_script( 'atlc-drag', plugins_url( 'assets/js/atlc-drag.js', __FILE__ ), [], ATLC_VERSION, false ); wp_enqueue_style( 'atlc-floating', plugins_url( 'assets/css/atlc-floating.css', __FILE__ ), [], ATLC_VERSION ); } if ( 'analog' === atlc_get_option( 'display_mode' ) ) { wp_enqueue_style( 'atlc-analog', plugins_url( 'assets/css/analog.css', __FILE__ ), [], ATLC_VERSION ); wp_enqueue_script( 'atlc-analog', plugins_url( 'assets/js/analog.js', __FILE__ ), [ 'atlc-clock' ], ATLC_VERSION, true ); } } ); /* -------------------------------------------------------------------------- */ /* Toolbar / Floating clock rendering */ /* -------------------------------------------------------------------------- */ /** * Format date/time for different calendars. * * @param DateTime $dt DateTime object. * @param string $cal Calendar key. * @param string $pattern Time format (e.g. H:i:s A). * @return string */ function atlc_format_datetime( DateTime $dt, $cal, $pattern ) { if ( 'gregorian' === $cal ) { return $dt->format( $pattern . ' Y-m-d' ); } if ( class_exists( 'IntlDateFormatter' ) ) { $map = [ 'persian' => 'fa_IR@calendar=persian', 'islamic' => 'ar_SA@calendar=islamic', 'hebrew' => 'he_IL@calendar=hebrew', 'buddhist' => 'th_TH@calendar=buddhist', 'japanese' => 'ja_JP@calendar=japanese', ]; $locale = $map[ $cal ] ?? get_locale(); $fmt = new IntlDateFormatter( $locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $dt->getTimezone()->getName(), IntlDateFormatter::TRADITIONAL, $pattern . ' yyyy-MM-dd' ); return $fmt->format( $dt ); } return $dt->format( $pattern . ' Y-m-d' ); } /** * Initial label (static) before JS starts ticking. * * @return string */ function atlc_get_initial_label() { $tz = atlc_get_selected_timezone(); $pattern = ( '12' === atlc_get_option( 'time_format' ) ) ? 'h:i:s A' : 'H:i:s'; $now = new DateTime( 'now', new DateTimeZone( $tz ) ); return atlc_format_datetime( $now, (string) atlc_get_option( 'calendar' ), $pattern ); } /** * Inner clock HTML (anchor or span) for floating mode. * * @return string */ function atlc_get_clock_inner_html() { $initial = atlc_get_initial_label(); $label = esc_html( $initial ); // Prevent a brief flash of the digital label when Analog mode is enabled. // analog.js will fully replace the node contents after load. if ( 'analog' === atlc_get_option( 'display_mode' ) ) { $label = ''; } if ( (int) atlc_get_option( 'clickable_clock' ) === 1 ) { return sprintf( '%3$s', esc_url( atlc_get_settings_url() ), esc_attr( $initial ), $label ); } return sprintf( '%2$s', esc_attr( $initial ), $label ); } /** * Floating output (admin only). */ add_action( 'admin_footer', static function () { if ( ! is_admin() || ! current_user_can( 'manage_options' ) ) { return; } if ( ! atlc_should_use_floating_clock() ) { return; } printf( '
%s
', atlc_get_clock_inner_html() // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ); }, 5 ); /** * Toolbar node (admin only, when floating is disabled). */ add_action( 'admin_bar_menu', static function ( WP_Admin_Bar $bar ) { if ( ! is_admin() || ! current_user_can( 'manage_options' ) ) { return; } if ( atlc_should_use_floating_clock() ) { return; // Floating mode replaces toolbar output to avoid duplicate IDs. } $title = sprintf( '%s', esc_html( atlc_get_initial_label() ) ); $args = [ 'id' => 'atlc-node', 'parent' => 'top-secondary', 'title' => $title, ]; if ( (int) atlc_get_option( 'clickable_clock' ) === 1 ) { $args['href'] = atlc_get_settings_url(); } $bar->add_node( $args ); }, 999 ); /* -------------------------------------------------------------------------- */ /* Force English when user selects “en_US” */ /* -------------------------------------------------------------------------- */ add_filter( 'gettext', static function ( $translated, $text, $domain ) { if ( ATLC_DOMAIN === $domain && 'en_US' === atlc_get_option( 'language' ) ) { return $text; } return $translated; }, 10, 3 ); /* -------------------------------------------------------------------------- */ /* Add “Settings” link in plugin list */ /* -------------------------------------------------------------------------- */ add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), static function ( $links ) { if ( current_user_can( 'manage_options' ) ) { $settings_link = sprintf( '%s', esc_url( atlc_get_settings_url() ), esc_html__( 'Settings', 'admin-toolbar-live-clock' ) ); array_unshift( $links, $settings_link ); } return $links; } );