PluginProbe ʕ •ᴥ•ʔ
ShopEngine Elementor WooCommerce Builder Addon – All in One WooCommerce Solution with eCommerce Templates & Woo Widgets / 1.3.4
ShopEngine Elementor WooCommerce Builder Addon – All in One WooCommerce Solution with eCommerce Templates & Woo Widgets v1.3.4
4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 2.0.0 2.1.0 2.2.0 2.2.1 2.2.2 2.3.0 2.4.0 2.5.0 2.5.1 3.0.0 3.1.0 3.1.1 4.0.0 4.0.1 4.1.0 4.1.1 4.2.0 4.2.1 4.3.0 4.3.1 4.4.0 4.5.0 4.5.1 4.6.0 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.6.6 4.6.7 4.6.8 4.6.9 4.7.0 4.7.1 4.7.2 4.7.3 4.7.4 4.7.5 4.7.6 4.7.7 4.7.8 4.7.9 4.8.0 4.8.1 4.8.2 4.8.3 4.8.4 4.8.5 4.8.6 4.8.7 4.8.8 4.8.9 trunk 0.1.2-beta 0.1.3-beta 0.1.4-beta 1.0.0 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.2.1 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.5.0 1.5.1 1.6.0 1.6.1 1.7.0 1.8.0 1.8.1 1.9.0
shopengine / libs / updater / plugin-updater.php
shopengine / libs / updater Last commit date
init.php 4 years ago plugin-updater.php 4 years ago
plugin-updater.php
573 lines
1 <?php
2
3 namespace ShopEngine\Libs\Updater;
4
5 defined('ABSPATH') || exit;
6
7 /**
8 * Allows plugins to use their own update API.
9 *
10 * @version 1.1.4
11 */
12 class Plugin_Updater {
13
14 private $api_url = '';
15 private $api_data = [];
16 private $name = '';
17 private $slug = '';
18 private $version = '';
19 private $wp_override = false;
20 private $cache_key = '';
21
22 private $health_check_timeout = 5;
23
24 /**
25 * Class constructor.
26 *
27 * @param string $_api_url The URL pointing to the custom API endpoint.
28 * @param string $_plugin_file Path to the plugin file.
29 * @param array $_api_data Optional data to send with API calls.
30 * @uses hook()
31 *
32 * @uses plugin_basename()
33 */
34 public function __construct($_api_url, $_plugin_file, $_api_data = null) {
35
36 global $edd_plugin_data;
37
38 $this->api_url = trailingslashit($_api_url);
39 $this->api_data = $_api_data;
40 $this->name = plugin_basename($_plugin_file);
41 $this->slug = basename($_plugin_file, '.php');
42 $this->version = $_api_data['version'];
43 $this->wp_override = isset($_api_data['wp_override']) ? (bool)$_api_data['wp_override'] : false;
44 $this->beta = !empty($this->api_data['beta']) ? true : false;
45 $this->cache_key = 'edd_sl_' . md5(serialize($this->slug . $this->api_data['license'] . $this->beta));
46
47 $edd_plugin_data[$this->slug] = $this->api_data;
48
49 /**
50 * Fires after the $edd_plugin_data is setup.
51 *
52 * @param array $edd_plugin_data Array of EDD SL plugin data.
53 * @since 1.0.0
54 *
55 */
56 do_action('post_edd_sl_plugin_updater_setup', $edd_plugin_data);
57
58 // Set up hooks.
59 $this->init();
60
61 }
62
63 /**
64 * Set up WordPress filters to hook into WP's update process.
65 *
66 * @return void
67 * @uses add_filter()
68 *
69 */
70 public function init() {
71
72 add_filter('pre_set_site_transient_update_plugins', [$this, 'check_update']);
73 add_filter('plugins_api', [$this, 'plugins_api_filter'], 10, 3);
74 remove_action('after_plugin_row_' . $this->name, 'wp_plugin_update_row', 10);
75 add_action('after_plugin_row_' . $this->name, [$this, 'show_update_notification'], 10, 2);
76 add_action('admin_init', [$this, 'show_changelog']);
77
78 }
79
80 /**
81 * Check for Updates at the defined API endpoint and modify the update array.
82 *
83 * This function dives into the update API just when WordPress creates its update array,
84 * then adds a custom API call and injects the custom plugin data retrieved from the API.
85 * It is reassembled from parts of the native WordPress plugin update code.
86 * See wp-includes/update.php line 121 for the original wp_update_plugins() function.
87 *
88 * @param array $_transient_data Update array build by WordPress.
89 * @return array Modified update array with custom plugin data.
90 * @uses api_request()
91 *
92 */
93 public function check_update($_transient_data) {
94
95 global $pagenow;
96
97 if(!is_object($_transient_data)) {
98 $_transient_data = new \stdClass;
99 }
100
101 if('plugins.php' == $pagenow && is_multisite()) {
102 return $_transient_data;
103 }
104
105 if(!empty($_transient_data->response) && !empty($_transient_data->response[$this->name]) && false === $this->wp_override) {
106 return $_transient_data;
107 }
108
109 $version_info = $this->get_cached_version_info();
110
111 if(false === $version_info) {
112 $version_info = $this->api_request('plugin_latest_version', ['slug' => $this->slug, 'beta' => $this->beta]);
113
114 $this->set_version_info_cache($version_info);
115
116 }
117
118 if(false !== $version_info && is_object($version_info) && isset($version_info->new_version)) {
119
120 if(version_compare($this->version, $version_info->new_version, '<')) {
121
122 $_transient_data->response[$this->name] = $version_info;
123
124 // Make sure the plugin property is set to the plugin's name/location. See issue 1463 on Software Licensing's GitHub repo.
125 $_transient_data->response[$this->name]->plugin = $this->name;
126
127 }
128
129 $_transient_data->last_checked = time();
130 $_transient_data->checked[$this->name] = $this->version;
131
132 }
133
134 return $_transient_data;
135 }
136
137 /**
138 * show update nofication row -- needed for multisite subsites, because WP won't tell you otherwise!
139 *
140 * @param string $file
141 * @param array $plugin
142 */
143 public function show_update_notification($file, $plugin) {
144
145 if(is_network_admin()) {
146 return;
147 }
148
149 if(!current_user_can('update_plugins')) {
150 return;
151 }
152
153 if(!is_multisite()) {
154 return;
155 }
156
157 if($this->name != $file) {
158 return;
159 }
160
161 // Remove our filter on the site transient
162 remove_filter('pre_set_site_transient_update_plugins', [$this, 'check_update'], 10);
163
164 $update_cache = get_site_transient('update_plugins');
165
166 $update_cache = is_object($update_cache) ? $update_cache : new \stdClass();
167
168 if(empty($update_cache->response) || empty($update_cache->response[$this->name])) {
169
170 $version_info = $this->get_cached_version_info();
171
172 if(false === $version_info) {
173 $version_info = $this->api_request('plugin_latest_version', ['slug' => $this->slug, 'beta' => $this->beta]);
174
175 // Since we disabled our filter for the transient, we aren't running our object conversion on banners, sections, or icons. Do this now:
176 if(isset($version_info->banners) && !is_array($version_info->banners)) {
177 $version_info->banners = $this->convert_object_to_array($version_info->banners);
178 }
179
180 if(isset($version_info->sections) && !is_array($version_info->sections)) {
181 $version_info->sections = $this->convert_object_to_array($version_info->sections);
182 }
183
184 if(isset($version_info->icons) && !is_array($version_info->icons)) {
185 $version_info->icons = $this->convert_object_to_array($version_info->icons);
186 }
187
188 $this->set_version_info_cache($version_info);
189 }
190
191 if(!is_object($version_info)) {
192 return;
193 }
194
195 if(version_compare($this->version, $version_info->new_version, '<')) {
196
197 $update_cache->response[$this->name] = $version_info;
198
199 }
200
201 $update_cache->last_checked = time();
202 $update_cache->checked[$this->name] = $this->version;
203
204 set_site_transient('update_plugins', $update_cache);
205
206 } else {
207
208 $version_info = $update_cache->response[$this->name];
209
210 }
211
212 // Restore our filter
213 add_filter('pre_set_site_transient_update_plugins', [$this, 'check_update']);
214
215 if(!empty($update_cache->response[$this->name]) && version_compare($this->version, $version_info->new_version, '<')) {
216
217 // build a plugin list row, with update notification
218 $wp_list_table = _get_list_table('WP_Plugins_List_Table');
219 # <tr class="plugin-update-tr"><td colspan="' . $wp_list_table->get_column_count() . '" class="plugin-update colspanchange">
220 echo '<tr class="plugin-update-tr" id="' . $this->slug . '-update" data-slug="' . $this->slug . '" data-plugin="' . $this->slug . '/' . $file . '">';
221 echo '<td colspan="3" class="plugin-update colspanchange">';
222 echo '<div class="update-message notice inline notice-warning notice-alt">';
223
224 $changelog_link = self_admin_url('index.php?edd_sl_action=view_plugin_changelog&plugin=' . $this->name . '&slug=' . $this->slug . '&TB_iframe=true&width=772&height=911');
225
226 if(empty($version_info->download_link)) {
227 printf(
228 __('There is a new version of %1$s available. %2$sView version %3$s details%4$s.', 'shopengine'),
229 esc_html($version_info->name),
230 '<a target="_blank" class="thickbox" href="' . esc_url($changelog_link) . '">',
231 esc_html($version_info->new_version),
232 '</a>'
233 );
234 } else {
235 printf(
236 __('There is a new version of %1$s available. %2$sView version %3$s details%4$s or %5$supdate now%6$s.', 'shopengine'),
237 esc_html($version_info->name),
238 '<a target="_blank" class="thickbox" href="' . esc_url($changelog_link) . '">',
239 esc_html($version_info->new_version),
240 '</a>',
241 '<a href="' . esc_url(wp_nonce_url(self_admin_url('update.php?action=upgrade-plugin&plugin=') . $this->name, 'upgrade-plugin_' . $this->name)) . '">',
242 '</a>'
243 );
244 }
245
246 do_action("in_plugin_update_message-{$file}", $plugin, $version_info);
247
248 echo '</div></td></tr>';
249 }
250 }
251
252 /**
253 * Updates information on the "View version x.x details" page with custom data.
254 *
255 * @param mixed $_data
256 * @param string $_action
257 * @param object $_args
258 * @return object $_data
259 * @uses api_request()
260 *
261 */
262 public function plugins_api_filter($_data, $_action = '', $_args = null) {
263
264 if($_action != 'plugin_information') {
265
266 return $_data;
267
268 }
269
270 if(!isset($_args->slug) || ($_args->slug != $this->slug)) {
271
272 return $_data;
273
274 }
275
276 $to_send = [
277 'slug' => $this->slug,
278 'is_ssl' => is_ssl(),
279 'fields' => [
280 'banners' => [],
281 'reviews' => false,
282 'icons' => [],
283 ],
284 ];
285
286 $cache_key = 'edd_api_request_' . md5(serialize($this->slug . $this->api_data['license'] . $this->beta));
287
288 // Get the transient where we store the api request for this plugin for 24 hours
289 $edd_api_request_transient = $this->get_cached_version_info($cache_key);
290
291 //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.
292 if(empty($edd_api_request_transient)) {
293
294 $api_response = $this->api_request('plugin_information', $to_send);
295
296 // Expires in 3 hours
297 $this->set_version_info_cache($api_response, $cache_key);
298
299 if(false !== $api_response) {
300 $_data = $api_response;
301 }
302
303 } else {
304 $_data = $edd_api_request_transient;
305 }
306
307 // Convert sections into an associative array, since we're getting an object, but Core expects an array.
308 if(isset($_data->sections) && !is_array($_data->sections)) {
309 $_data->sections = $this->convert_object_to_array($_data->sections);
310 }
311
312 // Convert banners into an associative array, since we're getting an object, but Core expects an array.
313 if(isset($_data->banners) && !is_array($_data->banners)) {
314 $_data->banners = $this->convert_object_to_array($_data->banners);
315 }
316
317 // Convert icons into an associative array, since we're getting an object, but Core expects an array.
318 if(isset($_data->icons) && !is_array($_data->icons)) {
319 $_data->icons = $this->convert_object_to_array($_data->icons);
320 }
321
322 if(!isset($_data->plugin)) {
323 $_data->plugin = $this->name;
324 }
325
326 return $_data;
327 }
328
329 /**
330 * Convert some objects to arrays when injecting data into the update API
331 *
332 * Some data like sections, banners, and icons are expected to be an associative array, however due to the JSON
333 * decoding, they are objects. This method allows us to pass in the object and return an associative array.
334 *
335 * @param stdClass $data
336 *
337 * @return array
338 * @since 3.6.5
339 *
340 */
341 private function convert_object_to_array($data) {
342 $new_data = [];
343
344 foreach($data as $key => $value) {
345 $new_data[$key] = $value;
346 }
347
348 return $new_data;
349 }
350
351 /**
352 * Disable SSL verification in order to prevent download update failures
353 *
354 * @param array $args
355 * @param string $url
356 * @return object $array
357 */
358 public function http_request_args($args, $url) {
359
360 $verify_ssl = $this->verify_ssl();
361 if(strpos($url, 'https://') !== false && strpos($url, 'edd_action=package_download')) {
362 $args['sslverify'] = $verify_ssl;
363 }
364
365 return $args;
366
367 }
368
369 /**
370 * Calls the API and, if successfull, returns the object delivered by the API.
371 *
372 * @param string $_action The requested action.
373 * @param array $_data Parameters for the API action.
374 * @return false|object
375 * @uses get_bloginfo()
376 * @uses wp_remote_post()
377 * @uses is_wp_error()
378 *
379 */
380 private function api_request($_action, $_data) {
381
382 global $wp_version, $edd_plugin_url_available;
383
384 $verify_ssl = $this->verify_ssl();
385
386 // Do a quick status check on this domain if we haven't already checked it.
387 $store_hash = md5($this->api_url);
388 if(!is_array($edd_plugin_url_available) || !isset($edd_plugin_url_available[$store_hash])) {
389 $test_url_parts = parse_url($this->api_url);
390
391 $scheme = !empty($test_url_parts['scheme']) ? $test_url_parts['scheme'] : 'http';
392 $host = !empty($test_url_parts['host']) ? $test_url_parts['host'] : '';
393 $port = !empty($test_url_parts['port']) ? ':' . $test_url_parts['port'] : '';
394
395 if(empty($host)) {
396 $edd_plugin_url_available[$store_hash] = false;
397 } else {
398 $test_url = $scheme . '://' . $host . $port;
399 $response = wp_remote_get($test_url, ['timeout' => $this->health_check_timeout, 'sslverify' => $verify_ssl]);
400 $edd_plugin_url_available[$store_hash] = is_wp_error($response) ? false : true;
401 }
402 }
403
404 if(false === $edd_plugin_url_available[$store_hash]) {
405 return;
406 }
407
408 $data = array_merge($this->api_data, $_data);
409
410 if($data['slug'] != $this->slug) {
411 return;
412 }
413
414 if($this->api_url == trailingslashit(home_url())) {
415 return false; // Don't allow a plugin to ping itself
416 }
417
418 $api_params = [
419 'edd_action' => 'get_version',
420 'license' => !empty($data['license']) ? $data['license'] : '',
421 'item_name' => isset($data['item_name']) ? $data['item_name'] : false,
422 'item_id' => isset($data['item_id']) ? $data['item_id'] : false,
423 'version' => isset($data['version']) ? $data['version'] : false,
424 'slug' => $data['slug'],
425 'author' => $data['author'],
426 'url' => home_url(),
427 'beta' => !empty($data['beta']),
428 ];
429
430 $request = wp_remote_post($this->api_url, ['timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params]);
431
432 if(!is_wp_error($request)) {
433 $request = json_decode(wp_remote_retrieve_body($request));
434 }
435
436 if($request && isset($request->sections)) {
437 $request->sections = maybe_unserialize($request->sections);
438 } else {
439 $request = false;
440 }
441
442 if($request && isset($request->banners)) {
443 $request->banners = maybe_unserialize($request->banners);
444 }
445
446 if($request && isset($request->icons)) {
447 $request->icons = maybe_unserialize($request->icons);
448 }
449
450 if(!empty($request->sections)) {
451 foreach($request->sections as $key => $section) {
452 $request->$key = (array)$section;
453 }
454 }
455
456 return $request;
457 }
458
459 public function show_changelog() {
460
461 global $edd_plugin_data;
462
463 if(empty($_REQUEST['edd_sl_action']) || 'view_plugin_changelog' != $_REQUEST['edd_sl_action']) {
464 return;
465 }
466
467 if(empty($_REQUEST['plugin'])) {
468 return;
469 }
470
471 if(empty($_REQUEST['slug'])) {
472 return;
473 }
474
475 if(!current_user_can('update_plugins')) {
476 wp_die(esc_html__('You do not have permission to install plugin updates', 'shopengine'), esc_html__('Error', 'shopengine'), ['response' => 403]);
477 }
478
479 $data = $edd_plugin_data[$_REQUEST['slug']];
480 $beta = !empty($data['beta']) ? true : false;
481 $cache_key = md5('edd_plugin_' . sanitize_key($_REQUEST['plugin']) . '_' . $beta . '_version_info');
482 $version_info = $this->get_cached_version_info($cache_key);
483
484 if(false === $version_info) {
485
486 $api_params = [
487 'edd_action' => 'get_version',
488 'item_name' => isset($data['item_name']) ? $data['item_name'] : false,
489 'item_id' => isset($data['item_id']) ? $data['item_id'] : false,
490 'slug' => $_REQUEST['slug'],
491 'author' => $data['author'],
492 'url' => home_url(),
493 'beta' => !empty($data['beta']),
494 ];
495
496 $verify_ssl = $this->verify_ssl();
497 $request = wp_remote_post($this->api_url, ['timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params]);
498
499 if(!is_wp_error($request)) {
500 $version_info = json_decode(wp_remote_retrieve_body($request));
501 }
502
503
504 if(!empty($version_info) && isset($version_info->sections)) {
505 $version_info->sections = maybe_unserialize($version_info->sections);
506 } else {
507 $version_info = false;
508 }
509
510 if(!empty($version_info)) {
511 foreach($version_info->sections as $key => $section) {
512 $version_info->$key = (array)$section;
513 }
514 }
515
516 $this->set_version_info_cache($version_info, $cache_key);
517
518 }
519
520 if(!empty($version_info) && isset($version_info->sections['changelog'])) {
521 echo '<div style="background:#fff;padding:10px;">' . $version_info->sections['changelog'] . '</div>';
522 }
523
524 exit;
525 }
526
527 public function get_cached_version_info($cache_key = '') {
528
529 if(empty($cache_key)) {
530 $cache_key = $this->cache_key;
531 }
532
533 $cache = get_option($cache_key);
534
535 if(empty($cache['timeout']) || time() > $cache['timeout']) {
536 return false; // Cache is expired
537 }
538
539 // We need to turn the icons into an array, thanks to WP Core forcing these into an object at some point.
540 $cache['value'] = json_decode($cache['value']);
541 if(!empty($cache['value']->icons)) {
542 $cache['value']->icons = (array)$cache['value']->icons;
543 }
544
545 return $cache['value'];
546
547 }
548
549 public function set_version_info_cache($value = '', $cache_key = '') {
550
551 if(empty($cache_key)) {
552 $cache_key = $this->cache_key;
553 }
554
555 $data = [
556 'timeout' => strtotime('+3 hours', time()),
557 'value' => json_encode($value),
558 ];
559
560 update_option($cache_key, $data, 'no');
561 }
562
563 /**
564 * Returns if the SSL of the store should be verified.
565 *
566 * @return bool
567 * @since 1.6.13
568 */
569 private function verify_ssl() {
570 return (bool)apply_filters('edd_sl_api_request_verify_ssl', true, $this);
571 }
572 }
573