PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.29
Booktics – Appointment Booking Calendar for Service Businesses v1.0.29
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 / admin / theme-mode.php

theme-mode.php in Booktics – Appointment Booking Calendar for Service Businesses 1.0.29, at core/admin/theme-mode.php

179 lines 5.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Booktics\Admin;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit;
7 }
8
9 use Booktics\Contracts\Hookable_Service_Contract;
10
11 /**
12 * Per-user light/dark preference for the Booktics admin.
13 *
14 * The preference is stored against the WordPress user, not in plugin options,
15 * so one admin choosing dark does not impose it on everyone else who logs in.
16 *
17 * It is printed on <body> by PHP, before any script runs. Without that, a dark
18 * mode user gets a white flash while React mounts, which reads as a broken
19 * plugin rather than a loading one. PHP cannot resolve 'system' on the server,
20 * so it prints the preference and a CSS media query settles it; once React
21 * mounts it swaps in the resolved value.
22 *
23 * Saving needs no controller of our own: register_meta() with show_in_rest
24 * makes core's POST /wp/v2/users/me accept the field, and a logged-in user may
25 * always edit their own profile.
26 *
27 * @package Booktics/Admin
28 */
29 class Theme_Mode implements Hookable_Service_Contract {
30
31 /**
32 * User meta key holding the preference.
33 */
34 const META_KEY = 'booktics_theme_mode';
35
36 /**
37 * The only values that may ever be stored or printed.
38 */
39 const MODES = array( 'light', 'dark', 'system' );
40
41 /**
42 * Used when nothing is stored, and when anything invalid is.
43 */
44 const DEFAULT_MODE = 'system';
45
46 /**
47 * The one admin screen Booktics owns.
48 */
49 const SCREEN = 'toplevel_page_booktics';
50
51 /**
52 * Register hooks.
53 *
54 * @return void
55 */
56 public function register() {
57 // Called directly, not hooked. Service providers are booted from
58 // init_classes() which already runs on `init` (base/booktics.php:92),
59 // so adding another `init` callback here fires too late and the meta
60 // never reaches the REST user schema.
61 $this->register_meta();
62
63 add_filter( 'admin_body_class', array( $this, 'body_class' ) );
64 }
65
66 /**
67 * Expose the preference on core's user REST endpoint.
68 *
69 * @return void
70 */
71 public function register_meta() {
72 register_meta(
73 'user',
74 self::META_KEY,
75 array(
76 'type' => 'string',
77 'single' => true,
78 'default' => self::DEFAULT_MODE,
79 'show_in_rest' => true,
80 'sanitize_callback' => array( __CLASS__, 'sanitize' ),
81 'auth_callback' => function ( $allowed, $meta_key, $object_id ) {
82 return current_user_can( 'edit_user', $object_id );
83 },
84 )
85 );
86 }
87
88 /**
89 * Force any value into one of the three modes.
90 *
91 * This value ends up inside a class attribute on <body>, so it is a trust
92 * boundary. The REST endpoint belongs to core, which means the payload
93 * shape is not ours to control: arrays and nulls have to fall through
94 * quietly rather than raise a warning.
95 *
96 * @param mixed $value Raw value from REST, the database, or WP-CLI.
97 *
98 * @return string One of self::MODES.
99 */
100 public static function sanitize( $value ) {
101 if ( ! is_string( $value ) ) {
102 return self::DEFAULT_MODE;
103 }
104
105 return in_array( $value, self::MODES, true ) ? $value : self::DEFAULT_MODE;
106 }
107
108 /**
109 * The preference for a user.
110 *
111 * Sanitises on the way out as well as in. Meta can be written by WP-CLI,
112 * by other code, or by an older version of this plugin, so reading it is
113 * just as much a trust boundary as writing it.
114 *
115 * @param int $user_id 0 for the current user.
116 *
117 * @return string One of self::MODES.
118 */
119 public static function get_for_user( $user_id = 0 ) {
120 $user_id = $user_id ? $user_id : get_current_user_id();
121
122 if ( ! $user_id ) {
123 return self::DEFAULT_MODE;
124 }
125
126 return self::sanitize( get_user_meta( $user_id, self::META_KEY, true ) );
127 }
128
129 /**
130 * Whether the feature is visible to anyone.
131 *
132 * On since the conversion finished. The filter stays so a site owner who
133 * does not want dark mode can turn the whole thing off in one line:
134 *
135 * add_filter( 'booktics_enable_dark_mode', '__return_false' );
136 *
137 * @return bool
138 */
139 public static function is_enabled() {
140 return (bool) apply_filters( 'booktics_enable_dark_mode', true );
141 }
142
143 /**
144 * Add the theme class to <body> on the Booktics screen only.
145 *
146 * Returns other screens untouched, and does not look up a user on them,
147 * so this costs nothing anywhere else in wp-admin.
148 *
149 * @param string $classes Space separated classes from WordPress.
150 *
151 * @return string
152 */
153 public function body_class( $classes ) {
154 if ( ! $this->is_booktics_screen() ) {
155 return $classes;
156 }
157
158 return $classes . ' booktics-theme-' . self::get_for_user();
159 }
160
161 /**
162 * Whether the current request is rendering the Booktics admin screen.
163 *
164 * get_current_screen() is unavailable early in the request, hence the
165 * function_exists guard.
166 *
167 * @return bool
168 */
169 private function is_booktics_screen() {
170 if ( ! function_exists( 'get_current_screen' ) ) {
171 return false;
172 }
173
174 $screen = get_current_screen();
175
176 return $screen && self::SCREEN === $screen->base;
177 }
178 }
179