PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.8.0
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.8.0
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 +96 -43 3.0.63.8.0 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;
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;
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(): int {
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?.
@@ -41,13 +54,13 @@
41 54 * Can a user link another templately account in a setup?.
42 55 * @return boolean
43 56 */
44 57 public function link_account(): bool {
45 - return $this->who_am_i() === 'link' && ! $this->has_api;
58 + return $this->who_am_i() === 'link' && ! $this->has_api();
46 59 }
47 60
48 61 public function unlink_account(): bool {
49 - 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();
50 63 }
51 64
52 65 /**
53 66 * Get determined who am I.
@@ -54,14 +67,15 @@
54 67 * @return string
55 68 */
56 69 public function who_am_i(): string {
57 70 $_who_am_i = 'local';
71 + $current_user = $this->current_user_id();
58 72
59 - if( $this->is_global() > 0 && $this->is_global() === $this->current_user ) {
73 + if( $this->is_global() > 0 && $this->is_global() === $current_user ) {
60 74 $_who_am_i = 'global';
61 75 }
62 76
63 - if( $this->is_global() > 0 && $this->is_global() !== $this->current_user ) {
77 + if( $this->is_global() > 0 && $this->is_global() !== $current_user ) {
64 78 $_who_am_i = 'link';
65 79 }
66 80
67 81 if( $this->is_global() == 0 ) {
@@ -71,26 +85,50 @@
71 85 return $_who_am_i;
72 86 }
73 87
74 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 + /**
75 101 * Get user id determine dynamically
76 102 * @return integer
77 103 */
78 104 private function user_id(): int {
105 + if ( $this->force_current_user ) {
106 + return $this->current_user_id();
107 + }
108 +
79 109 $_who_am_i = $this->who_am_i();
80 110
81 111 if( ! empty( $_SERVER['REQUEST_URI'] ) ) {
82 - $parse_uri = explode( '/', substr( $_SERVER['REQUEST_URI'], 0, strpos( $_SERVER['REQUEST_URI'], '?' ) ) );
83 - if( $_who_am_i === 'link' && array_pop( $parse_uri ) === 'login' ) {
84 - 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();
85 123 }
86 124 }
87 125
88 - if( $_who_am_i === 'link' && $this->has_api ) {
89 - return $this->current_user;
126 + if( $_who_am_i === 'link' && $this->has_api() ) {
127 + return $this->current_user_id();
90 128 }
91 129
92 - return $_who_am_i === 'local' ? $this->current_user : $this->is_global();
130 + return $_who_am_i === 'local' ? $this->current_user_id() : $this->is_global();
93 131 }
94 132
95 133 /**
96 134 * Globally logged in and the User ID of globally logged-in user.
@@ -120,9 +158,9 @@
120 158 return $this->who_am_i() !== 'local';
121 159 }
122 160
123 161 public function signed_as_global(): bool {
124 - return $this->current_user === $this->is_global();
162 + return $this->current_user_id() === $this->is_global();
125 163 }
126 164
127 165 /**
128 166 * Set optional user meta or option data
@@ -134,15 +172,9 @@
134 172 */
135 173 public function set( $key, $value, $user_id = null ): bool {
136 174 $key = '_templately_' . $key;
137 175
138 - $updated = $this->update_user_meta( $user_id, $key, $value );
139 -
140 - if( $key === '_templately_api_key' && $updated ) {
141 - $this->has_api = true;
142 - }
143 -
144 - return $updated;
176 + return $this->update_user_meta( $user_id, $key, $value );
145 177 }
146 178
147 179 /**
148 180 * Get optional user meta or option data
@@ -153,9 +185,12 @@
153 185 */
154 186 public function get( $key, $default = false, $user_id = null ){
155 187 $key = '_templately_' . $key;
156 188 $_user_meta = $this->get_user_meta( $user_id, $key, true );
157 - 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;
158 193 }
159 194
160 195 /**
161 196 * Remove options data or user meta
@@ -164,14 +199,10 @@
164 199 * @return Options
165 200 */
166 201 public function remove( string $key ): Options {
167 202 $key = '_templately_' . $key;
168 - $updated = $this->delete_user_meta( $key );
203 + $this->delete_user_meta( $key );
169 204
170 - if( $key === '_templately_api_key' && $updated ) {
171 - $this->has_api = false;
172 - }
173 -
174 205 return $this;
175 206 }
176 207
177 208 public function get_user_meta( $user_id, $key = '', $single = false ) {
@@ -184,10 +215,16 @@
184 215 return get_user_option( $key, $user_id );
185 216 }
186 217
187 218 public function update_user_meta($user_id, $meta_key, $meta_value) {
188 - $user_id = is_null( $user_id ) ? $this->user_id() : $user_id;
219 + if ( is_null( $user_id ) ) {
220 + if ( ! $this->can_write() ) {
221 + return false;
222 + }
189 223
224 + $user_id = $this->user_id();
225 + }
226 +
190 227 if( ! is_multisite() ) {
191 228 return update_user_meta( $user_id, $meta_key, $meta_value );
192 229 }
193 230
@@ -194,8 +231,12 @@
194 231 return update_user_option( $user_id, $meta_key, $meta_value, $this->_is_global() );
195 232 }
196 233
197 234 public function delete_user_meta( $meta_key ): bool {
235 + if ( ! $this->can_write() ) {
236 + return false;
237 + }
238 +
198 239 if( ! is_multisite() ) {
199 240 return delete_user_meta( $this->user_id(), $meta_key );
200 241 }
201 242
@@ -201,8 +242,20 @@
201 242
202 243 return delete_user_option( $this->user_id(), $meta_key, $this->_is_global() );
203 244 }
204 245
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 +
205 258 private function _is_global() {
206 259 return apply_filters( 'templately_multisite_is_global', false );
207 260 }
208 261
@@ -233,5 +286,5 @@
233 286 */
234 287 public function update_option( $key, $value, $autoload = 'no' ): bool {
235 288 return update_option( $key, $value, $autoload );
236 289 }
237 -}
290 +}