PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.11.0
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.11.0
4.11.0 4.10.0 4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Framework / BackgroundProcessing / WithQueueAwareness.php
wp-staging / Framework / BackgroundProcessing Last commit date
Exceptions 1 day ago Job 1 day ago Action.php 1 day ago BackgroundProcessingServiceProvider.php 1 day ago Demo.php 1 day ago FeatureDetection.php 1 day ago Queue.php 1 day ago QueueActionAware.php 1 day ago QueueProcessor.php 1 day ago WithQueueAwareness.php 1 day ago
WithQueueAwareness.php
298 lines
1 <?php
2
3
4
5
6
7
8
9 namespace WPStaging\Framework\BackgroundProcessing;
10
11 use WP_Error;
12 use WPStaging\Framework\Facades\Hooks;
13 use WPStaging\Framework\Network\HttpBasicAuth;
14
15 use function WPStaging\functions\debug_log;
16
17
18
19
20
21
22 trait WithQueueAwareness
23 {
24 use HttpBasicAuth;
25
26
27
28
29
30
31 private $didFireAjaxAction = false;
32
33
34
35
36
37
38
39 public static function getDefaultPriority()
40 {
41 return 0;
42 }
43
44
45
46
47
48
49
50
51
52
53
54
55 public function fireAjaxAction($bodyData = null)
56 {
57 if ($this->didFireAjaxAction) {
58
59 return false;
60 }
61
62 $ajaxUrl = add_query_arg([
63 'action' => QueueProcessor::ACTION_QUEUE_PROCESS,
64 '_ajax_nonce' => wp_create_nonce(QueueProcessor::ACTION_QUEUE_PROCESS),
65 ], admin_url('admin-ajax.php'));
66
67 $useGetMethod = false;
68 $requestSent = false;
69
70 $useGetMethod = get_site_transient(QueueProcessor::TRANSIENT_REQUEST_GET_METHOD);
71
72 if ($useGetMethod === false) {
73
74 $useGetMethod = $this->checkGetRequestNeededForQueue($ajaxUrl, $bodyData);
75
76 $requestSent = !$useGetMethod;
77
78 set_site_transient(QueueProcessor::TRANSIENT_REQUEST_GET_METHOD, $useGetMethod ? 'Yes' : 'No', HOUR_IN_SECONDS);
79 debug_log('[WPSTG Fire Ajax] GET method is ' . ($useGetMethod ? 'needed' : 'not needed') . ' for Queue AJAX request.', 'info', false);
80 } else {
81 $useGetMethod = $useGetMethod === 'Yes';
82 }
83
84
85 if ($requestSent) {
86 $this->didFireAjaxAction = true;
87
88 Hooks::doAction('wpstg_queue_fire_ajax_request', $this);
89
90 return true;
91 }
92
93
94 $useGetMethod = Hooks::applyFilters(QueueProcessor::FILTER_REQUEST_FORCE_GET_METHOD, $useGetMethod);
95
96 $blocking = $this->useBlockingRequest();
97 debug_log('[WPSTG Fire Ajax] Firing AJAX request to process Queue actions. GET method: ' . ($useGetMethod ? 'Yes' : 'No'), 'debug', false);
98
99 $response = wp_remote_request(esc_url_raw($ajaxUrl), [
100 'headers' => array_merge(
101 ['X-WPSTG-Request' => QueueProcessor::ACTION_QUEUE_PROCESS],
102 $this->getHttpAuthHeaders()
103 ),
104 'method' => $useGetMethod ? 'GET' : 'POST',
105 'blocking' => $blocking,
106 'timeout' => $blocking ? 30 : 0.01,
107 'cookies' => $this->getLoginRelatedCookies(),
108 'sslverify' => apply_filters(FeatureDetection::FILTER_HTTPS_LOCAL_SSL_VERIFY, false),
109 'body' => $this->normalizeAjaxRequestBody($bodyData),
110 ]);
111
112
113
114
115
116
117 if ($response instanceof WP_Error) {
118 \WPStaging\functions\debug_log(json_encode([
119 'root' => 'Queue processing admin-ajax request failed.',
120 'class' => get_class($this),
121 'code' => $response->get_error_code(),
122 'message' => $response->get_error_message(),
123 'data' => $response->get_error_data(),
124 'blocking' => $blocking,
125 'method' => $useGetMethod ? 'GET' : 'POST',
126 ], JSON_PRETTY_PRINT));
127
128 delete_site_transient(QueueProcessor::TRANSIENT_REQUEST_GET_METHOD);
129 $this->recordFireFailure();
130
131 return false;
132 }
133
134 if ($blocking && is_array($response)) {
135 $code = isset($response['response']['code']) ? (int)$response['response']['code'] : 0;
136 if ($code < 200 || $code >= 300) {
137 $failures = (int)get_site_transient(QueueProcessor::TRANSIENT_FIRE_FAILURE_COUNT) + 1;
138 debug_log('[BG Queue] fire failed: HTTP code=' . $code . ' (failure ' . $failures . ')', 'info', false);
139 delete_site_transient(QueueProcessor::TRANSIENT_REQUEST_GET_METHOD);
140 $this->recordFireFailure();
141 return false;
142 }
143
144 if ((int)get_site_transient(QueueProcessor::TRANSIENT_FIRE_FAILURE_COUNT) > 0) {
145 debug_log('[BG Queue] fire mode -> non-blocking (loopback healthy, code=' . $code . ')', 'info', false);
146 delete_site_transient(QueueProcessor::TRANSIENT_FIRE_FAILURE_COUNT);
147 }
148 }
149
150
151 set_site_transient(QueueProcessor::TRANSIENT_LAST_FIRE_TIMESTAMP, time(), QueueProcessor::TRANSIENT_FIRE_STATE_TTL);
152
153 $this->didFireAjaxAction = true;
154
155
156
157
158
159
160
161
162 do_action('wpstg_queue_fire_ajax_request', $this);
163
164 return true;
165 }
166
167
168
169
170
171
172
173
174
175
176
177 private function normalizeAjaxRequestBody($bodyData)
178 {
179 $normalized = (array)$bodyData;
180
181 $normalized['_referer'] = __CLASS__;
182
183 return $normalized;
184 }
185
186
187
188
189
190
191 private function checkGetRequestNeededForQueue(string $ajaxUrl, $bodyData = null): bool
192 {
193
194 $response = wp_remote_post(esc_url_raw($ajaxUrl), [
195 'headers' => array_merge(
196 ['X-WPSTG-Request' => QueueProcessor::ACTION_QUEUE_PROCESS],
197 $this->getHttpAuthHeaders()
198 ),
199 'blocking' => true,
200 'timeout' => 5,
201 'cookies' => $this->getLoginRelatedCookies(),
202 'sslverify' => apply_filters(FeatureDetection::FILTER_HTTPS_LOCAL_SSL_VERIFY, false),
203 'body' => $this->normalizeAjaxRequestBody($bodyData),
204 ]);
205
206 if ($response instanceof WP_Error) {
207 debug_log('[WPSTG Fire Ajax] checkGetRequestNeededForQueue POST failed: code=' . $response->get_error_code() . ' message=' . $response->get_error_message(), 'debug', false);
208 } elseif (is_array($response) && isset($response['response']['code'])) {
209 debug_log('[WPSTG Fire Ajax] checkGetRequestNeededForQueue POST response code=' . $response['response']['code'], 'debug', false);
210 }
211
212
213 if ($response instanceof WP_Error) {
214 return true;
215 }
216
217 if (!is_array($response)) {
218 return false;
219 }
220
221
222 if (
223 array_key_exists('response', $response) &&
224 array_key_exists('code', $response['response']) &&
225 $response['response']['code'] === 404
226 ) {
227 return true;
228 }
229
230 return false;
231 }
232
233 private function useBlockingRequest(): bool
234 {
235 if (defined('DOING_AJAX') && DOING_AJAX) {
236 return false;
237 }
238
239 if (function_exists('wp_get_environment_type') && wp_get_environment_type() === 'local') {
240 return true;
241 }
242
243 return (int)get_site_transient(QueueProcessor::TRANSIENT_FIRE_FAILURE_COUNT) >= QueueProcessor::ADAPTIVE_BLOCKING_THRESHOLD;
244 }
245
246
247
248
249 private function recordFireFailure()
250 {
251 $failures = (int)get_site_transient(QueueProcessor::TRANSIENT_FIRE_FAILURE_COUNT);
252 if ($failures >= 10) {
253 return;
254 }
255
256 $newFailures = $failures + 1;
257 set_site_transient(QueueProcessor::TRANSIENT_FIRE_FAILURE_COUNT, $newFailures, QueueProcessor::TRANSIENT_FIRE_STATE_TTL);
258
259 if ($failures < QueueProcessor::ADAPTIVE_BLOCKING_THRESHOLD && $newFailures >= QueueProcessor::ADAPTIVE_BLOCKING_THRESHOLD) {
260 debug_log('[BG Queue] fire mode -> blocking (consecutive silent failures=' . $newFailures . ')', 'info', false);
261 }
262 }
263
264
265
266
267
268
269
270
271
272
273 private function getLoginRelatedCookies(): array
274 {
275 if (empty($_COOKIE) || !is_array($_COOKIE)) {
276 return [];
277 }
278
279 $allowed = [];
280 foreach ($_COOKIE as $name => $value) {
281 if (!is_string($name)) {
282 continue;
283 }
284
285
286 if (!preg_match('/^wordpress_(?:logged_in_|sec_)?[a-f0-9]{32}$/', $name)) {
287 continue;
288 }
289
290 if (is_scalar($value)) {
291 $allowed[$name] = (string)$value;
292 }
293 }
294
295 return $allowed;
296 }
297 }
298