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 +53 -46 3.7.2 → trunk View file →
@@ -9,20 +9,8 @@
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
15 - */
16 - private $current_user;
17 -
18 - /**
19 - * User has any api?
20 - * @var boolean
21 - */
22 - private $has_api;
23 -
24 - /**
25 13 * Pin every derived read/write to the acting user, bypassing the
26 14 * global-login fallback in user_id().
27 15 *
28 16 * @var bool
@@ -29,21 +17,38 @@
29 17 */
30 18 private $force_current_user = false;
31 19
32 20 /**
33 - * Automatically invoked and set up the properties.
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
34 38 */
35 - public function __construct(){
36 - $this->current_user = get_current_user_id();
37 - $this->has_api = ! empty( $this->get( 'api_key', '', $this->current_user ) );
39 + public function current_user_id(): int {
40 + return get_current_user_id();
38 41 }
39 42
40 43 /**
41 - * Get the current user ID.
42 - * @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
43 48 */
44 - public function current_user_id(): int {
45 - return $this->current_user;
49 + private function has_api(): bool {
50 + return ! empty( $this->get( 'api_key', '', $this->current_user_id() ) );
46 51 }
47 52
48 53 /**
49 54 * Can a user link another templately account in a setup?.
@@ -49,13 +54,13 @@
49 54 * Can a user link another templately account in a setup?.
50 55 * @return boolean
51 56 */
52 57 public function link_account(): bool {
53 - return $this->who_am_i() === 'link' && ! $this->has_api;
58 + return $this->who_am_i() === 'link' && ! $this->has_api();
54 59 }
55 60
56 61 public function unlink_account(): bool {
57 - return ( $this->who_am_i() === 'link' || $this->who_am_i() === 'local' ) && $this->has_api;
62 + return ( $this->who_am_i() === 'link' || $this->who_am_i() === 'local' ) && $this->has_api();
58 63 }
59 64
60 65 /**
61 66 * Get determined who am I.
@@ -62,14 +67,15 @@
62 67 * @return string
63 68 */
64 69 public function who_am_i(): string {
65 70 $_who_am_i = 'local';
71 + $current_user = $this->current_user_id();
66 72
67 - if( $this->is_global() > 0 && $this->is_global() === $this->current_user ) {
73 + if( $this->is_global() > 0 && $this->is_global() === $current_user ) {
68 74 $_who_am_i = 'global';
69 75 }
70 76
71 - if( $this->is_global() > 0 && $this->is_global() !== $this->current_user ) {
77 + if( $this->is_global() > 0 && $this->is_global() !== $current_user ) {
72 78 $_who_am_i = 'link';
73 79 }
74 80
75 81 if( $this->is_global() == 0 ) {
@@ -96,25 +102,33 @@
96 102 * @return integer
97 103 */
98 104 private function user_id(): int {
99 105 if ( $this->force_current_user ) {
100 - return $this->current_user;
106 + return $this->current_user_id();
101 107 }
102 108
103 109 $_who_am_i = $this->who_am_i();
104 110
105 111 if( ! empty( $_SERVER['REQUEST_URI'] ) ) {
106 - $parse_uri = explode( '/', substr( $_SERVER['REQUEST_URI'], 0, strpos( $_SERVER['REQUEST_URI'], '?' ) ) );
107 - if( $_who_am_i === 'link' && array_pop( $parse_uri ) === 'login' ) {
108 - 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();
109 123 }
110 124 }
111 125
112 - if( $_who_am_i === 'link' && $this->has_api ) {
113 - return $this->current_user;
126 + if( $_who_am_i === 'link' && $this->has_api() ) {
127 + return $this->current_user_id();
114 128 }
115 129
116 - return $_who_am_i === 'local' ? $this->current_user : $this->is_global();
130 + return $_who_am_i === 'local' ? $this->current_user_id() : $this->is_global();
117 131 }
118 132
119 133 /**
120 134 * Globally logged in and the User ID of globally logged-in user.
@@ -144,9 +158,9 @@
144 158 return $this->who_am_i() !== 'local';
145 159 }
146 160
147 161 public function signed_as_global(): bool {
148 - return $this->current_user === $this->is_global();
162 + return $this->current_user_id() === $this->is_global();
149 163 }
150 164
151 165 /**
152 166 * Set optional user meta or option data
@@ -158,15 +172,9 @@
158 172 */
159 173 public function set( $key, $value, $user_id = null ): bool {
160 174 $key = '_templately_' . $key;
161 175
162 - $updated = $this->update_user_meta( $user_id, $key, $value );
163 -
164 - if( $key === '_templately_api_key' && $updated ) {
165 - $this->has_api = true;
166 - }
167 -
168 - return $updated;
176 + return $this->update_user_meta( $user_id, $key, $value );
169 177 }
170 178
171 179 /**
172 180 * Get optional user meta or option data
@@ -177,9 +185,12 @@
177 185 */
178 186 public function get( $key, $default = false, $user_id = null ){
179 187 $key = '_templately_' . $key;
180 188 $_user_meta = $this->get_user_meta( $user_id, $key, true );
181 - return ! empty( $_user_meta ) ? $_user_meta : $default;
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;
182 193 }
183 194
184 195 /**
185 196 * Remove options data or user meta
@@ -188,14 +199,10 @@
188 199 * @return Options
189 200 */
190 201 public function remove( string $key ): Options {
191 202 $key = '_templately_' . $key;
192 - $updated = $this->delete_user_meta( $key );
203 + $this->delete_user_meta( $key );
193 204
194 - if( $key === '_templately_api_key' && $updated ) {
195 - $this->has_api = false;
196 - }
197 -
198 205 return $this;
199 206 }
200 207
201 208 public function get_user_meta( $user_id, $key = '', $single = false ) {
@@ -244,9 +251,9 @@
244 251 *
245 252 * @return bool
246 253 */
247 254 private function can_write(): bool {
248 - return $this->current_user > 0;
255 + return $this->current_user_id() > 0;
249 256 }
250 257
251 258 private function _is_global() {
252 259 return apply_filters( 'templately_multisite_is_global', false );