PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backup, Restore, Migration & Clone / 3.8.0
WP STAGING – WordPress Backup, Restore, Migration & Clone v3.8.0
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 / FeatureDetection.php
wp-staging / Framework / BackgroundProcessing Last commit date
Exceptions 5 years ago Action.php 2 years ago BackgroundProcessingServiceProvider.php 2 years ago Demo.php 4 years ago FeatureDetection.php 2 years ago Queue.php 2 years ago QueueActionAware.php 4 years ago QueueProcessor.php 2 years ago WithQueueAwareness.php 3 years ago
FeatureDetection.php
220 lines
1 <?php
2
3 /**
4 * Provides feature detection for the Queue system.
5 *
6 * @package WPStaging\Framework\BackgroundProcessing
7 */
8
9 namespace WPStaging\Framework\BackgroundProcessing;
10
11 use WP_Error;
12 use WPStaging\Framework\Adapter\WpAdapter;
13
14 use function WPStaging\functions\debug_log;
15
16 /**
17 * Class FeatureDetection
18 *
19 * @package WPStaging\Framework\BackgroundProcessing
20 */
21 class FeatureDetection
22 {
23 const AJAX_TEST_ACTION = 'wpstg_ajax_test_action';
24 const AJAX_OPTION_NAME = 'wpstg_q_feature_detection_ajax_available';
25 const AJAX_REQUEST_QUERY_VAR = 'wpstg_q_ajax_check';
26
27 /**
28 * A cache property to store the result of the AJAX support check.
29 *
30 * @var bool
31 */
32 protected $isAjaxAvailableCache;
33
34 /**
35 * Returns whether the AJAX based queue processing system is available or not.
36 *
37 * @param bool $showAdminNotice Whether to show an admin notice on missing support or not.
38 *
39 * @return bool Whether the AJAX based queue processing system is available or not.
40 */
41 public function isAjaxAvailable($showAdminNotice = true)
42 {
43 //debug_log('isAjaxAvailable start');
44 if ($this->isAjaxAvailableCache === null) {
45 // Run this check only on Admin UI and on PHP initial state.
46 // TODO: inject WpAdapter using DI
47 $notRightContext = wp_installing() || (defined('REST_REQUEST') && REST_REQUEST) || (new WpAdapter())->doingAjax()
48 || wp_doing_cron() || !is_admin();
49
50 if ($notRightContext) {
51 // Default to say that it's supported if we cannot exclude it.
52 debug_log(sprintf(
53 "isAjaxAvailable not right context: Is WP Installing? %s - Is Rest? %s - Is Ajax? %s - Is Cron? %s - Is admin? %s",
54 wp_installing() ? 'true' : 'false',
55 (defined('REST_REQUEST') && REST_REQUEST) ? 'true' : 'false',
56 (new WpAdapter())->doingAjax() ? 'true' : 'false',
57 wp_doing_cron() ? 'true' : 'false',
58 is_admin() ? 'true' : 'false'
59 ), 'info', false);
60 return true;
61 }
62
63 $availableOptionValue = get_option(self::AJAX_OPTION_NAME, null);
64
65 if (!in_array($availableOptionValue, ['y', 'n'], true)) {
66 $available = $this->runAjaxFeatureTest();
67 $availableOptionValue = $available ? 'y' : 'n';
68 update_option(self::AJAX_OPTION_NAME, $availableOptionValue, false);
69 }
70
71 $this->isAjaxAvailableCache = $availableOptionValue === 'y';
72 }
73
74 if (!$this->isAjaxAvailableCache && $showAdminNotice) {
75 add_action('wpstg.admin_notices', [$this, 'ajaxSupportMissingAdminNotice']);
76 }
77
78 //debug_log('isAjaxAvailable end. Result: ' . $this->isAjaxAvailableCache);
79
80 return $this->isAjaxAvailableCache;
81 }
82
83 /**
84 * Runs the AJAX support feature detection test.
85 *
86 * This method will fire a non-blocking POST request
87 * to the `admin-ajax` endpoint.
88 * In response, the `updateAjaxTestOption` will update
89 * the flag option value and set it to `y` or not set it at all.
90 * This method will wait for some time for its counter-part, the
91 * `updateAjaxTestOption` running in the other request, to update
92 * the option. If the time runs out and the option is not there,
93 * the we know AJAX is either not working or not reliable enough.
94 * The method uses an option, and not a transient, as flag value to
95 * be able to force re-fetch it from the database.
96 *
97 * @return bool Whether the AJAX-based system is supported or not.
98 *
99 * @see FeatureDetection::updateAjaxTestOption()
100 */
101 public function runAjaxFeatureTest()
102 {
103 // Start from a clean state.
104 debug_log('Starting from a clean state...', 'info', false);
105 delete_option(self::AJAX_OPTION_NAME);
106
107 $ajaxUrl = add_query_arg([
108 'action' => self::AJAX_TEST_ACTION,
109 '_ajax_nonce' => wp_create_nonce(self::AJAX_TEST_ACTION)
110 ], admin_url('admin-ajax.php'));
111
112 $hash = md5(uniqid(__CLASS__, true));
113
114 debug_log('Sending request to: ' . $ajaxUrl, 'info', false);
115
116 $response = wp_remote_post(esc_url_raw($ajaxUrl), [
117 'headers' => [
118 'X-WPSTG-Request' => self::AJAX_TEST_ACTION
119 ],
120 'blocking' => false,
121 'timeout' => 0.01,
122 'cookies' => !empty($_COOKIE) ? $_COOKIE : [],
123 'sslverify' => apply_filters('https_local_ssl_verify', false),
124 'body' => [self::AJAX_OPTION_NAME => $hash],
125 ]);
126
127 debug_log(wp_json_encode($response), 'info', false);
128
129 if ($response instanceof WP_Error) {
130 return false;
131 }
132
133 $test = static function () {
134 // Run a direct query to force the re-fetch and not hit the cache.
135 global $wpdb;
136 $fetched = $wpdb->get_var(
137 $wpdb->prepare(
138 "SELECT `option_value` from `{$wpdb->options}` WHERE `option_name` = '%s' AND `option_value` = 'y'",
139 self::AJAX_OPTION_NAME
140 )
141 );
142
143 debug_log('Fetched is equal to: ' . $fetched, 'info', false);
144 debug_log('Fetched is equal to (get_option): ' . get_option(self::AJAX_OPTION_NAME), 'info', false);
145
146 return $fetched === 'y';
147 };
148
149 $waited = 0;
150 $waitStep = .5 * 1e6; // 0.5 second
151 $timeout = 10 * 1e6; // 10 seconds
152
153 do {
154 debug_log('runAjaxFeatureTest waited ' . number_format($waited / 1e6, 1) . ' seconds...', 'info', false);
155 $waited += $waitStep;
156 usleep($waitStep);
157
158 if ($test()) {
159 // Look no further, it worked.
160 debug_log('runAjaxFeatureTest worked', 'info', false);
161 return true;
162 }
163 } while ($waited <= $timeout);
164
165 // We waited enough: either the AJAX system is not available or is not reliable.
166 debug_log('runAjaxFeatureTest did not work', 'info', false);
167 return false;
168 }
169
170 /**
171 * Writes `y` to the feature detection option.
172 *
173 * This method will be called in response to the AJAX request
174 * fired by the `runAjaxFeatureTest` method.
175 * That method will wait, in its PHP process, for this method to
176 * udpate the option value and deem the AJAX support as "working".
177 *
178 * @return void The method does not return any value and will have the
179 * side effect of updating the option.
180 *
181 * @see FeatureDetection::runAjaxFeatureTest()
182 */
183 public function updateAjaxTestOption()
184 {
185 debug_log('Running updateAjaxTestOption', 'info', false);
186 check_ajax_referer(self::AJAX_TEST_ACTION);
187
188 if (!update_option(self::AJAX_OPTION_NAME, 'y', false)) {
189 debug_log('updateAjaxTestOption update_option returned false', 'info', false);
190 }
191
192 debug_log('Complete updateAjaxTestOption. New value: ' . get_option(self::AJAX_OPTION_NAME), 'info', false);
193 }
194
195 /**
196 * Displays a dismissible admin notice to let the user know the BG Processing system will not
197 * perform at its best due to lack of AJAX support.
198 *
199 * @return void The method will have the side effect of echoing HTML to the page.
200 */
201 public function ajaxSupportMissingAdminNotice()
202 {
203 $message = __(
204 'WP STAGING Background Processing system cannot use AJAX: this will prevent it from performing at its best.',
205 'wp-staging'
206 );
207 $checkLink = sprintf(
208 '<a href="%s">%s</a>',
209 esc_url(admin_url('/?' . self::AJAX_REQUEST_QUERY_VAR . '=1')),
210 __('Click here to check again now.', 'wp-staging')
211 );
212 ?>
213 <div class="notice notice-warning is-dismissible wpstg__notice wpstg__notice--warning">
214 <p><?php echo wp_kses_post($message); ?></p>
215 <p><?php echo wp_kses_post($checkLink); ?></p>
216 </div>
217 <?php
218 }
219 }
220