PluginProbe
Polylang / 3.7.7
Polylang v3.7.7
3.8.9 3.8.8 3.8.7 3.8.6 3.8.5 3.8.4 3.8.3 2.7 2.7.0.1 2.7.1 2.7.2 2.7.3 2.7.4 2.8 2.8.1 2.8.2 2.8.3 2.8.4 2.9 2.9.1 2.9.2 3.0 3.0.1 3.0.2 3.0.3 All 233 releases
← All changes | install/plugin-updater.php +344 -239 2.93.7.7 View file →
@@ -1,8 +1,5 @@
1 1 <?php
2 -/**
3 - * @package Polylang
4 - */
5 2
6 3 // Exit if accessed directly
7 4 if ( ! defined( 'ABSPATH' ) ) {
8 5 exit;
@@ -9,25 +6,28 @@
9 6 }
10 7
11 8 /**
12 9 * Allows plugins to use their own update API.
13 - * Modified version with 'polylang' text domain and comments for translators.
10 + * Modified version with:
11 + * - 'polylang' text domain,
12 + * - missing comments for translators,
13 + * - a bug fix (https://github.com/polylang/polylang/pull/1629).
14 14 *
15 15 * @author Easy Digital Downloads
16 - * @version 1.8.0
16 + * @version 1.9.4
17 17 */
18 18 class PLL_Plugin_Updater {
19 19
20 20 private $api_url = '';
21 21 private $api_data = array();
22 + private $plugin_file = '';
22 23 private $name = '';
23 24 private $slug = '';
24 25 private $version = '';
25 26 private $wp_override = false;
26 - private $cache_key = '';
27 + private $beta = false;
28 + private $failed_request_cache_key;
27 29
28 - private $health_check_timeout = 5;
29 -
30 30 /**
31 31 * Class constructor.
32 32 *
33 33 * @uses plugin_basename()
@@ -40,16 +40,17 @@
40 40 public function __construct( $_api_url, $_plugin_file, $_api_data = null ) {
41 41
42 42 global $edd_plugin_data;
43 43
44 - $this->api_url = trailingslashit( $_api_url );
45 - $this->api_data = $_api_data;
46 - $this->name = plugin_basename( $_plugin_file );
47 - $this->slug = basename( $_plugin_file, '.php' );
48 - $this->version = $_api_data['version'];
49 - $this->wp_override = isset( $_api_data['wp_override'] ) ? (bool) $_api_data['wp_override'] : false;
50 - $this->beta = ! empty( $this->api_data['beta'] ) ? true : false;
51 - $this->cache_key = 'edd_sl_' . md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) );
44 + $this->api_url = trailingslashit( $_api_url );
45 + $this->api_data = $_api_data;
46 + $this->plugin_file = $_plugin_file;
47 + $this->name = plugin_basename( $_plugin_file );
48 + $this->slug = basename( dirname( $_plugin_file ) );
49 + $this->version = $_api_data['version'];
50 + $this->wp_override = isset( $_api_data['wp_override'] ) ? (bool) $_api_data['wp_override'] : false;
51 + $this->beta = ! empty( $this->api_data['beta'] ) ? true : false;
52 + $this->failed_request_cache_key = 'edd_sl_failed_http_' . md5( $this->api_url );
52 53
53 54 $edd_plugin_data[ $this->slug ] = $this->api_data;
54 55
55 56 /**
@@ -62,9 +63,8 @@
62 63 do_action( 'post_edd_sl_plugin_updater_setup', $edd_plugin_data );
63 64
64 65 // Set up hooks.
65 66 $this->init();
66 -
67 67 }
68 68
69 69 /**
70 70 * Set up WordPress filters to hook into WP's update process.
@@ -76,12 +76,10 @@
76 76 public function init() {
77 77
78 78 add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
79 79 add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
80 - remove_action( 'after_plugin_row_' . $this->name, 'wp_plugin_update_row', 10 );
81 - add_action( 'after_plugin_row_' . $this->name, array( $this, 'show_update_notification' ), 10, 2 );
80 + add_action( 'after_plugin_row', array( $this, 'show_update_notification' ), 10, 2 );
82 81 add_action( 'admin_init', array( $this, 'show_changelog' ) );
83 -
84 82 }
85 83
86 84 /**
87 85 * Check for Updates at the defined API endpoint and modify the update array.
@@ -97,23 +95,17 @@
97 95 * @return array Modified update array with custom plugin data.
98 96 */
99 97 public function check_update( $_transient_data ) {
100 98
101 - global $pagenow;
102 -
103 99 if ( ! is_object( $_transient_data ) ) {
104 - $_transient_data = new stdClass;
100 + $_transient_data = new stdClass();
105 101 }
106 102
107 - if ( 'plugins.php' == $pagenow && is_multisite() ) {
108 - return $_transient_data;
109 - }
110 -
111 103 if ( ! empty( $_transient_data->response ) && ! empty( $_transient_data->response[ $this->name ] ) && false === $this->wp_override ) {
112 104 return $_transient_data;
113 105 }
114 106
115 - $current = $this->get_repo_api_data();
107 + $current = $this->get_update_transient_data();
116 108 if ( false !== $current && is_object( $current ) && isset( $current->new_version ) ) {
117 109 if ( version_compare( $this->version, $current->new_version, '<' ) ) {
118 110 $_transient_data->response[ $this->name ] = $current;
119 111 } else {
@@ -150,134 +142,220 @@
150 142
151 143 // This is required for your plugin to support auto-updates in WordPress 5.5.
152 144 $version_info->plugin = $this->name;
153 145 $version_info->id = $this->name;
146 + $version_info->tested = $this->get_tested_version( $version_info );
147 + if ( ! isset( $version_info->requires ) ) {
148 + $version_info->requires = '';
149 + }
150 + if ( ! isset( $version_info->requires_php ) ) {
151 + $version_info->requires_php = '';
152 + }
154 153
155 154 $this->set_version_info_cache( $version_info );
156 155 }
157 156
157 + // Added by Polylang.
158 + $defaults = array(
159 + 'url' => '',
160 + 'package' => '',
161 + 'new_version' => '',
162 + 'tested' => '',
163 + 'requires' => '',
164 + 'requires_php' => '',
165 + 'icons' => new stdClass(),
166 + 'banners' => new stdClass(),
167 + );
168 +
169 + $version_info = (object) array_merge( $defaults, (array) $version_info );
170 + // End of added by Polylang.
171 +
158 172 return $version_info;
159 173 }
160 174
161 175 /**
162 - * show update nofication row -- needed for multisite subsites, because WP won't tell you otherwise!
176 + * Gets a limited set of data from the API response.
177 + * This is used for the update_plugins transient.
163 178 *
164 - * @param string $file
165 - * @param array $plugin
179 + * @since 3.8.12
180 + * @return \stdClass|false
166 181 */
167 - public function show_update_notification( $file, $plugin ) {
182 + private function get_update_transient_data() {
183 + $version_info = $this->get_repo_api_data();
168 184
169 - if ( is_network_admin() ) {
170 - return;
185 + if ( ! $version_info ) {
186 + return false;
171 187 }
172 188
173 - if( ! current_user_can( 'update_plugins' ) ) {
174 - return;
175 - }
189 + $limited_data = new \stdClass();
190 + $limited_data->slug = $this->slug;
191 + $limited_data->plugin = $this->name;
192 + $limited_data->url = $version_info->url;
193 + $limited_data->package = $version_info->package;
194 + $limited_data->icons = $this->convert_object_to_array( $version_info->icons );
195 + $limited_data->banners = $this->convert_object_to_array( $version_info->banners );
196 + $limited_data->new_version = $version_info->new_version;
197 + $limited_data->tested = $version_info->tested;
198 + $limited_data->requires = $version_info->requires;
199 + $limited_data->requires_php = $version_info->requires_php;
176 200
177 - if( ! is_multisite() ) {
178 - return;
179 - }
201 + return $limited_data;
202 + }
180 203
181 - if ( $this->name != $file ) {
182 - return;
204 + /**
205 + * Gets the plugin's tested version.
206 + *
207 + * @since 1.9.2
208 + * @param object $version_info
209 + * @return null|string
210 + */
211 + private function get_tested_version( $version_info ) {
212 +
213 + // There is no tested version.
214 + if ( empty( $version_info->tested ) ) {
215 + return null;
183 216 }
184 217
185 - // Remove our filter on the site transient
186 - remove_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ), 10 );
218 + // Strip off extra version data so the result is x.y or x.y.z.
219 + list( $current_wp_version ) = explode( '-', get_bloginfo( 'version' ) );
187 220
188 - $update_cache = get_site_transient( 'update_plugins' );
221 + // The tested version is greater than or equal to the current WP version, no need to do anything.
222 + if ( version_compare( $version_info->tested, $current_wp_version, '>=' ) ) {
223 + return $version_info->tested;
224 + }
225 + $current_version_parts = explode( '.', $current_wp_version );
226 + $tested_parts = explode( '.', $version_info->tested );
189 227
190 - $update_cache = is_object( $update_cache ) ? $update_cache : new stdClass();
228 + // The current WordPress version is x.y.z, so update the tested version to match it.
229 + if ( isset( $current_version_parts[2] ) && $current_version_parts[0] === $tested_parts[0] && $current_version_parts[1] === $tested_parts[1] ) {
230 + $tested_parts[2] = $current_version_parts[2];
231 + }
191 232
192 - if ( empty( $update_cache->response ) || empty( $update_cache->response[ $this->name ] ) ) {
233 + return implode( '.', $tested_parts );
234 + }
193 235
194 - $version_info = $this->get_repo_api_data();
236 + /**
237 + * Show the update notification on multisite subsites.
238 + *
239 + * @param string $file
240 + * @param array $plugin
241 + */
242 + public function show_update_notification( $file, $plugin ) {
195 243
196 - if ( false === $version_info ) {
197 - $version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
244 + // Return early if in the network admin, or if this is not a multisite install.
245 + if ( is_network_admin() || ! is_multisite() ) {
246 + return;
247 + }
198 248
199 - // Since we disabled our filter for the transient, we aren't running our object conversion on banners, sections, or icons. Do this now:
200 - if ( isset( $version_info->banners ) && ! is_array( $version_info->banners ) ) {
201 - $version_info->banners = $this->convert_object_to_array( $version_info->banners );
202 - }
249 + // Allow single site admins to see that an update is available.
250 + if ( ! current_user_can( 'activate_plugins' ) ) {
251 + return;
252 + }
203 253
204 - if ( isset( $version_info->sections ) && ! is_array( $version_info->sections ) ) {
205 - $version_info->sections = $this->convert_object_to_array( $version_info->sections );
206 - }
254 + if ( $this->name !== $file ) {
255 + return;
256 + }
207 257
208 - if ( isset( $version_info->icons ) && ! is_array( $version_info->icons ) ) {
209 - $version_info->icons = $this->convert_object_to_array( $version_info->icons );
210 - }
258 + // Do not print any message if update does not exist.
259 + $update_cache = get_site_transient( 'update_plugins' );
211 260
212 - if ( isset( $version_info->contributors ) && ! is_array( $version_info->contributors ) ) {
213 - $version_info->contributors = $this->convert_object_to_array( $version_info->contributors );
214 - }
261 + if ( ! isset( $update_cache->response[ $this->name ] ) ) {
262 + if ( ! is_object( $update_cache ) ) {
263 + $update_cache = new stdClass();
264 + }
265 + $update_cache->response[ $this->name ] = $this->get_repo_api_data();
266 + }
215 267
216 - $this->set_version_info_cache( $version_info );
217 - }
268 + // Return early if this plugin isn't in the transient->response or if the site is running the current or newer version of the plugin.
269 + if ( empty( $update_cache->response[ $this->name ] ) || version_compare( $this->version, $update_cache->response[ $this->name ]->new_version, '>=' ) ) {
270 + return;
271 + }
218 272
219 - if ( ! is_object( $version_info ) ) {
220 - return;
221 - }
273 + printf(
274 + '<tr class="plugin-update-tr %3$s" id="%1$s-update" data-slug="%1$s" data-plugin="%2$s">',
275 + $this->slug,
276 + $file,
277 + in_array( $this->name, $this->get_active_plugins(), true ) ? 'active' : 'inactive'
278 + );
222 279
223 - if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
224 - $update_cache->response[ $this->name ] = $version_info;
225 - } else {
226 - $update_cache->no_update[ $this->name ] = $version_info;
227 - }
280 + echo '<td colspan="3" class="plugin-update colspanchange">';
281 + echo '<div class="update-message notice inline notice-warning notice-alt"><p>';
228 282
229 - $update_cache->last_checked = time();
230 - $update_cache->checked[ $this->name ] = $this->version;
283 + $changelog_link = '';
284 + if ( ! empty( $update_cache->response[ $this->name ]->sections->changelog ) ) {
285 + $changelog_link = add_query_arg(
286 + array(
287 + 'edd_sl_action' => 'view_plugin_changelog',
288 + 'plugin' => urlencode( $this->name ),
289 + 'slug' => urlencode( $this->slug ),
290 + 'TB_iframe' => 'true',
291 + 'width' => 77,
292 + 'height' => 911,
293 + ),
294 + self_admin_url( 'index.php' )
295 + );
296 + }
297 + $update_link = add_query_arg(
298 + array(
299 + 'action' => 'upgrade-plugin',
300 + 'plugin' => urlencode( $this->name ),
301 + ),
302 + self_admin_url( 'update.php' )
303 + );
231 304
232 - set_site_transient( 'update_plugins', $update_cache );
305 + printf(
306 + /* translators: the plugin name. */
307 + esc_html__( 'There is a new version of %1$s available.', 'polylang' ),
308 + esc_html( $plugin['Name'] )
309 + );
233 310
311 + if ( ! current_user_can( 'update_plugins' ) ) {
312 + echo ' ';
313 + esc_html_e( 'Contact your network administrator to install the update.', 'polylang' );
314 + } elseif ( empty( $update_cache->response[ $this->name ]->package ) && ! empty( $changelog_link ) ) {
315 + echo ' ';
316 + printf(
317 + /* translators: 1. opening anchor tag, do not translate 2. the new plugin version 3. closing anchor tag, do not translate. */
318 + __( '%1$sView version %2$s details%3$s.', 'polylang' ),
319 + '<a target="_blank" class="thickbox open-plugin-details-modal" href="' . esc_url( $changelog_link ) . '">',
320 + esc_html( $update_cache->response[ $this->name ]->new_version ),
321 + '</a>'
322 + );
323 + } elseif ( ! empty( $changelog_link ) ) {
324 + echo ' ';
325 + printf(
326 + /* translators: 1. and 4. are opening anchor tags 2. the new plugin version 3. and 5. are closing anchor tags. */
327 + __( '%1$sView version %2$s details%3$s or %4$supdate now%5$s.', 'polylang' ),
328 + '<a target="_blank" class="thickbox open-plugin-details-modal" href="' . esc_url( $changelog_link ) . '">',
329 + esc_html( $update_cache->response[ $this->name ]->new_version ),
330 + '</a>',
331 + '<a target="_blank" class="update-link" href="' . esc_url( wp_nonce_url( $update_link, 'upgrade-plugin_' . $file ) ) . '">',
332 + '</a>'
333 + );
234 334 } else {
235 -
236 - $version_info = $update_cache->response[ $this->name ];
237 -
335 + printf(
336 + ' %1$s%2$s%3$s',
337 + '<a target="_blank" class="update-link" href="' . esc_url( wp_nonce_url( $update_link, 'upgrade-plugin_' . $file ) ) . '">',
338 + esc_html__( 'Update now.', 'polylang' ),
339 + '</a>'
340 + );
238 341 }
239 342
240 - // Restore our filter
241 - add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
343 + do_action( "in_plugin_update_message-{$file}", $plugin, $plugin );
242 344
243 - if ( ! empty( $update_cache->response[ $this->name ] ) && version_compare( $this->version, $version_info->new_version, '<' ) ) {
345 + echo '</p></div></td></tr>';
346 + }
244 347
245 - // build a plugin list row, with update notification
246 - $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
247 - # <tr class="plugin-update-tr"><td colspan="' . $wp_list_table->get_column_count() . '" class="plugin-update colspanchange">
248 - echo '<tr class="plugin-update-tr" id="' . $this->slug . '-update" data-slug="' . $this->slug . '" data-plugin="' . $this->slug . '/' . $file . '">';
249 - echo '<td colspan="3" class="plugin-update colspanchange">';
250 - echo '<div class="update-message notice inline notice-warning notice-alt">';
348 + /**
349 + * Gets the plugins active in a multisite network.
350 + *
351 + * @return array
352 + */
353 + private function get_active_plugins() {
354 + $active_plugins = (array) get_option( 'active_plugins' );
355 + $active_network_plugins = (array) get_site_option( 'active_sitewide_plugins' );
251 356
252 - $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' );
253 -
254 - if ( empty( $version_info->download_link ) ) {
255 - printf(
256 - /* translators: %1$s plugin name, %3$s plugin version, %2$s is link start tag, %4$s is link end tag. */
257 - __( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s.', 'polylang' ),
258 - esc_html( $version_info->name ),
259 - '<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
260 - esc_html( $version_info->new_version ),
261 - '</a>'
262 - );
263 - } else {
264 - printf(
265 - /* translators: %1$s plugin name, %3$s plugin version, %2$s and %5$s are link start tags, %4$s and %6$s are link end tags. */
266 - __( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s or %5$supdate now%6$s.', 'polylang' ),
267 - esc_html( $version_info->name ),
268 - '<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
269 - esc_html( $version_info->new_version ),
270 - '</a>',
271 - '<a href="' . esc_url( wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' ) . $this->name, 'upgrade-plugin_' . $this->name ) ) .'">',
272 - '</a>'
273 - );
274 - }
275 -
276 - do_action( "in_plugin_update_message-{$file}", $plugin, $version_info );
277 -
278 - echo '</div></td></tr>';
279 - }
357 + return array_merge( $active_plugins, array_keys( $active_network_plugins ) );
280 358 }
281 359
282 360 /**
283 361 * Updates information on the "View version x.x details" page with custom data.
@@ -290,15 +368,15 @@
290 368 * @return object $_data
291 369 */
292 370 public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
293 371
294 - if ( $_action != 'plugin_information' ) {
372 + if ( 'plugin_information' !== $_action ) {
295 373
296 374 return $_data;
297 375
298 376 }
299 377
300 - if ( ! isset( $_args->slug ) || ( $_args->slug != $this->slug ) ) {
378 + if ( ! isset( $_args->slug ) || ( $_args->slug !== $this->slug ) ) {
301 379
302 380 return $_data;
303 381
304 382 }
@@ -309,9 +387,9 @@
309 387 'fields' => array(
310 388 'banners' => array(),
311 389 'reviews' => false,
312 390 'icons' => array(),
313 - )
391 + ),
314 392 );
315 393
316 394 // Get the transient where we store the api request for this plugin for 24 hours
317 395 $edd_api_request_transient = $this->get_cached_version_info();
@@ -319,16 +397,16 @@
319 397 //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.
320 398 if ( empty( $edd_api_request_transient ) ) {
321 399
322 400 $api_response = $this->api_request( 'plugin_information', $to_send );
401 + if ( empty( $api_response ) ) {
402 + return $_data;
403 + }
323 404
324 405 // Expires in 3 hours
325 406 $this->set_version_info_cache( $api_response );
326 407
327 - if ( false !== $api_response ) {
328 - $_data = $api_response;
329 - }
330 -
408 + $_data = $api_response;
331 409 } else {
332 410 $_data = $edd_api_request_transient;
333 411 }
334 412
@@ -351,12 +429,16 @@
351 429 if ( isset( $_data->contributors ) && ! is_array( $_data->contributors ) ) {
352 430 $_data->contributors = $this->convert_object_to_array( $_data->contributors );
353 431 }
354 432
355 - if( ! isset( $_data->plugin ) ) {
433 + if ( ! isset( $_data->plugin ) ) {
356 434 $_data->plugin = $this->name;
357 435 }
358 436
437 + if ( ! isset( $_data->version ) && ! empty( $_data->new_version ) ) {
438 + $_data->version = $_data->new_version;
439 + }
440 +
359 441 return $_data;
360 442 }
361 443
362 444 /**
@@ -371,8 +453,11 @@
371 453 *
372 454 * @return array
373 455 */
374 456 private function convert_object_to_array( $data ) {
457 + if ( ! is_array( $data ) && ! is_object( $data ) ) {
458 + return array();
459 + }
375 460 $new_data = array();
376 461 foreach ( $data as $key => $value ) {
377 462 $new_data[ $key ] = is_object( $value ) ? $this->convert_object_to_array( $value ) : $value;
378 463 }
@@ -388,14 +473,12 @@
388 473 * @return object $array
389 474 */
390 475 public function http_request_args( $args, $url ) {
391 476
392 - $verify_ssl = $this->verify_ssl();
393 477 if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) {
394 - $args['sslverify'] = $verify_ssl;
478 + $args['sslverify'] = $this->verify_ssl();
395 479 }
396 480 return $args;
397 -
398 481 }
399 482
400 483 /**
401 484 * Calls the API and, if successfull, returns the object delivered by the API.
@@ -405,87 +488,70 @@
405 488 * @uses is_wp_error()
406 489 *
407 490 * @param string $_action The requested action.
408 491 * @param array $_data Parameters for the API action.
409 - * @return false|object
492 + * @return false|object|void
410 493 */
411 494 private function api_request( $_action, $_data ) {
495 + $data = array_merge( $this->api_data, $_data );
412 496
413 - global $wp_version, $edd_plugin_url_available;
414 -
415 - $verify_ssl = $this->verify_ssl();
416 -
417 - // Do a quick status check on this domain if we haven't already checked it.
418 - $store_hash = md5( $this->api_url );
419 - if ( ! is_array( $edd_plugin_url_available ) || ! isset( $edd_plugin_url_available[ $store_hash ] ) ) {
420 - $test_url_parts = parse_url( $this->api_url );
421 -
422 - $scheme = ! empty( $test_url_parts['scheme'] ) ? $test_url_parts['scheme'] : 'http';
423 - $host = ! empty( $test_url_parts['host'] ) ? $test_url_parts['host'] : '';
424 - $port = ! empty( $test_url_parts['port'] ) ? ':' . $test_url_parts['port'] : '';
425 -
426 - if ( empty( $host ) ) {
427 - $edd_plugin_url_available[ $store_hash ] = false;
428 - } else {
429 - $test_url = $scheme . '://' . $host . $port;
430 - $response = wp_remote_get( $test_url, array( 'timeout' => $this->health_check_timeout, 'sslverify' => $verify_ssl ) );
431 - $edd_plugin_url_available[ $store_hash ] = is_wp_error( $response ) ? false : true;
432 - }
497 + if ( $data['slug'] !== $this->slug ) {
498 + return;
433 499 }
434 500
435 - if ( false === $edd_plugin_url_available[ $store_hash ] ) {
501 + // Don't allow a plugin to ping itself
502 + if ( trailingslashit( home_url() ) === $this->api_url ) {
436 503 return false;
437 504 }
438 505
439 - $data = array_merge( $this->api_data, $_data );
440 -
441 - if ( $data['slug'] != $this->slug ) {
506 + if ( $this->request_recently_failed() ) {
442 507 return false;
443 508 }
444 509
445 - if ( $this->api_url == trailingslashit ( home_url() ) ) {
446 - return false; // Don't allow a plugin to ping itself
447 - }
510 + return $this->get_version_from_remote();
511 + }
448 512
449 - $api_params = array(
450 - 'edd_action' => 'get_version',
451 - 'license' => ! empty( $data['license'] ) ? $data['license'] : '',
452 - 'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
453 - 'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
454 - 'version' => isset( $data['version'] ) ? $data['version'] : false,
455 - 'slug' => $data['slug'],
456 - 'author' => $data['author'],
457 - 'url' => home_url(),
458 - 'beta' => ! empty( $data['beta'] ),
459 - );
513 + /**
514 + * Determines if a request has recently failed.
515 + *
516 + * @since 1.9.1
517 + *
518 + * @return bool
519 + */
520 + private function request_recently_failed() {
521 + $failed_request_details = get_option( $this->failed_request_cache_key );
460 522
461 - $request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
462 -
463 - if ( ! is_wp_error( $request ) ) {
464 - $request = json_decode( wp_remote_retrieve_body( $request ) );
523 + // Request has never failed.
524 + if ( empty( $failed_request_details ) || ! is_numeric( $failed_request_details ) ) {
525 + return false;
465 526 }
466 527
467 - if ( $request && isset( $request->sections ) ) {
468 - $request->sections = maybe_unserialize( $request->sections );
469 - } else {
470 - $request = false;
471 - }
528 + /*
529 + * Request previously failed, but the timeout has expired.
530 + * This means we're allowed to try again.
531 + */
532 + if ( time() > $failed_request_details ) {
533 + delete_option( $this->failed_request_cache_key );
472 534
473 - if ( $request && isset( $request->banners ) ) {
474 - $request->banners = maybe_unserialize( $request->banners );
535 + return false;
475 536 }
476 537
477 - if ( $request && isset( $request->icons ) ) {
478 - $request->icons = maybe_unserialize( $request->icons );
479 - }
538 + return true;
539 + }
480 540
481 - if ( ! empty( $request->sections ) ) {
482 - foreach( $request->sections as $key => $section ) {
483 - $request->$key = (array) $section;
484 - }
485 - }
486 -
487 - return $request;
541 + /**
542 + * Logs a failed HTTP request for this API URL.
543 + * We set a timestamp for 1 hour from now. This prevents future API requests from being
544 + * made to this domain for 1 hour. Once the timestamp is in the past, API requests
545 + * will be allowed again. This way if the site is down for some reason we don't bombard
546 + * it with failed API requests.
547 + *
548 + * @see EDD_SL_Plugin_Updater::request_recently_failed
549 + *
550 + * @since 1.9.1
551 + */
552 + private function log_failed_request() {
553 + update_option( $this->failed_request_cache_key, strtotime( '+1 hour' ) );
488 554 }
489 555
490 556 /**
491 557 * If available, show the changelog for sites in a multisite install.
@@ -491,92 +557,121 @@
491 557 * If available, show the changelog for sites in a multisite install.
492 558 */
493 559 public function show_changelog() {
494 560
495 - global $edd_plugin_data;
561 + if ( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' !== $_REQUEST['edd_sl_action'] ) {
562 + return;
563 + }
496 564
497 - if( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' != $_REQUEST['edd_sl_action'] ) {
565 + if ( empty( $_REQUEST['plugin'] ) ) {
498 566 return;
499 567 }
500 568
501 - if( empty( $_REQUEST['plugin'] ) ) {
569 + if ( empty( $_REQUEST['slug'] ) || $this->slug !== $_REQUEST['slug'] ) {
502 570 return;
503 571 }
504 572
505 - if( empty( $_REQUEST['slug'] ) ) {
506 - return;
573 + if ( ! current_user_can( 'update_plugins' ) ) {
574 + wp_die( esc_html__( 'You do not have permission to install plugin updates', 'polylang' ), esc_html__( 'Error', 'polylang' ), array( 'response' => 403 ) );
507 575 }
508 576
509 - if( ! current_user_can( 'update_plugins' ) ) {
510 - wp_die( __( 'You do not have permission to install plugin updates', 'polylang' ), __( 'Error', 'polylang' ), array( 'response' => 403 ) );
577 + $version_info = $this->get_repo_api_data();
578 + if ( isset( $version_info->sections ) ) {
579 + $sections = $this->convert_object_to_array( $version_info->sections );
580 + if ( ! empty( $sections['changelog'] ) ) {
581 + echo '<div style="background:#fff;padding:10px;">' . wp_kses_post( $sections['changelog'] ) . '</div>';
582 + }
511 583 }
512 584
513 - $data = $edd_plugin_data[ $_REQUEST['slug'] ];
514 - $version_info = $this->get_cached_version_info();
585 + exit;
586 + }
515 587
516 - if( false === $version_info ) {
588 + /**
589 + * Gets the current version information from the remote site.
590 + *
591 + * @return array|false
592 + */
593 + private function get_version_from_remote() {
594 + $api_params = array(
595 + 'edd_action' => 'get_version',
596 + 'license' => ! empty( $this->api_data['license'] ) ? $this->api_data['license'] : '',
597 + 'item_name' => isset( $this->api_data['item_name'] ) ? $this->api_data['item_name'] : false,
598 + 'item_id' => isset( $this->api_data['item_id'] ) ? $this->api_data['item_id'] : false,
599 + 'version' => isset( $this->api_data['version'] ) ? $this->api_data['version'] : false,
600 + 'slug' => $this->slug,
601 + 'author' => $this->api_data['author'],
602 + 'url' => home_url(),
603 + 'beta' => $this->beta,
604 + 'php_version' => phpversion(),
605 + 'wp_version' => get_bloginfo( 'version' ),
606 + );
517 607
518 - $api_params = array(
519 - 'edd_action' => 'get_version',
520 - 'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
521 - 'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
522 - 'slug' => $_REQUEST['slug'],
523 - 'author' => $data['author'],
524 - 'url' => home_url(),
525 - 'beta' => ! empty( $data['beta'] )
526 - );
608 + /**
609 + * Filters the parameters sent in the API request.
610 + *
611 + * @param array $api_params The array of data sent in the request.
612 + * @param array $this->api_data The array of data set up in the class constructor.
613 + * @param string $this->plugin_file The full path and filename of the file.
614 + */
615 + $api_params = apply_filters( 'edd_sl_plugin_updater_api_params', $api_params, $this->api_data, $this->plugin_file );
527 616
528 - $verify_ssl = $this->verify_ssl();
529 - $request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
617 + $request = wp_remote_post(
618 + $this->api_url,
619 + array(
620 + 'timeout' => 15,
621 + 'sslverify' => $this->verify_ssl(),
622 + 'body' => $api_params,
623 + )
624 + );
530 625
531 - if ( ! is_wp_error( $request ) ) {
532 - $version_info = json_decode( wp_remote_retrieve_body( $request ) );
533 - }
626 + if ( is_wp_error( $request ) || ( 200 !== wp_remote_retrieve_response_code( $request ) ) ) {
627 + $this->log_failed_request();
534 628
535 - if ( ! empty( $version_info ) && isset( $version_info->sections ) ) {
536 - $version_info->sections = maybe_unserialize( $version_info->sections );
537 - } else {
538 - $version_info = false;
539 - }
629 + return false;
630 + }
540 631
541 - if( ! empty( $version_info ) ) {
542 - foreach( $version_info->sections as $key => $section ) {
543 - $version_info->$key = (array) $section;
544 - }
545 - }
632 + $request = json_decode( wp_remote_retrieve_body( $request ) );
546 633
547 - $this->set_version_info_cache( $version_info );
634 + if ( $request && isset( $request->sections ) ) {
635 + $request->sections = maybe_unserialize( $request->sections );
636 + } else {
637 + $request = false;
638 + }
548 639
549 - // Delete the unneeded option
550 - delete_option( md5( 'edd_plugin_' . sanitize_key( $_REQUEST['plugin'] ) . '_' . $this->beta . '_version_info' ) );
640 + if ( $request && isset( $request->banners ) ) {
641 + $request->banners = maybe_unserialize( $request->banners );
551 642 }
552 643
553 - if ( isset( $version_info->sections ) ) {
554 - $sections = $this->convert_object_to_array( $version_info->sections );
555 - if ( ! empty( $sections['changelog'] ) ) {
556 - echo '<div style="background:#fff;padding:10px;">' . wp_kses_post( $sections['changelog'] ) . '</div>';
644 + if ( $request && isset( $request->icons ) ) {
645 + $request->icons = maybe_unserialize( $request->icons );
646 + }
647 +
648 + if ( ! empty( $request->sections ) ) {
649 + foreach ( $request->sections as $key => $section ) {
650 + $request->$key = (array) $section;
557 651 }
558 652 }
559 653
560 - exit;
654 + return $request;
561 655 }
562 656
563 657 /**
564 - * Gets the plugin's cached version information from the database.
658 + * Get the version info from the cache, if it exists.
565 659 *
566 660 * @param string $cache_key
567 - * @return boolean|string
661 + * @return object
568 662 */
569 663 public function get_cached_version_info( $cache_key = '' ) {
570 664
571 - if( empty( $cache_key ) ) {
572 - $cache_key = $this->cache_key;
665 + if ( empty( $cache_key ) ) {
666 + $cache_key = $this->get_cache_key();
573 667 }
574 668
575 669 $cache = get_option( $cache_key );
576 670
577 - if( empty( $cache['timeout'] ) || time() > $cache['timeout'] ) {
578 - return false; // Cache is expired
671 + // Cache is expired
672 + if ( empty( $cache['timeout'] ) || time() > $cache['timeout'] ) {
673 + return false;
579 674 }
580 675
581 676 // We need to turn the icons into an array, thanks to WP Core forcing these into an object at some point.
582 677 $cache['value'] = json_decode( $cache['value'] );
@@ -584,9 +679,8 @@
584 679 $cache['value']->icons = (array) $cache['value']->icons;
585 680 }
586 681
587 682 return $cache['value'];
588 -
589 683 }
590 684
591 685 /**
592 686 * Adds the plugin version information to the database.
@@ -595,15 +689,15 @@
595 689 * @param string $cache_key
596 690 */
597 691 public function set_version_info_cache( $value = '', $cache_key = '' ) {
598 692
599 - if( empty( $cache_key ) ) {
600 - $cache_key = $this->cache_key;
693 + if ( empty( $cache_key ) ) {
694 + $cache_key = $this->get_cache_key();
601 695 }
602 696
603 697 $data = array(
604 698 'timeout' => strtotime( '+3 hours', time() ),
605 - 'value' => json_encode( $value )
699 + 'value' => wp_json_encode( $value ),
606 700 );
607 701
608 702 update_option( $cache_key, $data, 'no' );
609 703
@@ -620,5 +714,16 @@
620 714 private function verify_ssl() {
621 715 return (bool) apply_filters( 'edd_sl_api_request_verify_ssl', true, $this );
622 716 }
623 717
718 + /**
719 + * Gets the unique key (option name) for a plugin.
720 + *
721 + * @since 1.9.0
722 + * @return string
723 + */
724 + private function get_cache_key() {
725 + $string = $this->slug . $this->api_data['license'] . $this->beta;
726 +
727 + return 'edd_sl_' . md5( serialize( $string ) );
728 + }
624 729 }