PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / trunk
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! vtrunk
3.8.0 3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 All 112 releases
← All changes | includes/Utils/Options.php +159 -69 3.0.3 → trunk View file →
@@ -9,33 +9,46 @@
9 9 use function get_current_user_id;
10 10
11 11 class Options extends Base {
12 12 /**
13 - * Current User Id
14 - * @var integer
13 + * Pin every derived read/write to the acting user, bypassing the
14 + * global-login fallback in user_id().
15 + *
16 + * @var bool
15 17 */
16 - private $current_user = 0;
18 + private $force_current_user = false;
17 19
18 20 /**
19 - * User has any api?
20 - * @var boolean
21 + * Get the current user ID.
22 + *
23 + * Resolved live on every call rather than cached at construction time:
24 + * `Options` is a request-lifetime singleton (`Base::get_instance()`), and
25 + * something in Templately's own bootstrap (e.g. `Admin`/`Settings`)
26 + * constructs it during `plugins_loaded` — before WordPress resolves the
27 + * REST Application-Password current user (which only happens later, when
28 + * the REST server actually dispatches the route). Caching `
29 + * get_current_user_id()` at construction froze it at `0` for the rest of
30 + * the request on any headless (non-cookie) REST/MCP call, so every later
31 + * `Options` read looked up user `0`'s meta instead of the real acting
32 + * user's — reporting "session expired" even for a genuinely connected
33 + * account. `get_current_user_id()` is itself cheap (it just reads WP
34 + * core's own already-resolved `$current_user` global), so there is no
35 + * reason to cache it a second time here.
36 + *
37 + * @return int
21 38 */
22 - private $has_api = false;
23 -
24 - /**
25 - * Automatically invoked and set up the properties.
26 - */
27 - public function __construct(){
28 - $this->current_user = get_current_user_id();
29 - $this->has_api = ! empty( $this->get( 'api_key', '', $this->current_user ) );
39 + public function current_user_id(): int {
40 + return get_current_user_id();
30 41 }
31 42
32 43 /**
33 - * Get the current user ID.
34 - * @return int
44 + * Whether the current user has an API key set — computed live for the
45 + * same reason as {@see current_user_id()}, not cached.
46 + *
47 + * @return boolean
35 48 */
36 - public function current_user_id(){
37 - return $this->current_user;
49 + private function has_api(): bool {
50 + return ! empty( $this->get( 'api_key', '', $this->current_user_id() ) );
38 51 }
39 52
40 53 /**
41 54 * Can a user link another templately account in a setup?.
@@ -40,109 +53,132 @@
40 53 /**
41 54 * Can a user link another templately account in a setup?.
42 55 * @return boolean
43 56 */
44 - public function link_account() {
45 - return $this->whoami() === 'link' && ! $this->has_api;
57 + public function link_account(): bool {
58 + return $this->who_am_i() === 'link' && ! $this->has_api();
46 59 }
47 60
48 - public function unlink_account() {
49 - return ( $this->whoami() === 'link' || $this->whoami() === 'local' ) && $this->has_api;
61 + public function unlink_account(): bool {
62 + return ( $this->who_am_i() === 'link' || $this->who_am_i() === 'local' ) && $this->has_api();
50 63 }
64 +
51 65 /**
52 66 * Get determined who am I.
53 67 * @return string
54 68 */
55 - public function whoami(){
56 - $_whoami = 'local';
69 + public function who_am_i(): string {
70 + $_who_am_i = 'local';
71 + $current_user = $this->current_user_id();
57 72
58 - if( $this->is_global() > 0 && $this->is_global() === $this->current_user ) {
59 - $_whoami = 'global';
73 + if( $this->is_global() > 0 && $this->is_global() === $current_user ) {
74 + $_who_am_i = 'global';
60 75 }
61 76
62 - if( $this->is_global() > 0 && $this->is_global() !== $this->current_user ) {
63 - $_whoami = 'link';
77 + if( $this->is_global() > 0 && $this->is_global() !== $current_user ) {
78 + $_who_am_i = 'link';
64 79 }
65 80
66 81 if( $this->is_global() == 0 ) {
67 - $_whoami = 'local';
82 + $_who_am_i = 'local';
68 83 }
69 84
70 - return $_whoami;
85 + return $_who_am_i;
71 86 }
87 +
72 88 /**
89 + * Pin the acting user as the target for every derived read/write.
90 + *
91 + * @param bool $force Whether to force the current user.
92 + * @return Options
93 + */
94 + public function use_current_user( bool $force = true ): Options {
95 + $this->force_current_user = $force;
96 +
97 + return $this;
98 + }
99 +
100 + /**
73 101 * Get user id determine dynamically
74 102 * @return integer
75 103 */
76 - private function user_id(){
77 - $_whoami = $this->whoami();
78 - // Helper::log( '_whoami: ' . $_whoami );
104 + private function user_id(): int {
105 + if ( $this->force_current_user ) {
106 + return $this->current_user_id();
107 + }
79 108
109 + $_who_am_i = $this->who_am_i();
110 +
80 111 if( ! empty( $_SERVER['REQUEST_URI'] ) ) {
81 - $parse_uri = explode( '/', substr( $_SERVER['REQUEST_URI'], 0, strpos( $_SERVER['REQUEST_URI'], '?' ) ) );
82 - if( $_whoami === 'link' && array_pop( $parse_uri ) === 'login' ) {
83 - return $this->current_user;
112 + // FR-003: in 'link' mode the login-endpoint override targets the
113 + // current user so they can store their own credentials. Detect it
114 + // with a substring match on '/login' so REST paths such as
115 + // `/wp-json/templately/v1/login` are covered — including requests
116 + // with NO query string, which the former last-path-segment check
117 + // (substr up to '?', which collapses to '' when there is no '?')
118 + // never matched. `strpos(...) !== false` keeps the PHP 7.4 floor
119 + // (`str_contains()` is PHP 8.0+).
120 + $uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) );
121 + if( $_who_am_i === 'link' && strpos( $uri, '/login' ) !== false ) {
122 + return $this->current_user_id();
84 123 }
85 124 }
86 125
87 - if( $_whoami === 'link' && $this->has_api ) {
88 - return $this->current_user;
126 + if( $_who_am_i === 'link' && $this->has_api() ) {
127 + return $this->current_user_id();
89 128 }
90 129
91 - return $_whoami === 'local' ? $this->current_user : $this->is_global();
130 + return $_who_am_i === 'local' ? $this->current_user_id() : $this->is_global();
92 131 }
132 +
93 133 /**
94 134 * Globally logged in and the User ID of globally logged-in user.
95 135 * @return integer
96 136 */
97 - public function is_global(){
137 + public function is_global(): int {
98 138 return intval( get_option('_templately_global_login', 0) );
99 139 }
140 +
100 141 /**
101 142 * Set global login flag
102 143 * @return boolean
103 144 */
104 - public static function set_global_login() {
145 + public static function set_global_login(): bool {
105 146 return update_option('_templately_global_login', get_current_user_id(), 'no');
106 147 }
148 +
107 149 /**
108 150 * Remove global login flag
109 151 * @return boolean
110 152 */
111 - public function remove_global_login() {
153 + public function remove_global_login(): bool {
112 154 return delete_option( '_templately_global_login' );
113 155 }
114 156
115 - public function is_globally_signed() {
116 - return $this->whoami() !== 'local';
157 + public function is_globally_signed(): bool {
158 + return $this->who_am_i() !== 'local';
117 159 }
118 - public function signed_as_global() {
119 - return $this->current_user === $this->is_global();
160 +
161 + public function signed_as_global(): bool {
162 + return $this->current_user_id() === $this->is_global();
120 163 }
164 +
121 165 /**
122 - * Set optional usermeta or option data
166 + * Set optional user meta or option data
123 167 *
124 168 * @param string $key
125 169 * @param mixed $value
170 + * @param null $user_id
126 171 * @return boolean
127 172 */
128 - public function set( $key, $value, $user_id = null ){
173 + public function set( $key, $value, $user_id = null ): bool {
129 174 $key = '_templately_' . $key;
130 175
131 - if( $user_id === null ) {
132 - $user_id = $this->user_id();
133 - }
176 + return $this->update_user_meta( $user_id, $key, $value );
177 + }
134 178
135 - $updated = update_user_meta( $user_id, $key, $value );
136 -
137 - if( $key === '_templately_api_key' && $updated ) {
138 - $this->has_api = true;
139 - }
140 -
141 - return $updated;
142 - }
143 179 /**
144 - * Get optional usermeta or option data
180 + * Get optional user meta or option data
145 181 *
146 182 * @param string $key
147 183 * @param mixed $default
148 184 * @return mixed
@@ -148,29 +184,83 @@
148 184 * @return mixed
149 185 */
150 186 public function get( $key, $default = false, $user_id = null ){
151 187 $key = '_templately_' . $key;
152 - $_user_meta = get_user_meta( is_null( $user_id ) ? $this->user_id() : $user_id, $key, true );
153 - return ! empty( $_user_meta ) ? $_user_meta : $default;
188 + $_user_meta = $this->get_user_meta( $user_id, $key, true );
189 + // '' (get_user_meta) and false (get_user_option) are the MISSING sentinels —
190 + // but 0, '0' and [] are legitimate stored values and must not collapse to
191 + // the default (the same falsy-swallow bug fixed in API::get_param()).
192 + return ( '' === $_user_meta || false === $_user_meta ) ? $default : $_user_meta;
154 193 }
194 +
155 195 /**
156 - * Remove options data or usermeta
196 + * Remove options data or user meta
157 197 *
158 198 * @param string $key
159 199 * @return Options
160 200 */
161 - public function remove( $key ){
201 + public function remove( string $key ): Options {
162 202 $key = '_templately_' . $key;
163 - $updated = delete_user_meta( $this->user_id(), $key ); // delete user meta
203 + $this->delete_user_meta( $key );
164 204
165 - if( $key === '_templately_api_key' && $updated ) {
166 - $this->has_api = false;
205 + return $this;
206 + }
207 +
208 + public function get_user_meta( $user_id, $key = '', $single = false ) {
209 + $user_id = is_null( $user_id ) ? $this->user_id() : $user_id;
210 +
211 + if( ! is_multisite() ) {
212 + return get_user_meta( $user_id, $key, $single);
167 213 }
168 214
169 - return $this;
215 + return get_user_option( $key, $user_id );
170 216 }
171 217
218 + public function update_user_meta($user_id, $meta_key, $meta_value) {
219 + if ( is_null( $user_id ) ) {
220 + if ( ! $this->can_write() ) {
221 + return false;
222 + }
223 +
224 + $user_id = $this->user_id();
225 + }
226 +
227 + if( ! is_multisite() ) {
228 + return update_user_meta( $user_id, $meta_key, $meta_value );
229 + }
230 +
231 + return update_user_option( $user_id, $meta_key, $meta_value, $this->_is_global() );
232 + }
233 +
234 + public function delete_user_meta( $meta_key ): bool {
235 + if ( ! $this->can_write() ) {
236 + return false;
237 + }
238 +
239 + if( ! is_multisite() ) {
240 + return delete_user_meta( $this->user_id(), $meta_key );
241 + }
242 +
243 + return delete_user_option( $this->user_id(), $meta_key, $this->_is_global() );
244 + }
245 +
172 246 /**
247 + * Whether the current request may write to a derived user target.
248 + *
249 + * Reads retain the global-login fallback for unauthenticated cloud callbacks,
250 + * but an anonymous request must never write through it to the administrator.
251 + *
252 + * @return bool
253 + */
254 + private function can_write(): bool {
255 + return $this->current_user_id() > 0;
256 + }
257 +
258 + private function _is_global() {
259 + return apply_filters( 'templately_multisite_is_global', false );
260 + }
261 +
262 + /**
173 263 * Get option data
174 264 *
175 265 * @since 2.0.1
176 266 *
@@ -193,8 +283,8 @@
193 283 * @param string $autoload
194 284 *
195 285 * @return bool
196 286 */
197 - public function update_option( $key, $value, $autoload = 'no' ){
287 + public function update_option( $key, $value, $autoload = 'no' ): bool {
198 288 return update_option( $key, $value, $autoload );
199 289 }
200 -}
290 +}