PluginProbe ʕ •ᴥ•ʔ
Folders – Unlimited Folders to Organize Media Library Folder, Pages, Posts, File Manager / 2.1.2
Folders – Unlimited Folders to Organize Media Library Folder, Pages, Posts, File Manager v2.1.2
3.1.9 3.1.8 3.1.7 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7 2.9.8 3.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 trunk 1.3.7 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.2.8 2.2.9 2.3 2.3.1 2.3.2 2.3.3 2.3.4 2.3.5 2.3.6 2.3.7 2.3.8 2.3.9 2.4 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.4.8 2.4.9 2.5 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.5.8 2.5.9 2.6 2.6.1 2.6.2 2.6.3 2.6.4 2.6.5 2.6.6 2.6.7 2.6.8 2.6.9 2.7 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9 2.9.1 2.9.2
folders / includes / plugin.updates.php
folders / includes Last commit date
folders.class.php 7 years ago form.class.php 7 years ago plugin.updates.php 7 years ago tree.class.php 7 years ago
plugin.updates.php
567 lines
1 <?php
2
3 defined('ABSPATH') or die('Nope, not accessing this');
4
5 // Exit if accessed directly
6 if ( ! defined( 'ABSPATH' ) ) exit;
7
8 /**
9 * Allows plugins to use their own update API.
10 *
11 * @author Easy Digital Downloads
12 * @version 1.6.17
13 */
14 class Folder_Plugin_Updater {
15
16 private $api_url = '';
17 private $api_data = array();
18 private $name = '';
19 private $slug = '';
20 private $version = '';
21 private $wp_override = false;
22 private $cache_key = '';
23
24 private $health_check_timeout = 5;
25
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
40 $this->api_url = trailingslashit( $_api_url );
41 $this->api_data = $_api_data;
42 $this->name = plugin_basename( $_plugin_file );
43 $this->slug = basename( $_plugin_file, '.php' );
44 $this->version = $_api_data['version'];
45 $this->wp_override = isset( $_api_data['wp_override'] ) ? (bool) $_api_data['wp_override'] : false;
46 $this->beta = ! empty( $this->api_data['beta'] ) ? true : false;
47 $this->cache_key = 'edd_sl_' . md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) );
48
49 $edd_plugin_data[ $this->slug ] = $this->api_data;
50
51 /**
52 * Fires after the $edd_plugin_data is setup.
53 *
54 * @since x.x.x
55 *
56 * @param array $edd_plugin_data Array of EDD SL plugin data.
57 */
58 do_action( 'post_edd_sl_plugin_updater_setup', $edd_plugin_data );
59
60 // Set up hooks.
61 $this->init();
62
63 }
64
65 /**
66 * Set up WordPress filters to hook into WP's update process.
67 *
68 * @uses add_filter()
69 *
70 * @return void
71 */
72 public function init() {
73
74 add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
75 add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
76 remove_action( 'after_plugin_row_' . $this->name, 'wp_plugin_update_row', 10 );
77 add_action( 'after_plugin_row_' . $this->name, array( $this, 'show_update_notification' ), 10, 2 );
78 add_action( 'admin_init', array( $this, 'show_changelog' ) );
79
80 }
81
82 /**
83 * Check for Updates at the defined API endpoint and modify the update array.
84 *
85 * This function dives into the update API just when WordPress creates its update array,
86 * then adds a custom API call and injects the custom plugin data retrieved from the API.
87 * It is reassembled from parts of the native WordPress plugin update code.
88 * See wp-includes/update.php line 121 for the original wp_update_plugins() function.
89 *
90 * @uses api_request()
91 *
92 * @param array $_transient_data Update array build by WordPress.
93 * @return array Modified update array with custom plugin data.
94 */
95 public function check_update( $_transient_data ) {
96
97 global $pagenow;
98
99 if ( ! is_object( $_transient_data ) ) {
100 $_transient_data = new stdClass;
101 }
102
103 if ( 'plugins.php' == $pagenow && is_multisite() ) {
104 return $_transient_data;
105 }
106
107 if ( ! empty( $_transient_data->response ) && ! empty( $_transient_data->response[ $this->name ] ) && false === $this->wp_override ) {
108 return $_transient_data;
109 }
110
111 $version_info = $this->get_cached_version_info();
112
113 if ( false === $version_info ) {
114 $version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
115
116 $this->set_version_info_cache( $version_info );
117
118 }
119
120 if ( false !== $version_info && is_object( $version_info ) && isset( $version_info->new_version ) ) {
121
122 if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
123
124 $_transient_data->response[ $this->name ] = $version_info;
125
126 }
127
128 $_transient_data->last_checked = time();
129 $_transient_data->checked[ $this->name ] = $this->version;
130
131 }
132
133 return $_transient_data;
134 }
135
136 /**
137 * show update nofication row -- needed for multisite subsites, because WP won't tell you otherwise!
138 *
139 * @param string $file
140 * @param array $plugin
141 */
142 public function show_update_notification( $file, $plugin ) {
143
144 if ( is_network_admin() ) {
145 return;
146 }
147
148 if( ! current_user_can( 'update_plugins' ) ) {
149 return;
150 }
151
152 if( ! is_multisite() ) {
153 return;
154 }
155
156 if ( $this->name != $file ) {
157 return;
158 }
159
160 // Remove our filter on the site transient
161 remove_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ), 10 );
162
163 $update_cache = get_site_transient( 'update_plugins' );
164
165 $update_cache = is_object( $update_cache ) ? $update_cache : new stdClass();
166
167 if ( empty( $update_cache->response ) || empty( $update_cache->response[ $this->name ] ) ) {
168
169 $version_info = $this->get_cached_version_info();
170
171 if ( false === $version_info ) {
172 $version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
173
174 // Since we disabled our filter for the transient, we aren't running our object conversion on banners, sections, or icons. Do this now:
175 if ( isset( $version_info->banners ) && ! is_array( $version_info->banners ) ) {
176 $version_info->banners = $this->convert_object_to_array( $version_info->banners );
177 }
178
179 if ( isset( $version_info->sections ) && ! is_array( $version_info->sections ) ) {
180 $version_info->sections = $this->convert_object_to_array( $version_info->sections );
181 }
182
183 if ( isset( $version_info->icons ) && ! is_array( $version_info->icons ) ) {
184 $version_info->icons = $this->convert_object_to_array( $version_info->icons );
185 }
186
187 $this->set_version_info_cache( $version_info );
188 }
189
190 if ( ! is_object( $version_info ) ) {
191 return;
192 }
193
194 if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
195
196 $update_cache->response[ $this->name ] = $version_info;
197
198 }
199
200 $update_cache->last_checked = time();
201 $update_cache->checked[ $this->name ] = $this->version;
202
203 set_site_transient( 'update_plugins', $update_cache );
204
205 } else {
206
207 $version_info = $update_cache->response[ $this->name ];
208
209 }
210
211 // Restore our filter
212 add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
213
214 if ( ! empty( $update_cache->response[ $this->name ] ) && version_compare( $this->version, $version_info->new_version, '<' ) ) {
215
216 // build a plugin list row, with update notification
217 $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
218 # <tr class="plugin-update-tr"><td colspan="' . $wp_list_table->get_column_count() . '" class="plugin-update colspanchange">
219 echo '<tr class="plugin-update-tr" id="' . $this->slug . '-update" data-slug="' . $this->slug . '" data-plugin="' . $this->slug . '/' . $file . '">';
220 echo '<td colspan="3" class="plugin-update colspanchange">';
221 echo '<div class="update-message notice inline notice-warning notice-alt">';
222
223 $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' );
224
225 if ( empty( $version_info->download_link ) ) {
226 printf(
227 __( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s.', 'easy-digital-downloads' ),
228 esc_html( $version_info->name ),
229 '<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
230 esc_html( $version_info->new_version ),
231 '</a>'
232 );
233 } else {
234 printf(
235 __( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s or %5$supdate now%6$s.', 'easy-digital-downloads' ),
236 esc_html( $version_info->name ),
237 '<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
238 esc_html( $version_info->new_version ),
239 '</a>',
240 '<a href="' . esc_url( wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' ) . $this->name, 'upgrade-plugin_' . $this->name ) ) .'">',
241 '</a>'
242 );
243 }
244
245 do_action( "in_plugin_update_message-{$file}", $plugin, $version_info );
246
247 echo '</div></td></tr>';
248 }
249 }
250
251 /**
252 * Updates information on the "View version x.x details" page with custom data.
253 *
254 * @uses api_request()
255 *
256 * @param mixed $_data
257 * @param string $_action
258 * @param object $_args
259 * @return object $_data
260 */
261 public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
262
263 if ( $_action != 'plugin_information' ) {
264
265 return $_data;
266
267 }
268
269 if ( ! isset( $_args->slug ) || ( $_args->slug != $this->slug ) ) {
270
271 return $_data;
272
273 }
274
275 $to_send = array(
276 'slug' => $this->slug,
277 'is_ssl' => is_ssl(),
278 'fields' => array(
279 'banners' => array(),
280 'reviews' => false,
281 'icons' => array(),
282 )
283 );
284
285 $cache_key = 'edd_api_request_' . md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) );
286
287 // Get the transient where we store the api request for this plugin for 24 hours
288 $edd_api_request_transient = $this->get_cached_version_info( $cache_key );
289
290 //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.
291 if ( empty( $edd_api_request_transient ) ) {
292
293 $api_response = $this->api_request( 'plugin_information', $to_send );
294
295 // Expires in 3 hours
296 $this->set_version_info_cache( $api_response, $cache_key );
297
298 if ( false !== $api_response ) {
299 $_data = $api_response;
300 }
301
302 } else {
303 $_data = $edd_api_request_transient;
304 }
305
306 // Convert sections into an associative array, since we're getting an object, but Core expects an array.
307 if ( isset( $_data->sections ) && ! is_array( $_data->sections ) ) {
308 $_data->sections = $this->convert_object_to_array( $_data->sections );
309 }
310
311 // Convert banners into an associative array, since we're getting an object, but Core expects an array.
312 if ( isset( $_data->banners ) && ! is_array( $_data->banners ) ) {
313 $_data->banners = $this->convert_object_to_array( $_data->banners );
314 }
315
316 // Convert icons into an associative array, since we're getting an object, but Core expects an array.
317 if ( isset( $_data->icons ) && ! is_array( $_data->icons ) ) {
318 $_data->icons = $this->convert_object_to_array( $_data->icons );
319 }
320
321 return $_data;
322 }
323
324 /**
325 * Convert some objects to arrays when injecting data into the update API
326 *
327 * Some data like sections, banners, and icons are expected to be an associative array, however due to the JSON
328 * decoding, they are objects. This method allows us to pass in the object and return an associative array.
329 *
330 * @since 3.6.5
331 *
332 * @param stdClass $data
333 *
334 * @return array
335 */
336 private function convert_object_to_array( $data ) {
337 $new_data = array();
338 foreach ( $data as $key => $value ) {
339 $new_data[ $key ] = $value;
340 }
341
342 return $new_data;
343 }
344
345 /**
346 * Disable SSL verification in order to prevent download update failures
347 *
348 * @param array $args
349 * @param string $url
350 * @return object $array
351 */
352 public function http_request_args( $args, $url ) {
353
354 $verify_ssl = $this->verify_ssl();
355 if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) {
356 $args['sslverify'] = $verify_ssl;
357 }
358 return $args;
359
360 }
361
362 /**
363 * Calls the API and, if successfull, returns the object delivered by the API.
364 *
365 * @uses get_bloginfo()
366 * @uses wp_remote_post()
367 * @uses is_wp_error()
368 *
369 * @param string $_action The requested action.
370 * @param array $_data Parameters for the API action.
371 * @return false|object
372 */
373 private function api_request( $_action, $_data ) {
374
375 global $wp_version, $edd_plugin_url_available;
376
377 // Do a quick status check on this domain if we haven't already checked it.
378 $store_hash = md5( $this->api_url );
379 if ( ! is_array( $edd_plugin_url_available ) || ! isset( $edd_plugin_url_available[ $store_hash ] ) ) {
380 $test_url_parts = parse_url( $this->api_url );
381
382 $scheme = ! empty( $test_url_parts['scheme'] ) ? $test_url_parts['scheme'] : 'http';
383 $host = ! empty( $test_url_parts['host'] ) ? $test_url_parts['host'] : '';
384 $port = ! empty( $test_url_parts['port'] ) ? ':' . $test_url_parts['port'] : '';
385
386 if ( empty( $host ) ) {
387 $edd_plugin_url_available[ $store_hash ] = false;
388 } else {
389 $test_url = $scheme . '://' . $host . $port;
390 $response = wp_remote_get( $test_url, array( 'timeout' => $this->health_check_timeout, 'sslverify' => true ) );
391 $edd_plugin_url_available[ $store_hash ] = is_wp_error( $response ) ? false : true;
392 }
393 }
394
395 if ( false === $edd_plugin_url_available[ $store_hash ] ) {
396 return;
397 }
398
399 $data = array_merge( $this->api_data, $_data );
400
401 if ( $data['slug'] != $this->slug ) {
402 return;
403 }
404
405 if( $this->api_url == trailingslashit ( home_url() ) ) {
406 return false; // Don't allow a plugin to ping itself
407 }
408
409 $api_params = array(
410 'edd_action' => 'get_version',
411 'license' => ! empty( $data['license'] ) ? $data['license'] : '',
412 'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
413 'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
414 'version' => isset( $data['version'] ) ? $data['version'] : false,
415 'slug' => $data['slug'],
416 'author' => $data['author'],
417 'url' => home_url(),
418 'beta' => ! empty( $data['beta'] ),
419 );
420
421 $verify_ssl = $this->verify_ssl();
422 $request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
423
424 if ( ! is_wp_error( $request ) ) {
425 $request = json_decode( wp_remote_retrieve_body( $request ) );
426 }
427
428 if ( $request && isset( $request->sections ) ) {
429 $request->sections = maybe_unserialize( $request->sections );
430 } else {
431 $request = false;
432 }
433
434 if ( $request && isset( $request->banners ) ) {
435 $request->banners = maybe_unserialize( $request->banners );
436 }
437
438 if ( $request && isset( $request->icons ) ) {
439 $request->icons = maybe_unserialize( $request->icons );
440 }
441
442 if( ! empty( $request->sections ) ) {
443 foreach( $request->sections as $key => $section ) {
444 $request->$key = (array) $section;
445 }
446 }
447
448 return $request;
449 }
450
451 public function show_changelog() {
452
453 global $edd_plugin_data;
454
455 if( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' != $_REQUEST['edd_sl_action'] ) {
456 return;
457 }
458
459 if( empty( $_REQUEST['plugin'] ) ) {
460 return;
461 }
462
463 if( empty( $_REQUEST['slug'] ) ) {
464 return;
465 }
466
467 if( ! current_user_can( 'update_plugins' ) ) {
468 wp_die( __( 'You do not have permission to install plugin updates', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) );
469 }
470
471 $data = $edd_plugin_data[ $_REQUEST['slug'] ];
472 $beta = ! empty( $data['beta'] ) ? true : false;
473 $cache_key = md5( 'edd_plugin_' . sanitize_key( $_REQUEST['plugin'] ) . '_' . $beta . '_version_info' );
474 $version_info = $this->get_cached_version_info( $cache_key );
475
476 if( false === $version_info ) {
477
478 $api_params = array(
479 'edd_action' => 'get_version',
480 'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
481 'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
482 'slug' => $_REQUEST['slug'],
483 'author' => $data['author'],
484 'url' => home_url(),
485 'beta' => ! empty( $data['beta'] )
486 );
487
488 $verify_ssl = $this->verify_ssl();
489 $request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
490
491 if ( ! is_wp_error( $request ) ) {
492 $version_info = json_decode( wp_remote_retrieve_body( $request ) );
493 }
494
495
496 if ( ! empty( $version_info ) && isset( $version_info->sections ) ) {
497 $version_info->sections = maybe_unserialize( $version_info->sections );
498 } else {
499 $version_info = false;
500 }
501
502 if( ! empty( $version_info ) ) {
503 foreach( $version_info->sections as $key => $section ) {
504 $version_info->$key = (array) $section;
505 }
506 }
507
508 $this->set_version_info_cache( $version_info, $cache_key );
509
510 }
511
512 if( ! empty( $version_info ) && isset( $version_info->sections['changelog'] ) ) {
513 echo '<div style="background:#fff;padding:10px;">' . $version_info->sections['changelog'] . '</div>';
514 }
515
516 exit;
517 }
518
519 public function get_cached_version_info( $cache_key = '' ) {
520
521 if( empty( $cache_key ) ) {
522 $cache_key = $this->cache_key;
523 }
524
525 $cache = get_option( $cache_key );
526
527 if( empty( $cache['timeout'] ) || time() > $cache['timeout'] ) {
528 return false; // Cache is expired
529 }
530
531 // We need to turn the icons into an array, thanks to WP Core forcing these into an object at some point.
532 $cache['value'] = json_decode( $cache['value'] );
533 if ( ! empty( $cache['value']->icons ) ) {
534 $cache['value']->icons = (array) $cache['value']->icons;
535 }
536
537 return $cache['value'];
538
539 }
540
541 public function set_version_info_cache( $value = '', $cache_key = '' ) {
542
543 if( empty( $cache_key ) ) {
544 $cache_key = $this->cache_key;
545 }
546
547 $data = array(
548 'timeout' => strtotime( '+3 hours', time() ),
549 'value' => json_encode( $value )
550 );
551
552 update_option( $cache_key, $data, 'no' );
553
554 }
555
556 /**
557 * Returns if the SSL of the store should be verified.
558 *
559 * @since 1.6.13
560 * @return bool
561 */
562 private function verify_ssl() {
563 return (bool) apply_filters( 'edd_sl_api_request_verify_ssl', true, $this );
564 }
565
566 }
567