PluginProbe
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO / 1.4.14
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO v1.4.14
1.4.14 1.4.13 1.4.12 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.54 1.3.53 1.3.52 1.3.51 1.3.50 1.3.49 1.3.48 1.3.47 1.3.46 1.3.45 All 177 releases
disco / vendor / inpsyde / wp-context / src / WpContext.php

WpContext.php in Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO 1.4.14, at vendor/inpsyde/wp-context/src/WpContext.php

404 lines 10.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Inpsyde;
6
7 class WpContext implements \JsonSerializable
8 {
9 public const AJAX = 'ajax';
10 public const BACKOFFICE = 'backoffice';
11 public const CLI = 'wpcli';
12 public const CORE = 'core';
13 public const CRON = 'cron';
14 public const FRONTOFFICE = 'frontoffice';
15 public const INSTALLING = 'installing';
16 public const LOGIN = 'login';
17 public const REST = 'rest';
18 public const XML_RPC = 'xml-rpc';
19 public const WP_ACTIVATE = 'wp-activate';
20
21 private const ALL = [
22 self::AJAX,
23 self::BACKOFFICE,
24 self::CLI,
25 self::CORE,
26 self::CRON,
27 self::FRONTOFFICE,
28 self::INSTALLING,
29 self::LOGIN,
30 self::REST,
31 self::XML_RPC,
32 self::WP_ACTIVATE,
33 ];
34
35 /**
36 * @var array
37 */
38 private $data;
39
40 /**
41 * @var array<string, callable>
42 */
43 private $actionCallbacks = [];
44
45 /**
46 * @return WpContext
47 */
48 final public static function new(): WpContext
49 {
50 return new self(array_fill_keys(self::ALL, false));
51 }
52
53 /**
54 * @return WpContext
55 */
56 final public static function determine(): WpContext
57 {
58 /** @psalm-suppress RedundantCondition */
59 $installing = defined('WP_INSTALLING') && WP_INSTALLING;
60 /** @psalm-suppress RedundantCondition */
61 $xmlRpc = defined('XMLRPC_REQUEST') && XMLRPC_REQUEST;
62 $isCore = defined('ABSPATH');
63 $isCli = defined('WP_CLI');
64 $notInstalling = $isCore && !$installing;
65 $isAjax = $notInstalling && wp_doing_ajax();
66 $isAdmin = $notInstalling && is_admin() && !$isAjax;
67 $isCron = $notInstalling && wp_doing_cron();
68 $isWpActivate = $installing && is_multisite() && self::isWpActivateRequest();
69
70 $undetermined = $notInstalling && !$isAdmin && !$isCron && !$isCli && !$xmlRpc && !$isAjax;
71
72 $isRest = $undetermined && static::isRestRequest();
73 $isLogin = $undetermined && !$isRest && static::isLoginRequest();
74
75 // When nothing else matches, we assume it is a front-office request.
76 $isFront = $undetermined && !$isRest && !$isLogin;
77
78 /*
79 * Note that when core is installing **only** `INSTALLING` will be true, not even `CORE`.
80 * This is done to do as less as possible during installation, when most of WP does not act
81 * as expected.
82 */
83
84 $instance = new self(
85 [
86 self::AJAX => $isAjax,
87 self::BACKOFFICE => $isAdmin,
88 self::CLI => $isCli,
89 self::CORE => ($isCore || $xmlRpc) && (!$installing || $isWpActivate),
90 self::CRON => $isCron,
91 self::FRONTOFFICE => $isFront,
92 self::INSTALLING => $installing && !$isWpActivate,
93 self::LOGIN => $isLogin,
94 self::REST => $isRest,
95 self::XML_RPC => $xmlRpc && !$installing,
96 self::WP_ACTIVATE => $isWpActivate,
97 ]
98 );
99
100 $instance->addActionHooks();
101
102 return $instance;
103 }
104
105 /**
106 * @return bool
107 */
108 private static function isRestRequest(): bool
109 {
110 if (
111 (defined('REST_REQUEST') && REST_REQUEST)
112 || !empty($_GET['rest_route']) // phpcs:ignore
113 ) {
114 return true;
115 }
116
117 if (!get_option('permalink_structure')) {
118 return false;
119 }
120
121 /*
122 * This is needed because, if called early, global $wp_rewrite is not defined but required
123 * by get_rest_url(). WP will reuse what we set here, or in worst case will replace, but no
124 * consequences for us in any case.
125 */
126 if (empty($GLOBALS['wp_rewrite'])) {
127 $GLOBALS['wp_rewrite'] = new \WP_Rewrite();
128 }
129
130 $currentPath = trim((string)parse_url((string)add_query_arg([]), PHP_URL_PATH), '/') . '/';
131 $restPath = trim((string)parse_url((string)get_rest_url(), PHP_URL_PATH), '/') . '/';
132
133 return strpos($currentPath, $restPath) === 0;
134 }
135
136 /**
137 * @return bool
138 */
139 private static function isLoginRequest(): bool
140 {
141 /**
142 * New core function with WordPress 6.1
143 * @link https://make.wordpress.org/core/2022/09/11/new-is_login-function-for-determining-if-a-page-is-the-login-screen/
144 */
145 if (function_exists('is_login')) {
146 return is_login() !== false;
147 }
148
149 if (!empty($_REQUEST['interim-login'])) { // phpcs:ignore
150 return true;
151 }
152
153 /**
154 * Fallback and 1:1 copy from is_login() in case, the function is
155 * not available for WP < 6.1.
156 * phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotValidated
157 * phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
158 * phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
159 */
160 $scriptName = $_SERVER['SCRIPT_NAME'] ?? '';
161
162 return false !== stripos(wp_login_url(), $scriptName);
163 }
164
165 /**
166 * @return bool
167 */
168 private static function isWpActivateRequest(): bool
169 {
170 return static::isPageNow('wp-activate.php', network_site_url('wp-activate.php'));
171 }
172
173 /**
174 * @param string $page
175 * @param string $url
176 * @return bool
177 */
178 private static function isPageNow(string $page, string $url): bool
179 {
180 $pageNow = (string)($GLOBALS['pagenow'] ?? '');
181 if ($pageNow && (basename($pageNow) === $page)) {
182 return true;
183 }
184
185 $currentPath = (string)parse_url(add_query_arg([]), PHP_URL_PATH);
186 $targetPath = (string)parse_url($url, PHP_URL_PATH);
187
188 return trim($currentPath, '/') === trim($targetPath, '/');
189 }
190
191 /**
192 * @param array $data
193 */
194 private function __construct(array $data)
195 {
196 $this->data = $data;
197 }
198
199 /**
200 * @param string $context
201 * @return WpContext
202 */
203 final public function force(string $context): WpContext
204 {
205 if (!in_array($context, self::ALL, true)) {
206 throw new \LogicException("'{$context}' is not a valid context.");
207 }
208
209 $this->removeActionHooks();
210
211 $data = array_fill_keys(self::ALL, false);
212 $data[$context] = true;
213 if (!in_array($context, [self::INSTALLING, self::CLI, self::CORE], true)) {
214 $data[self::CORE] = true;
215 }
216
217 $this->data = $data;
218
219 return $this;
220 }
221
222 /**
223 * @return WpContext
224 */
225 final public function withCli(): WpContext
226 {
227 $this->data[self::CLI] = true;
228
229 return $this;
230 }
231
232 /**
233 * @param string $context
234 * @param string ...$contexts
235 * @return bool
236 */
237 final public function is(string $context, string ...$contexts): bool
238 {
239 array_unshift($contexts, $context);
240
241 foreach ($contexts as $context) {
242 if (($this->data[$context] ?? null)) {
243 return true;
244 }
245 }
246
247 return false;
248 }
249
250 /**
251 * @return bool
252 */
253 public function isCore(): bool
254 {
255 return $this->is(self::CORE);
256 }
257
258 /**
259 * @return bool
260 */
261 public function isFrontoffice(): bool
262 {
263 return $this->is(self::FRONTOFFICE);
264 }
265
266 /**
267 * @return bool
268 */
269 public function isBackoffice(): bool
270 {
271 return $this->is(self::BACKOFFICE);
272 }
273
274 /**
275 * @return bool
276 */
277 public function isAjax(): bool
278 {
279 return $this->is(self::AJAX);
280 }
281
282 /**
283 * @return bool
284 */
285 public function isLogin(): bool
286 {
287 return $this->is(self::LOGIN);
288 }
289
290 /**
291 * @return bool
292 */
293 public function isRest(): bool
294 {
295 return $this->is(self::REST);
296 }
297
298 /**
299 * @return bool
300 */
301 public function isCron(): bool
302 {
303 return $this->is(self::CRON);
304 }
305
306 /**
307 * @return bool
308 */
309 public function isWpCli(): bool
310 {
311 return $this->is(self::CLI);
312 }
313
314 /**
315 * @return bool
316 */
317 public function isXmlRpc(): bool
318 {
319 return $this->is(self::XML_RPC);
320 }
321
322 /**
323 * @return bool
324 */
325 public function isInstalling(): bool
326 {
327 return $this->is(self::INSTALLING);
328 }
329
330 /**
331 * @return bool
332 */
333 public function isWpActivate(): bool
334 {
335 return $this->is(self::WP_ACTIVATE);
336 }
337
338 /**
339 * @return array
340 */
341 public function jsonSerialize(): array
342 {
343 return $this->data;
344 }
345
346 /**
347 * When context is determined very early we do our best to understand some context like
348 * login, rest and front-office even if WordPress normally would require a later hook.
349 * When that later hook happen, we change what we have determined, leveraging the more
350 * "core-compliant" approach.
351 *
352 * @return void
353 */
354 private function addActionHooks(): void
355 {
356 $this->actionCallbacks = [
357 'login_init' => function (): void {
358 $this->resetAndForce(self::LOGIN);
359 },
360 'rest_api_init' => function (): void {
361 $this->resetAndForce(self::REST);
362 },
363 'activate_header' => function (): void {
364 $this->resetAndForce(self::WP_ACTIVATE);
365 },
366 'template_redirect' => function (): void {
367 $this->resetAndForce(self::FRONTOFFICE);
368 },
369 'current_screen' => function (\WP_Screen $screen): void {
370 $screen->in_admin() and $this->resetAndForce(self::BACKOFFICE);
371 },
372 ];
373
374 foreach ($this->actionCallbacks as $action => $callback) {
375 add_action($action, $callback, PHP_INT_MIN);
376 }
377 }
378
379 /**
380 * When "force" is called on an instance created via `determine()` we need to remove added hooks
381 * or what we are forcing might be overridden.
382 *
383 * @return void
384 */
385 private function removeActionHooks(): void
386 {
387 foreach ($this->actionCallbacks as $action => $callback) {
388 remove_action($action, $callback, PHP_INT_MIN);
389 }
390 $this->actionCallbacks = [];
391 }
392
393 /**
394 * @param string $context
395 * @return void
396 */
397 private function resetAndForce(string $context): void
398 {
399 $cli = $this->isWpCli();
400 $this->force($context);
401 $cli and $this->withCli();
402 }
403 }
404