PluginProbe
Meow Gallery / 4.2.0
Meow Gallery v4.2.0
5.5.5 5.5.4 5.5.3 5.5.2 5.5.1 5.5.0 5.4.9 5.4.8 5.4.7 4.1.5 4.1.6 4.1.7 4.1.8 4.1.9 4.2.0 4.2.1 4.2.2 4.2.3 4.2.4 4.2.5 4.2.6 4.2.7 4.2.8 4.2.9 4.3.0 All 157 releases
meow-gallery / common / classes / updater.php

updater.php in Meow Gallery 4.2.0, at common/classes/updater.php

594 lines 18.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // Exit if accessed directly
4 if ( ! defined( 'ABSPATH' ) ) exit;
5
6 /**
7 * Allows plugins to use their own update API.
8 *
9 * @author Easy Digital Downloads
10 * @version 1.6.18
11 */
12 class MeowCommon_Classes_Updater {
13
14 private $api_url = '';
15 private $api_data = array();
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 * @uses plugin_basename()
28 * @uses hook()
29 *
30 * @param string $_api_url The URL pointing to the custom API endpoint.
31 * @param string $_plugin_file Path to the plugin file.
32 * @param array $_api_data Optional data to send with API calls.
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 * @since x.x.x
53 *
54 * @param array $edd_plugin_data Array of EDD SL plugin data.
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 * @uses add_filter()
67 *
68 * @return void
69 */
70 public function init() {
71
72 add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
73 add_filter( 'plugins_api', array( $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, array( $this, 'show_update_notification' ), 10, 2 );
76 add_action( 'admin_init', array( $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 * @uses api_request()
89 *
90 * @param array $_transient_data Update array build by WordPress.
91 * @return array Modified update array with custom plugin data.
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', array( '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', array( $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', array( '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', array( $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="' . esc_html( $this->slug ) .
221 '-update" data-slug="' . esc_attr( $this->slug ) . '" data-plugin="' .
222 esc_html( $this->slug . '/' . $file ) . '">';
223 echo '<td colspan="3" class="plugin-update colspanchange">';
224 echo '<div class="update-message notice inline notice-warning notice-alt">';
225
226 $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' );
227
228 if ( empty( $version_info->download_link ) ) {
229 printf(
230 __( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s.', 'easy-digital-downloads' ),
231 esc_html( $version_info->name ),
232 '<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
233 esc_html( $version_info->new_version ),
234 '</a>'
235 );
236 } else {
237 printf(
238 __( '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' ),
239 esc_html( $version_info->name ),
240 '<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
241 esc_html( $version_info->new_version ),
242 '</a>',
243 '<a href="' . esc_url( wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' ) . $this->name, 'upgrade-plugin_' . $this->name ) ) .'">',
244 '</a>'
245 );
246 }
247
248 do_action( "in_plugin_update_message-{$file}", $plugin, $version_info );
249
250 echo '</div></td></tr>';
251 }
252 }
253
254 /**
255 * Updates information on the "View version x.x details" page with custom data.
256 *
257 * @uses api_request()
258 *
259 * @param mixed $_data
260 * @param string $_action
261 * @param object $_args
262 * @return object $_data
263 */
264 public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
265
266 if ( $_action != 'plugin_information' ) {
267
268 return $_data;
269
270 }
271
272 if ( ! isset( $_args->slug ) || ( $_args->slug != $this->slug ) ) {
273
274 return $_data;
275
276 }
277
278 $to_send = array(
279 'slug' => $this->slug,
280 'is_ssl' => is_ssl(),
281 'fields' => array(
282 'banners' => array(),
283 'reviews' => false,
284 'icons' => array(),
285 )
286 );
287
288 $cache_key = 'edd_api_request_' . md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) );
289
290 // Get the transient where we store the api request for this plugin for 24 hours
291 $edd_api_request_transient = $this->get_cached_version_info( $cache_key );
292
293 //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.
294 if ( empty( $edd_api_request_transient ) ) {
295
296 $api_response = $this->api_request( 'plugin_information', $to_send );
297
298 // Expires in 3 hours
299 $this->set_version_info_cache( $api_response, $cache_key );
300
301 if ( false !== $api_response ) {
302 $_data = $api_response;
303 }
304
305 } else {
306 $_data = $edd_api_request_transient;
307 }
308
309 // Convert sections into an associative array, since we're getting an object, but Core expects an array.
310 if ( isset( $_data->sections ) && ! is_array( $_data->sections ) ) {
311 $_data->sections = $this->convert_object_to_array( $_data->sections );
312 }
313
314 // Convert banners into an associative array, since we're getting an object, but Core expects an array.
315 if ( isset( $_data->banners ) && ! is_array( $_data->banners ) ) {
316 $_data->banners = $this->convert_object_to_array( $_data->banners );
317 }
318
319 // Convert icons into an associative array, since we're getting an object, but Core expects an array.
320 if ( isset( $_data->icons ) && ! is_array( $_data->icons ) ) {
321 $_data->icons = $this->convert_object_to_array( $_data->icons );
322 }
323
324 // Convert icons into an associative array, since we're getting an object, but Core expects an array.
325 if ( isset( $_data->contributors ) && ! is_array( $_data->contributors ) ) {
326 $_data->contributors = $this->convert_object_to_array( $_data->contributors );
327 foreach ( $_data->contributors as $key => $value ) {
328 $_data->contributors[$key] = $this->convert_object_to_array( $value );
329 }
330 }
331
332 // FOR DEBUG
333 // $fordebug = clone $_data;
334 // foreach ( $fordebug->sections as $key => $value ) {
335 // if ( strlen( $fordebug->sections[$key] ) > 200 )
336 // $fordebug->sections[$key] = '...';
337 // }
338 // $fordebug->description = '...';
339 // $fordebug->changelog = '...';
340 // var_dump( $fordebug, 1 );
341 // exit;
342
343 if ( !empty( $_data ) && !isset( $_data->plugin ) ) {
344 $_data->plugin = $this->name;
345 }
346
347 return $_data;
348 }
349
350 /**
351 * Convert some objects to arrays when injecting data into the update API
352 *
353 * Some data like sections, banners, and icons are expected to be an associative array, however due to the JSON
354 * decoding, they are objects. This method allows us to pass in the object and return an associative array.
355 *
356 * @since 3.6.5
357 *
358 * @param stdClass $data
359 *
360 * @return array
361 */
362 private function convert_object_to_array( $data ) {
363 $new_data = array();
364 foreach ( $data as $key => $value ) {
365 $new_data[ $key ] = $value;
366 }
367
368 return $new_data;
369 }
370
371 /**
372 * Disable SSL verification in order to prevent download update failures
373 *
374 * @param array $args
375 * @param string $url
376 * @return object $array
377 */
378 public function http_request_args( $args, $url ) {
379
380 $verify_ssl = $this->verify_ssl();
381 if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) {
382 $args['sslverify'] = $verify_ssl;
383 }
384 return $args;
385
386 }
387
388 /**
389 * Calls the API and, if successfull, returns the object delivered by the API.
390 *
391 * @uses get_bloginfo()
392 * @uses wp_remote_post()
393 * @uses is_wp_error()
394 *
395 * @param string $_action The requested action.
396 * @param array $_data Parameters for the API action.
397 * @return false|object
398 */
399 private function api_request( $_action, $_data ) {
400
401 global $wp_version, $edd_plugin_url_available;
402
403 $verify_ssl = $this->verify_ssl();
404
405 // Do a quick status check on this domain if we haven't already checked it.
406 $store_hash = md5( $this->api_url );
407 if ( ! is_array( $edd_plugin_url_available ) || ! isset( $edd_plugin_url_available[ $store_hash ] ) ) {
408 $test_url_parts = parse_url( $this->api_url );
409
410 $scheme = ! empty( $test_url_parts['scheme'] ) ? $test_url_parts['scheme'] : 'http';
411 $host = ! empty( $test_url_parts['host'] ) ? $test_url_parts['host'] : '';
412 $port = ! empty( $test_url_parts['port'] ) ? ':' . $test_url_parts['port'] : '';
413
414 if ( empty( $host ) ) {
415 $edd_plugin_url_available[ $store_hash ] = false;
416 } else {
417 $test_url = $scheme . '://' . $host . $port;
418 $response = wp_remote_get( $test_url, array( 'timeout' => $this->health_check_timeout, 'sslverify' => $verify_ssl ) );
419 $edd_plugin_url_available[ $store_hash ] = is_wp_error( $response ) ? false : true;
420 }
421 }
422
423 if ( false === $edd_plugin_url_available[ $store_hash ] ) {
424 return;
425 }
426
427 $data = array_merge( $this->api_data, $_data );
428
429 if ( $data['slug'] != $this->slug ) {
430 return;
431 }
432
433 if( $this->api_url == trailingslashit ( home_url() ) ) {
434 return false; // Don't allow a plugin to ping itself
435 }
436
437 $api_params = array(
438 'edd_action' => 'get_version',
439 'license' => ! empty( $data['license'] ) ? $data['license'] : '',
440 'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
441 'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
442 'version' => isset( $data['version'] ) ? $data['version'] : false,
443 'slug' => $data['slug'],
444 'author' => $data['author'],
445 'url' => home_url(),
446 'beta' => ! empty( $data['beta'] ),
447 );
448
449 $request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
450
451 if ( ! is_wp_error( $request ) ) {
452 $request = json_decode( wp_remote_retrieve_body( $request ) );
453 }
454
455 if ( $request && isset( $request->sections ) ) {
456 $request->sections = maybe_unserialize( $request->sections );
457 } else {
458 $request = false;
459 }
460
461 if ( $request && isset( $request->banners ) ) {
462 $request->banners = maybe_unserialize( $request->banners );
463 }
464
465 if ( $request && isset( $request->icons ) ) {
466 $request->icons = maybe_unserialize( $request->icons );
467 }
468
469 if( ! empty( $request->sections ) ) {
470 foreach( $request->sections as $key => $section ) {
471 $request->$key = (array) $section;
472 }
473 }
474
475 return $request;
476 }
477
478 public function show_changelog() {
479
480 global $edd_plugin_data;
481
482 if( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' != $_REQUEST['edd_sl_action'] ) {
483 return;
484 }
485
486 if( empty( $_REQUEST['plugin'] ) ) {
487 return;
488 }
489
490 if( empty( $_REQUEST['slug'] ) ) {
491 return;
492 }
493
494 if( ! current_user_can( 'update_plugins' ) ) {
495 wp_die( __( 'You do not have permission to install plugin updates', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) );
496 }
497
498 $data = $edd_plugin_data[ $_REQUEST['slug'] ];
499 $beta = ! empty( $data['beta'] ) ? true : false;
500 $cache_key = md5( 'edd_plugin_' . sanitize_key( $_REQUEST['plugin'] ) . '_' . $beta . '_version_info' );
501 $version_info = $this->get_cached_version_info( $cache_key );
502
503 if( false === $version_info ) {
504
505 $api_params = array(
506 'edd_action' => 'get_version',
507 'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
508 'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
509 'slug' => sanitize_text_field( $_REQUEST['slug'] ),
510 'author' => $data['author'],
511 'url' => home_url(),
512 'beta' => ! empty( $data['beta'] )
513 );
514
515 $verify_ssl = $this->verify_ssl();
516 $request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
517
518 if ( ! is_wp_error( $request ) ) {
519 $version_info = json_decode( wp_remote_retrieve_body( $request ) );
520 }
521
522
523 if ( ! empty( $version_info ) && isset( $version_info->sections ) ) {
524 $version_info->sections = maybe_unserialize( $version_info->sections );
525 } else {
526 $version_info = false;
527 }
528
529 if( ! empty( $version_info ) ) {
530 foreach( $version_info->sections as $key => $section ) {
531 $version_info->$key = (array) $section;
532 }
533 }
534
535 $this->set_version_info_cache( $version_info, $cache_key );
536
537 }
538
539 if( ! empty( $version_info ) && isset( $version_info->sections['changelog'] ) ) {
540 echo '<div style="background:#fff;padding:10px;">' . $version_info->sections['changelog'] . '</div>';
541 }
542
543 exit;
544 }
545
546 public function get_cached_version_info( $cache_key = '' ) {
547
548 if( empty( $cache_key ) ) {
549 $cache_key = $this->cache_key;
550 }
551
552 $cache = get_option( $cache_key );
553
554 if( empty( $cache['timeout'] ) || time() > $cache['timeout'] ) {
555 return false; // Cache is expired
556 }
557
558 // We need to turn the icons into an array, thanks to WP Core forcing these into an object at some point.
559 $cache['value'] = json_decode( $cache['value'] );
560 if ( ! empty( $cache['value']->icons ) ) {
561 $cache['value']->icons = (array) $cache['value']->icons;
562 }
563
564 return $cache['value'];
565
566 }
567
568 public function set_version_info_cache( $value = '', $cache_key = '' ) {
569
570 if( empty( $cache_key ) ) {
571 $cache_key = $this->cache_key;
572 }
573
574 $data = array(
575 'timeout' => strtotime( '+3 hours', time() ),
576 'value' => json_encode( $value )
577 );
578
579 update_option( $cache_key, $data, 'no' );
580
581 }
582
583 /**
584 * Returns if the SSL of the store should be verified.
585 *
586 * @since 1.6.13
587 * @return bool
588 */
589 private function verify_ssl() {
590 return (bool) apply_filters( 'edd_sl_api_request_verify_ssl', true, $this );
591 }
592
593 }
594