PluginProbe
Polylang / 3.7.7
Polylang v3.7.7
3.8.10 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 All 234 releases
← All changes | install/plugin-updater.php +371 -279 2.8.2 → 3.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.7.1
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,67 +95,147 @@
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() ) {
103 + if ( ! empty( $_transient_data->response ) && ! empty( $_transient_data->response[ $this->name ] ) && false === $this->wp_override ) {
108 104 return $_transient_data;
109 105 }
110 106
111 - if ( ! empty( $_transient_data->response ) && ! empty( $_transient_data->response[ $this->name ] ) && false === $this->wp_override ) {
112 - return $_transient_data;
107 + $current = $this->get_update_transient_data();
108 + if ( false !== $current && is_object( $current ) && isset( $current->new_version ) ) {
109 + if ( version_compare( $this->version, $current->new_version, '<' ) ) {
110 + $_transient_data->response[ $this->name ] = $current;
111 + } else {
112 + // Populating the no_update information is required to support auto-updates in WordPress 5.5.
113 + $_transient_data->no_update[ $this->name ] = $current;
114 + }
113 115 }
116 + $_transient_data->last_checked = time();
117 + $_transient_data->checked[ $this->name ] = $this->version;
114 118
119 + return $_transient_data;
120 + }
121 +
122 + /**
123 + * Get repo API data from store.
124 + * Save to cache.
125 + *
126 + * @return \stdClass
127 + */
128 + public function get_repo_api_data() {
115 129 $version_info = $this->get_cached_version_info();
116 130
117 131 if ( false === $version_info ) {
118 - $version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
132 + $version_info = $this->api_request(
133 + 'plugin_latest_version',
134 + array(
135 + 'slug' => $this->slug,
136 + 'beta' => $this->beta,
137 + )
138 + );
139 + if ( ! $version_info ) {
140 + return false;
141 + }
119 142
143 + // This is required for your plugin to support auto-updates in WordPress 5.5.
144 + $version_info->plugin = $this->name;
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 + }
153 +
120 154 $this->set_version_info_cache( $version_info );
155 + }
121 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 +
172 + return $version_info;
173 + }
174 +
175 + /**
176 + * Gets a limited set of data from the API response.
177 + * This is used for the update_plugins transient.
178 + *
179 + * @since 3.8.12
180 + * @return \stdClass|false
181 + */
182 + private function get_update_transient_data() {
183 + $version_info = $this->get_repo_api_data();
184 +
185 + if ( ! $version_info ) {
186 + return false;
122 187 }
123 188
124 - if ( false !== $version_info && is_object( $version_info ) && isset( $version_info->new_version ) ) {
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;
125 200
126 - $no_update = false;
127 - if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
201 + return $limited_data;
202 + }
128 203
129 - $_transient_data->response[ $this->name ] = $version_info;
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 ) {
130 212
131 - // Make sure the plugin property is set to the plugin's name/location. See issue 1463 on Software Licensing's GitHub repo.
132 - $_transient_data->response[ $this->name ]->plugin = $this->name;
213 + // There is no tested version.
214 + if ( empty( $version_info->tested ) ) {
215 + return null;
216 + }
133 217
134 - } else {
135 - $no_update = new stdClass();
136 - $no_update->id = '';
137 - $no_update->slug = $this->slug;
138 - $no_update->plugin = $this->name;
139 - $no_update->new_version = $version_info->new_version;
140 - $no_update->url = $version_info->homepage;
141 - $no_update->package = isset( $version_info->package ) ? $version_info->package : ''; #modified#
142 - $no_update->icons = $version_info->icons;
143 - $no_update->banners = $version_info->banners;
144 - $no_update->banners_rtl = array();
145 - }
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' ) );
146 220
147 - $_transient_data->last_checked = time();
148 - $_transient_data->checked[ $this->name ] = $this->version;
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 );
149 227
150 - if ( $no_update ) {
151 - $_transient_data->no_update[ $this->name ] = $no_update;
152 - }
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];
153 231 }
154 232
155 - return $_transient_data;
233 + return implode( '.', $tested_parts );
156 234 }
157 235
158 236 /**
159 - * show update nofication row -- needed for multisite subsites, because WP won't tell you otherwise!
237 + * Show the update notification on multisite subsites.
160 238 *
161 239 * @param string $file
162 240 * @param array $plugin
163 241 */
@@ -162,134 +240,122 @@
162 240 * @param array $plugin
163 241 */
164 242 public function show_update_notification( $file, $plugin ) {
165 243
166 - if ( is_network_admin() ) {
244 + // Return early if in the network admin, or if this is not a multisite install.
245 + if ( is_network_admin() || ! is_multisite() ) {
167 246 return;
168 247 }
169 248
170 - if( ! current_user_can( 'update_plugins' ) ) {
249 + // Allow single site admins to see that an update is available.
250 + if ( ! current_user_can( 'activate_plugins' ) ) {
171 251 return;
172 252 }
173 253
174 - if( ! is_multisite() ) {
254 + if ( $this->name !== $file ) {
175 255 return;
176 256 }
177 257
178 - if ( $this->name != $file ) {
179 - return;
180 - }
181 -
182 - // Remove our filter on the site transient
183 - remove_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ), 10 );
184 -
258 + // Do not print any message if update does not exist.
185 259 $update_cache = get_site_transient( 'update_plugins' );
186 260
187 - $update_cache = is_object( $update_cache ) ? $update_cache : new stdClass();
188 -
189 - if ( empty( $update_cache->response ) || empty( $update_cache->response[ $this->name ] ) ) {
190 -
191 - $version_info = $this->get_cached_version_info();
192 -
193 - if ( false === $version_info ) {
194 - $version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
195 -
196 - // Since we disabled our filter for the transient, we aren't running our object conversion on banners, sections, or icons. Do this now:
197 - if ( isset( $version_info->banners ) && ! is_array( $version_info->banners ) ) {
198 - $version_info->banners = $this->convert_object_to_array( $version_info->banners );
199 - }
200 -
201 - if ( isset( $version_info->sections ) && ! is_array( $version_info->sections ) ) {
202 - $version_info->sections = $this->convert_object_to_array( $version_info->sections );
203 - }
204 -
205 - if ( isset( $version_info->icons ) && ! is_array( $version_info->icons ) ) {
206 - $version_info->icons = $this->convert_object_to_array( $version_info->icons );
207 - }
208 -
209 - if ( isset( $version_info->contributors ) && ! is_array( $version_info->contributors ) ) {
210 - $version_info->contributors = $this->convert_object_to_array( $version_info->contributors );
211 - }
212 -
213 - $this->set_version_info_cache( $version_info );
261 + if ( ! isset( $update_cache->response[ $this->name ] ) ) {
262 + if ( ! is_object( $update_cache ) ) {
263 + $update_cache = new stdClass();
214 264 }
265 + $update_cache->response[ $this->name ] = $this->get_repo_api_data();
266 + }
215 267
216 - if ( ! is_object( $version_info ) ) {
217 - return;
218 - }
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 + }
219 272
220 - $no_update = false;
221 - if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
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 - $update_cache->response[ $this->name ] = $version_info;
280 + echo '<td colspan="3" class="plugin-update colspanchange">';
281 + echo '<div class="update-message notice inline notice-warning notice-alt"><p>';
224 282
225 - } else {
226 - $no_update = new stdClass();
227 - $no_update->id = '';
228 - $no_update->slug = $this->slug;
229 - $no_update->plugin = $this->name;
230 - $no_update->new_version = $version_info->new_version;
231 - $no_update->url = $version_info->homepage;
232 - $no_update->package = isset( $version_info->package ) ? $version_info->package : ''; #modified#
233 - $no_update->icons = $version_info->icons;
234 - $no_update->banners = $version_info->banners;
235 - $no_update->banners_rtl = array();
236 - }
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 + );
237 304
238 - $update_cache->last_checked = time();
239 - $update_cache->checked[ $this->name ] = $this->version;
240 - if ( $no_update ) {
241 - $update_cache->no_update[ $this->name ] = $no_update;
242 - }
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 + );
243 310
244 - set_site_transient( 'update_plugins', $update_cache );
245 -
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 + );
246 334 } else {
247 -
248 - $version_info = $update_cache->response[ $this->name ];
249 -
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 + );
250 341 }
251 342
252 - // Restore our filter
253 - add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
343 + do_action( "in_plugin_update_message-{$file}", $plugin, $plugin );
254 344
255 - if ( ! empty( $update_cache->response[ $this->name ] ) && version_compare( $this->version, $version_info->new_version, '<' ) ) {
345 + echo '</p></div></td></tr>';
346 + }
256 347
257 - // build a plugin list row, with update notification
258 - $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
259 - # <tr class="plugin-update-tr"><td colspan="' . $wp_list_table->get_column_count() . '" class="plugin-update colspanchange">
260 - echo '<tr class="plugin-update-tr" id="' . $this->slug . '-update" data-slug="' . $this->slug . '" data-plugin="' . $this->slug . '/' . $file . '">';
261 - echo '<td colspan="3" class="plugin-update colspanchange">';
262 - 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' );
263 356
264 - $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' );
265 -
266 - if ( empty( $version_info->download_link ) ) {
267 - printf(
268 - /* translators: %1$s plugin name, %3$s plugin version, %2$s is link start tag, %4$s is link end tag. */
269 - __( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s.', 'polylang' ),
270 - esc_html( $version_info->name ),
271 - '<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
272 - esc_html( $version_info->new_version ),
273 - '</a>'
274 - );
275 - } else {
276 - printf(
277 - /* 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. */
278 - __( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s or %5$supdate now%6$s.', 'polylang' ),
279 - esc_html( $version_info->name ),
280 - '<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
281 - esc_html( $version_info->new_version ),
282 - '</a>',
283 - '<a href="' . esc_url( wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' ) . $this->name, 'upgrade-plugin_' . $this->name ) ) .'">',
284 - '</a>'
285 - );
286 - }
287 -
288 - do_action( "in_plugin_update_message-{$file}", $plugin, $version_info );
289 -
290 - echo '</div></td></tr>';
291 - }
357 + return array_merge( $active_plugins, array_keys( $active_network_plugins ) );
292 358 }
293 359
294 360 /**
295 361 * Updates information on the "View version x.x details" page with custom data.
@@ -302,15 +368,15 @@
302 368 * @return object $_data
303 369 */
304 370 public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
305 371
306 - if ( $_action != 'plugin_information' ) {
372 + if ( 'plugin_information' !== $_action ) {
307 373
308 374 return $_data;
309 375
310 376 }
311 377
312 - if ( ! isset( $_args->slug ) || ( $_args->slug != $this->slug ) ) {
378 + if ( ! isset( $_args->slug ) || ( $_args->slug !== $this->slug ) ) {
313 379
314 380 return $_data;
315 381
316 382 }
@@ -321,9 +387,9 @@
321 387 'fields' => array(
322 388 'banners' => array(),
323 389 'reviews' => false,
324 390 'icons' => array(),
325 - )
391 + ),
326 392 );
327 393
328 394 // Get the transient where we store the api request for this plugin for 24 hours
329 395 $edd_api_request_transient = $this->get_cached_version_info();
@@ -331,16 +397,16 @@
331 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.
332 398 if ( empty( $edd_api_request_transient ) ) {
333 399
334 400 $api_response = $this->api_request( 'plugin_information', $to_send );
401 + if ( empty( $api_response ) ) {
402 + return $_data;
403 + }
335 404
336 405 // Expires in 3 hours
337 406 $this->set_version_info_cache( $api_response );
338 407
339 - if ( false !== $api_response ) {
340 - $_data = $api_response;
341 - }
342 -
408 + $_data = $api_response;
343 409 } else {
344 410 $_data = $edd_api_request_transient;
345 411 }
346 412
@@ -363,12 +429,16 @@
363 429 if ( isset( $_data->contributors ) && ! is_array( $_data->contributors ) ) {
364 430 $_data->contributors = $this->convert_object_to_array( $_data->contributors );
365 431 }
366 432
367 - if( ! isset( $_data->plugin ) ) {
433 + if ( ! isset( $_data->plugin ) ) {
368 434 $_data->plugin = $this->name;
369 435 }
370 436
437 + if ( ! isset( $_data->version ) && ! empty( $_data->new_version ) ) {
438 + $_data->version = $_data->new_version;
439 + }
440 +
371 441 return $_data;
372 442 }
373 443
374 444 /**
@@ -383,8 +453,11 @@
383 453 *
384 454 * @return array
385 455 */
386 456 private function convert_object_to_array( $data ) {
457 + if ( ! is_array( $data ) && ! is_object( $data ) ) {
458 + return array();
459 + }
387 460 $new_data = array();
388 461 foreach ( $data as $key => $value ) {
389 462 $new_data[ $key ] = is_object( $value ) ? $this->convert_object_to_array( $value ) : $value;
390 463 }
@@ -400,14 +473,12 @@
400 473 * @return object $array
401 474 */
402 475 public function http_request_args( $args, $url ) {
403 476
404 - $verify_ssl = $this->verify_ssl();
405 477 if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) {
406 - $args['sslverify'] = $verify_ssl;
478 + $args['sslverify'] = $this->verify_ssl();
407 479 }
408 480 return $args;
409 -
410 481 }
411 482
412 483 /**
413 484 * Calls the API and, if successfull, returns the object delivered by the API.
@@ -417,87 +488,70 @@
417 488 * @uses is_wp_error()
418 489 *
419 490 * @param string $_action The requested action.
420 491 * @param array $_data Parameters for the API action.
421 - * @return false|object
492 + * @return false|object|void
422 493 */
423 494 private function api_request( $_action, $_data ) {
495 + $data = array_merge( $this->api_data, $_data );
424 496
425 - global $wp_version, $edd_plugin_url_available;
426 -
427 - $verify_ssl = $this->verify_ssl();
428 -
429 - // Do a quick status check on this domain if we haven't already checked it.
430 - $store_hash = md5( $this->api_url );
431 - if ( ! is_array( $edd_plugin_url_available ) || ! isset( $edd_plugin_url_available[ $store_hash ] ) ) {
432 - $test_url_parts = parse_url( $this->api_url );
433 -
434 - $scheme = ! empty( $test_url_parts['scheme'] ) ? $test_url_parts['scheme'] : 'http';
435 - $host = ! empty( $test_url_parts['host'] ) ? $test_url_parts['host'] : '';
436 - $port = ! empty( $test_url_parts['port'] ) ? ':' . $test_url_parts['port'] : '';
437 -
438 - if ( empty( $host ) ) {
439 - $edd_plugin_url_available[ $store_hash ] = false;
440 - } else {
441 - $test_url = $scheme . '://' . $host . $port;
442 - $response = wp_remote_get( $test_url, array( 'timeout' => $this->health_check_timeout, 'sslverify' => $verify_ssl ) );
443 - $edd_plugin_url_available[ $store_hash ] = is_wp_error( $response ) ? false : true;
444 - }
445 - }
446 -
447 - if ( false === $edd_plugin_url_available[ $store_hash ] ) {
497 + if ( $data['slug'] !== $this->slug ) {
448 498 return;
449 499 }
450 500
451 - $data = array_merge( $this->api_data, $_data );
452 -
453 - if ( $data['slug'] != $this->slug ) {
454 - return;
501 + // Don't allow a plugin to ping itself
502 + if ( trailingslashit( home_url() ) === $this->api_url ) {
503 + return false;
455 504 }
456 505
457 - if( $this->api_url == trailingslashit ( home_url() ) ) {
458 - return false; // Don't allow a plugin to ping itself
506 + if ( $this->request_recently_failed() ) {
507 + return false;
459 508 }
460 509
461 - $api_params = array(
462 - 'edd_action' => 'get_version',
463 - 'license' => ! empty( $data['license'] ) ? $data['license'] : '',
464 - 'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
465 - 'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
466 - 'version' => isset( $data['version'] ) ? $data['version'] : false,
467 - 'slug' => $data['slug'],
468 - 'author' => $data['author'],
469 - 'url' => home_url(),
470 - 'beta' => ! empty( $data['beta'] ),
471 - );
510 + return $this->get_version_from_remote();
511 + }
472 512
473 - $request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
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 );
474 522
475 - if ( ! is_wp_error( $request ) ) {
476 - $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;
477 526 }
478 527
479 - if ( $request && isset( $request->sections ) ) {
480 - $request->sections = maybe_unserialize( $request->sections );
481 - } else {
482 - $request = false;
483 - }
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 );
484 534
485 - if ( $request && isset( $request->banners ) ) {
486 - $request->banners = maybe_unserialize( $request->banners );
535 + return false;
487 536 }
488 537
489 - if ( $request && isset( $request->icons ) ) {
490 - $request->icons = maybe_unserialize( $request->icons );
491 - }
538 + return true;
539 + }
492 540
493 - if( ! empty( $request->sections ) ) {
494 - foreach( $request->sections as $key => $section ) {
495 - $request->$key = (array) $section;
496 - }
497 - }
498 -
499 - 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' ) );
500 554 }
501 555
502 556 /**
503 557 * If available, show the changelog for sites in a multisite install.
@@ -503,92 +557,121 @@
503 557 * If available, show the changelog for sites in a multisite install.
504 558 */
505 559 public function show_changelog() {
506 560
507 - global $edd_plugin_data;
561 + if ( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' !== $_REQUEST['edd_sl_action'] ) {
562 + return;
563 + }
508 564
509 - if( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' != $_REQUEST['edd_sl_action'] ) {
565 + if ( empty( $_REQUEST['plugin'] ) ) {
510 566 return;
511 567 }
512 568
513 - if( empty( $_REQUEST['plugin'] ) ) {
569 + if ( empty( $_REQUEST['slug'] ) || $this->slug !== $_REQUEST['slug'] ) {
514 570 return;
515 571 }
516 572
517 - if( empty( $_REQUEST['slug'] ) ) {
518 - 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 ) );
519 575 }
520 576
521 - if( ! current_user_can( 'update_plugins' ) ) {
522 - 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 + }
523 583 }
524 584
525 - $data = $edd_plugin_data[ $_REQUEST['slug'] ];
526 - $version_info = $this->get_cached_version_info();
585 + exit;
586 + }
527 587
528 - 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 + );
529 607
530 - $api_params = array(
531 - 'edd_action' => 'get_version',
532 - 'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
533 - 'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
534 - 'slug' => $_REQUEST['slug'],
535 - 'author' => $data['author'],
536 - 'url' => home_url(),
537 - 'beta' => ! empty( $data['beta'] )
538 - );
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 );
539 616
540 - $verify_ssl = $this->verify_ssl();
541 - $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 + );
542 625
543 - if ( ! is_wp_error( $request ) ) {
544 - $version_info = json_decode( wp_remote_retrieve_body( $request ) );
545 - }
626 + if ( is_wp_error( $request ) || ( 200 !== wp_remote_retrieve_response_code( $request ) ) ) {
627 + $this->log_failed_request();
546 628
547 - if ( ! empty( $version_info ) && isset( $version_info->sections ) ) {
548 - $version_info->sections = maybe_unserialize( $version_info->sections );
549 - } else {
550 - $version_info = false;
551 - }
629 + return false;
630 + }
552 631
553 - if( ! empty( $version_info ) ) {
554 - foreach( $version_info->sections as $key => $section ) {
555 - $version_info->$key = (array) $section;
556 - }
557 - }
632 + $request = json_decode( wp_remote_retrieve_body( $request ) );
558 633
559 - $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 + }
560 639
561 - // Delete the unneeded option
562 - 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 );
563 642 }
564 643
565 - if ( isset( $version_info->sections ) ) {
566 - $sections = $this->convert_object_to_array( $version_info->sections );
567 - if ( ! empty( $sections['changelog'] ) ) {
568 - 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;
569 651 }
570 652 }
571 653
572 - exit;
654 + return $request;
573 655 }
574 656
575 657 /**
576 - * Gets the plugin's cached version information from the database.
658 + * Get the version info from the cache, if it exists.
577 659 *
578 660 * @param string $cache_key
579 - * @return boolean|string
661 + * @return object
580 662 */
581 663 public function get_cached_version_info( $cache_key = '' ) {
582 664
583 - if( empty( $cache_key ) ) {
584 - $cache_key = $this->cache_key;
665 + if ( empty( $cache_key ) ) {
666 + $cache_key = $this->get_cache_key();
585 667 }
586 668
587 669 $cache = get_option( $cache_key );
588 670
589 - if( empty( $cache['timeout'] ) || time() > $cache['timeout'] ) {
590 - return false; // Cache is expired
671 + // Cache is expired
672 + if ( empty( $cache['timeout'] ) || time() > $cache['timeout'] ) {
673 + return false;
591 674 }
592 675
593 676 // We need to turn the icons into an array, thanks to WP Core forcing these into an object at some point.
594 677 $cache['value'] = json_decode( $cache['value'] );
@@ -596,9 +679,8 @@
596 679 $cache['value']->icons = (array) $cache['value']->icons;
597 680 }
598 681
599 682 return $cache['value'];
600 -
601 683 }
602 684
603 685 /**
604 686 * Adds the plugin version information to the database.
@@ -607,15 +689,15 @@
607 689 * @param string $cache_key
608 690 */
609 691 public function set_version_info_cache( $value = '', $cache_key = '' ) {
610 692
611 - if( empty( $cache_key ) ) {
612 - $cache_key = $this->cache_key;
693 + if ( empty( $cache_key ) ) {
694 + $cache_key = $this->get_cache_key();
613 695 }
614 696
615 697 $data = array(
616 698 'timeout' => strtotime( '+3 hours', time() ),
617 - 'value' => json_encode( $value )
699 + 'value' => wp_json_encode( $value ),
618 700 );
619 701
620 702 update_option( $cache_key, $data, 'no' );
621 703
@@ -632,6 +714,16 @@
632 714 private function verify_ssl() {
633 715 return (bool) apply_filters( 'edd_sl_api_request_verify_ssl', true, $this );
634 716 }
635 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 + }
636 729 }
637 -