PluginProbe
YayMail – WooCommerce Email Customizer / 4.4.2
YayMail – WooCommerce Email Customizer v4.4.2
4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 trunk 1.9.6 2.1.4 2.1.5 3.2.2 3.2.6 3.2.7.1 3.2.8.1 3.2.9 3.3 3.3.1 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9 3.4 3.4.1 All 57 releases
yaymail / vendor-prefixed / src / License / EDD_SL_Plugin_Updater.php

EDD_SL_Plugin_Updater.php in YayMail – WooCommerce Email Customizer 4.4.2, at vendor-prefixed/src/License/EDD_SL_Plugin_Updater.php

463 lines 19.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace YayMailScoped\YayCommerce\AdminShell\License;
4
5 // Exit if accessed directly
6 if (!\defined('ABSPATH')) {
7 exit;
8 }
9 /**
10 * EDD SL Plugin Updater
11 *
12 * @package EDD_SL_Plugin_Updater
13 * @internal
14 */
15 class EDD_SL_Plugin_Updater
16 {
17 private $api_url = '';
18 private $api_data = [];
19 private $name = '';
20 private $slug = '';
21 private $version = '';
22 private $wp_override = \false;
23 private $beta = \false;
24 private $cache_key = '';
25 private $health_check_timeout = 5;
26 /**
27 * Class constructor.
28 *
29 * @uses plugin_basename()
30 * @uses hook()
31 *
32 * @param string $_api_url The URL pointing to the custom API endpoint.
33 * @param string $_plugin_file Path to the plugin file.
34 * @param array $_api_data Optional data to send with API calls.
35 */
36 public function __construct($_api_url, $_plugin_file, $_api_data = null)
37 {
38 global $edd_plugin_data;
39 $this->api_url = trailingslashit($_api_url);
40 $this->api_data = $_api_data;
41 $this->name = \plugin_basename($_plugin_file);
42 $this->slug = \basename($_plugin_file, '.php');
43 $this->version = $_api_data['version'];
44 $this->wp_override = isset($_api_data['wp_override']) ? (bool) $_api_data['wp_override'] : \false;
45 $this->beta = !empty($this->api_data['beta']) ? \true : \false;
46 $this->cache_key = 'edd_sl_' . \md5(\serialize($this->slug . $this->api_data['license'] . $this->beta));
47 $edd_plugin_data[$this->slug] = $this->api_data;
48 /**
49 * Fires after the $edd_plugin_data is setup.
50 *
51 * @since 3.4.5
52 *
53 * @param array $edd_plugin_data Array of EDD SL plugin data.
54 */
55 \do_action('post_edd_sl_plugin_updater_setup', $edd_plugin_data);
56 // Set up hooks.
57 $this->init();
58 }
59 /**
60 * Set up WordPress filters to hook into WP's update process.
61 *
62 * @uses add_filter()
63 *
64 * @return void
65 */
66 public function init()
67 {
68 add_filter('pre_set_site_transient_update_plugins', [$this, 'check_update']);
69 add_filter('plugins_api', [$this, 'plugins_api_filter'], 10, 3);
70 // add_action( 'load-plugins.php', array( $this, 'remove_default_after_row' ), 100 );
71 add_action('after_plugin_row_' . $this->name, [$this, 'show_update_notification'], 11, 2);
72 add_action('admin_init', [$this, 'show_changelog']);
73 }
74 public function remove_default_after_row()
75 {
76 remove_action('after_plugin_row_' . $this->name, 'wp_plugin_update_row', 10, 2);
77 }
78 /**
79 * Check for Updates at the defined API endpoint and modify the update array.
80 *
81 * This function dives into the update API just when WordPress creates its update array,
82 * then adds a custom API call and injects the custom plugin data retrieved from the API.
83 * It is reassembled from parts of the native WordPress plugin update code.
84 * See wp-includes/update.php line 121 for the original wp_update_plugins() function.
85 *
86 * @uses api_request()
87 *
88 * @param array $_transient_data Update array build by WordPress.
89 * @return array Modified update array with custom plugin data.
90 */
91 public function check_update($_transient_data)
92 {
93 global $pagenow;
94 if (!\is_object($_transient_data)) {
95 $_transient_data = new \stdClass();
96 }
97 if ('plugins.php' == $pagenow) {
98 return $_transient_data;
99 }
100 if (!empty($_transient_data->response) && !empty($_transient_data->response[$this->name]) && \false === $this->wp_override) {
101 return $_transient_data;
102 }
103 $current = $this->get_repo_api_data();
104 if (\false !== $current && \is_object($current) && isset($current->new_version)) {
105 if (\version_compare($this->version, $current->new_version, '<')) {
106 $_transient_data->response[$this->name] = $current;
107 } else {
108 // Populating the no_update information is required to support auto-updates in WordPress 5.5.
109 $_transient_data->no_update[$this->name] = $current;
110 }
111 }
112 $_transient_data->last_checked = \time();
113 $_transient_data->checked[$this->name] = $this->version;
114 return $_transient_data;
115 }
116 /**
117 * Get repo API data from store.
118 * Save to cache.
119 *
120 * @return \stdClass
121 */
122 public function get_repo_api_data()
123 {
124 $version_info = $this->get_cached_version_info();
125 if (\false === $version_info) {
126 $version_info = $this->api_request('plugin_latest_version', ['slug' => $this->slug, 'beta' => $this->beta]);
127 if (!$version_info) {
128 return \false;
129 }
130 // This is required for your plugin to support auto-updates in WordPress 5.5.
131 $version_info->plugin = $this->name;
132 $version_info->id = $this->name;
133 $this->set_version_info_cache($version_info);
134 }
135 return $version_info;
136 }
137 public function yaycommerce_update_transient()
138 {
139 if (is_network_admin()) {
140 return;
141 }
142 if (!\current_user_can('update_plugins')) {
143 return;
144 }
145 // Remove our filter on the site transient
146 remove_filter('pre_set_site_transient_update_plugins', [$this, 'check_update'], 10);
147 $update_cache = get_site_transient('update_plugins');
148 $update_cache = \is_object($update_cache) ? $update_cache : new \stdClass();
149 if (empty($update_cache->response) || empty($update_cache->response[$this->name])) {
150 $version_info = $this->get_repo_api_data();
151 if (\false === $version_info) {
152 $version_info = $this->api_request('plugin_latest_version', ['slug' => $this->slug, 'beta' => $this->beta]);
153 // Since we disabled our filter for the transient, we aren't running our object conversion on banners, sections, or icons. Do this now:
154 if (isset($version_info->banners) && !\is_array($version_info->banners)) {
155 $version_info->banners = $this->convert_object_to_array($version_info->banners);
156 }
157 if (isset($version_info->sections) && !\is_array($version_info->sections)) {
158 $version_info->sections = $this->convert_object_to_array($version_info->sections);
159 }
160 if (isset($version_info->icons) && !\is_array($version_info->icons)) {
161 $version_info->icons = $this->convert_object_to_array($version_info->icons);
162 }
163 if (isset($version_info->contributors) && !\is_array($version_info->contributors)) {
164 $version_info->contributors = $this->convert_object_to_array($version_info->contributors);
165 }
166 $this->set_version_info_cache($version_info);
167 }
168 //end if
169 if (!\is_object($version_info)) {
170 return;
171 }
172 $has_update = \false;
173 if (\version_compare($this->version, $version_info->new_version, '<')) {
174 $update_cache->response[$this->name] = $version_info;
175 $has_update = \true;
176 } else {
177 $update_cache->no_update[$this->name] = $version_info;
178 }
179 $update_cache->last_checked = \time();
180 $update_cache->checked[$this->name] = $this->version;
181 if ($has_update) {
182 set_site_transient('update_plugins', $update_cache);
183 }
184 } else {
185 $version_info = $update_cache->response[$this->name];
186 }
187 //end if
188 }
189 /**
190 * Show update nofication row -- needed for multisite subsites, because WP won't tell you otherwise!
191 *
192 * @param string $file
193 * @param array $plugin
194 */
195 public function show_update_notification($file, $plugin)
196 {
197 $output = '';
198 if ($this->name != $file) {
199 return $output;
200 }
201 $this->yaycommerce_update_transient();
202 $update_cache = get_site_transient('update_plugins');
203 $update_cache = \is_object($update_cache) ? $update_cache : new \stdClass();
204 $version_info = isset($update_cache->response[$this->name]) ? $update_cache->response[$this->name] : '';
205 // Restore our filter
206 add_filter('pre_set_site_transient_update_plugins', [$this, 'check_update']);
207 \do_action("in_plugin_update_message-{$file}", $plugin, $version_info);
208 echo wp_kses_post($output);
209 }
210 /**
211 * Updates information on the "View version x.x details" page with custom data.
212 *
213 * @uses api_request()
214 *
215 * @param mixed $_data
216 * @param string $_action
217 * @param object $_args
218 * @return object $_data
219 */
220 public function plugins_api_filter($_data, $_action = '', $_args = null)
221 {
222 if ('plugin_information' != $_action) {
223 return $_data;
224 }
225 if (!isset($_args->slug) || $_args->slug != $this->slug) {
226 return $_data;
227 }
228 $to_send = ['slug' => $this->slug, 'is_ssl' => is_ssl(), 'fields' => ['banners' => [], 'reviews' => \false, 'icons' => []]];
229 // Get the transient where we store the api request for this plugin for 24 hours
230 $edd_api_request_transient = $this->get_cached_version_info();
231 // If we have no transient-saved value, run the API, set a fresh transient with the API value, and return that value too right now.
232 if (empty($edd_api_request_transient)) {
233 $api_response = $this->api_request('plugin_information', $to_send);
234 // Expires in 3 hours
235 $this->set_version_info_cache($api_response);
236 if (\false !== $api_response) {
237 $_data = $api_response;
238 }
239 } else {
240 $_data = $edd_api_request_transient;
241 }
242 // Convert sections into an associative array, since we're getting an object, but Core expects an array.
243 if (isset($_data->sections) && !\is_array($_data->sections)) {
244 $_data->sections = $this->convert_object_to_array($_data->sections);
245 }
246 // Convert banners into an associative array, since we're getting an object, but Core expects an array.
247 if (isset($_data->banners) && !\is_array($_data->banners)) {
248 $_data->banners = $this->convert_object_to_array($_data->banners);
249 }
250 // Convert icons into an associative array, since we're getting an object, but Core expects an array.
251 if (isset($_data->icons) && !\is_array($_data->icons)) {
252 $_data->icons = $this->convert_object_to_array($_data->icons);
253 }
254 // Convert contributors into an associative array, since we're getting an object, but Core expects an array.
255 if (isset($_data->contributors) && !\is_array($_data->contributors)) {
256 $_data->contributors = $this->convert_object_to_array($_data->contributors);
257 }
258 if (!isset($_data->plugin)) {
259 $_data->plugin = $this->name;
260 }
261 return $_data;
262 }
263 /**
264 * Convert some objects to arrays when injecting data into the update API
265 *
266 * Some data like sections, banners, and icons are expected to be an associative array, however due to the JSON
267 * decoding, they are objects. This method allows us to pass in the object and return an associative array.
268 *
269 * @since 3.6.5
270 *
271 * @param stdClass $data
272 *
273 * @return array
274 */
275 private function convert_object_to_array($data)
276 {
277 $new_data = [];
278 foreach ($data as $key => $value) {
279 $new_data[$key] = \is_object($value) ? $this->convert_object_to_array($value) : $value;
280 }
281 return $new_data;
282 }
283 /**
284 * Disable SSL verification in order to prevent download update failures
285 *
286 * @param array $args
287 * @param string $url
288 * @return object $array
289 */
290 public function http_request_args($args, $url)
291 {
292 $verify_ssl = $this->verify_ssl();
293 if (\strpos($url, 'https://') !== \false && \strpos($url, 'edd_action=package_download')) {
294 $args['sslverify'] = $verify_ssl;
295 }
296 return $args;
297 }
298 /**
299 * Calls the API and, if successfull, returns the object delivered by the API.
300 *
301 * @uses get_bloginfo()
302 * @uses wp_remote_post()
303 * @uses is_wp_error()
304 *
305 * @param string $_action The requested action.
306 * @param array $_data Parameters for the API action.
307 * @return false|object
308 */
309 private function api_request($_action, $_data)
310 {
311 global $wp_version, $edd_plugin_url_available;
312 $verify_ssl = $this->verify_ssl();
313 // Do a quick status check on this domain if we haven't already checked it.
314 $store_hash = \md5($this->api_url);
315 if (!\is_array($edd_plugin_url_available) || !isset($edd_plugin_url_available[$store_hash])) {
316 $test_url_parts = \parse_url($this->api_url);
317 $scheme = !empty($test_url_parts['scheme']) ? $test_url_parts['scheme'] : 'http';
318 $host = !empty($test_url_parts['host']) ? $test_url_parts['host'] : '';
319 $port = !empty($test_url_parts['port']) ? ':' . $test_url_parts['port'] : '';
320 if (empty($host)) {
321 $edd_plugin_url_available[$store_hash] = \false;
322 } else {
323 $test_url = $scheme . '://' . $host . $port;
324 $response = wp_remote_get($test_url, ['timeout' => $this->health_check_timeout, 'sslverify' => $verify_ssl]);
325 $edd_plugin_url_available[$store_hash] = is_wp_error($response) ? \false : \true;
326 }
327 }
328 //end if
329 if (\false === $edd_plugin_url_available[$store_hash]) {
330 return \false;
331 }
332 $data = \array_merge($this->api_data, $_data);
333 if ($data['slug'] != $this->slug) {
334 return \false;
335 }
336 if (trailingslashit(\home_url()) == $this->api_url) {
337 return \false;
338 // Don't allow a plugin to ping itself
339 }
340 $api_params = ['edd_action' => 'get_version', 'license' => !empty($data['license']) ? $data['license'] : '', 'item_name' => isset($data['item_name']) ? $data['item_name'] : \false, 'item_id' => isset($data['item_id']) ? $data['item_id'] : \false, 'version' => isset($data['version']) ? $data['version'] : \false, 'slug' => $data['slug'], 'author' => $data['author'], 'url' => \home_url(), 'beta' => !empty($data['beta'])];
341 $request = wp_remote_post($this->api_url, ['timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params]);
342 if (!is_wp_error($request)) {
343 $request = \json_decode(wp_remote_retrieve_body($request));
344 }
345 if ($request && isset($request->sections)) {
346 $request->sections = maybe_unserialize($request->sections);
347 } else {
348 $request = \false;
349 }
350 if ($request && isset($request->banners)) {
351 $request->banners = maybe_unserialize($request->banners);
352 }
353 if ($request && isset($request->icons)) {
354 $request->icons = maybe_unserialize($request->icons);
355 }
356 if (!empty($request->sections)) {
357 foreach ($request->sections as $key => $section) {
358 $request->{$key} = (array) $section;
359 }
360 }
361 return $request;
362 }
363 /**
364 * If available, show the changelog for sites in a multisite install.
365 */
366 public function show_changelog()
367 {
368 global $edd_plugin_data;
369 if (empty($_REQUEST['edd_sl_action']) || 'view_plugin_changelog' != $_REQUEST['edd_sl_action']) {
370 return;
371 }
372 if (empty($_REQUEST['plugin'])) {
373 return;
374 }
375 if (empty($_REQUEST['slug'])) {
376 return;
377 }
378 if (!\current_user_can('update_plugins')) {
379 wp_die(\esc_html__('You do not have permission to install plugin updates', 'easy-digital-downloads'), \esc_html__('Error', 'easy-digital-downloads'), ['response' => 403]);
380 }
381 $data = $edd_plugin_data[\sanitize_text_field($_REQUEST['slug'])];
382 $version_info = $this->get_cached_version_info();
383 if (\false === $version_info) {
384 $api_params = ['edd_action' => 'get_version', 'item_name' => isset($data['item_name']) ? $data['item_name'] : \false, 'item_id' => isset($data['item_id']) ? $data['item_id'] : \false, 'slug' => \sanitize_text_field($_REQUEST['slug']), 'author' => $data['author'], 'url' => \home_url(), 'beta' => !empty($data['beta'])];
385 $verify_ssl = $this->verify_ssl();
386 $request = wp_remote_post($this->api_url, ['timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params]);
387 if (!is_wp_error($request)) {
388 $version_info = \json_decode(wp_remote_retrieve_body($request));
389 }
390 if (!empty($version_info) && isset($version_info->sections)) {
391 $version_info->sections = maybe_unserialize($version_info->sections);
392 } else {
393 $version_info = \false;
394 }
395 if (!empty($version_info)) {
396 foreach ($version_info->sections as $key => $section) {
397 $version_info->{$key} = (array) $section;
398 }
399 }
400 $this->set_version_info_cache($version_info);
401 // Delete the unneeded option
402 delete_option(\md5('edd_plugin_' . \sanitize_key($_REQUEST['plugin']) . '_' . $this->beta . '_version_info'));
403 }
404 //end if
405 if (isset($version_info->sections)) {
406 $sections = $this->convert_object_to_array($version_info->sections);
407 if (!empty($sections['changelog'])) {
408 echo '<div style="background:#fff;padding:10px;">' . wp_kses_post($sections['changelog']) . '</div>';
409 }
410 }
411 exit;
412 }
413 /**
414 * Gets the plugin's cached version information from the database.
415 *
416 * @param string $cache_key
417 * @return boolean|string
418 */
419 public function get_cached_version_info($cache_key = '')
420 {
421 if (empty($cache_key)) {
422 $cache_key = $this->cache_key;
423 }
424 $cache = get_option($cache_key);
425 if (empty($cache['timeout']) || \time() > $cache['timeout']) {
426 return \false;
427 // Cache is expired
428 }
429 // We need to turn the icons into an array, thanks to WP Core forcing these into an object at some point.
430 $cache['value'] = \json_decode($cache['value']);
431 if (!empty($cache['value']->icons)) {
432 $cache['value']->icons = (array) $cache['value']->icons;
433 }
434 return $cache['value'];
435 }
436 /**
437 * Adds the plugin version information to the database.
438 *
439 * @param string $value
440 * @param string $cache_key
441 */
442 public function set_version_info_cache($value = '', $cache_key = '')
443 {
444 if (empty($cache_key)) {
445 $cache_key = $this->cache_key;
446 }
447 $data = ['timeout' => \strtotime('+3 hours', \time()), 'value' => \json_encode($value)];
448 update_option($cache_key, $data, 'no');
449 // Delete the duplicate option
450 delete_option('edd_api_request_' . \md5(\serialize($this->slug . $this->api_data['license'] . $this->beta)));
451 }
452 /**
453 * Returns if the SSL of the store should be verified.
454 *
455 * @since 1.6.13
456 * @return bool
457 */
458 private function verify_ssl()
459 {
460 return (bool) \apply_filters('edd_sl_api_request_verify_ssl', \true, $this);
461 }
462 }
463