PluginProbe
Polylang / 2.3
Polylang v2.3
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
polylang / install / plugin-updater.php

plugin-updater.php in Polylang 2.3, at install/plugin-updater.php

497 lines 14.5 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' ) ) {
5 exit;
6 }
7
8 /**
9 * Allows plugins to use their own update API.
10 * Modified version with 'polylang' text domain and comments for translators
11 *
12 * @author Easy Digital Downloads
13 * @version 1.6.15
14 */
15 class PLL_Plugin_Updater {
16
17 private $api_url = '';
18 private $api_data = array();
19 private $name = '';
20 private $slug = '';
21 private $version = '';
22 private $wp_override = false;
23 private $cache_key = '';
24
25 /**
26 * Class constructor.
27 *
28 * @uses plugin_basename()
29 * @uses hook()
30 *
31 * @param string $_api_url The URL pointing to the custom API endpoint.
32 * @param string $_plugin_file Path to the plugin file.
33 * @param array $_api_data Optional data to send with API calls.
34 */
35 public function __construct( $_api_url, $_plugin_file, $_api_data = null ) {
36
37 global $edd_plugin_data;
38
39 $this->api_url = trailingslashit( $_api_url );
40 $this->api_data = $_api_data;
41 $this->name = plugin_basename( $_plugin_file );
42 $this->slug = basename( $_plugin_file, '.php' );
43 $this->version = $_api_data['version'];
44 $this->wp_override = isset( $_api_data['wp_override'] ) ? (bool) $_api_data['wp_override'] : false;
45 $this->beta = ! empty( $this->api_data['beta'] ) ? true : false;
46 $this->cache_key = 'edd_sl_' . md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) );
47
48 $edd_plugin_data[ $this->slug ] = $this->api_data;
49
50 // Set up hooks.
51 $this->init();
52
53 }
54
55 /**
56 * Set up WordPress filters to hook into WP's update process.
57 *
58 * @uses add_filter()
59 *
60 * @return void
61 */
62 public function init() {
63
64 add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
65 add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
66 remove_action( 'after_plugin_row_' . $this->name, 'wp_plugin_update_row', 10 );
67 add_action( 'after_plugin_row_' . $this->name, array( $this, 'show_update_notification' ), 10, 2 );
68 add_action( 'admin_init', array( $this, 'show_changelog' ) );
69
70 }
71
72 /**
73 * Check for Updates at the defined API endpoint and modify the update array.
74 *
75 * This function dives into the update API just when WordPress creates its update array,
76 * then adds a custom API call and injects the custom plugin data retrieved from the API.
77 * It is reassembled from parts of the native WordPress plugin update code.
78 * See wp-includes/update.php line 121 for the original wp_update_plugins() function.
79 *
80 * @uses api_request()
81 *
82 * @param array $_transient_data Update array build by WordPress.
83 * @return array Modified update array with custom plugin data.
84 */
85 public function check_update( $_transient_data ) {
86
87 global $pagenow;
88
89 if ( ! is_object( $_transient_data ) ) {
90 $_transient_data = new stdClass;
91 }
92
93 if ( 'plugins.php' == $pagenow && is_multisite() ) {
94 return $_transient_data;
95 }
96
97 if ( ! empty( $_transient_data->response ) && ! empty( $_transient_data->response[ $this->name ] ) && false === $this->wp_override ) {
98 return $_transient_data;
99 }
100
101 $version_info = $this->get_cached_version_info();
102
103 if ( false === $version_info ) {
104 $version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
105
106 $this->set_version_info_cache( $version_info );
107
108 }
109
110 if ( false !== $version_info && is_object( $version_info ) && isset( $version_info->new_version ) ) {
111
112 if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
113
114 $_transient_data->response[ $this->name ] = $version_info;
115
116 }
117
118 $_transient_data->last_checked = current_time( 'timestamp' );
119 $_transient_data->checked[ $this->name ] = $this->version;
120
121 }
122
123 return $_transient_data;
124 }
125
126 /**
127 * show update nofication row -- needed for multisite subsites, because WP won't tell you otherwise!
128 *
129 * @param string $file
130 * @param array $plugin
131 */
132 public function show_update_notification( $file, $plugin ) {
133
134 if ( is_network_admin() ) {
135 return;
136 }
137
138 if( ! current_user_can( 'update_plugins' ) ) {
139 return;
140 }
141
142 if( ! is_multisite() ) {
143 return;
144 }
145
146 if ( $this->name != $file ) {
147 return;
148 }
149
150 // Remove our filter on the site transient
151 remove_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ), 10 );
152
153 $update_cache = get_site_transient( 'update_plugins' );
154
155 $update_cache = is_object( $update_cache ) ? $update_cache : new stdClass();
156
157 if ( empty( $update_cache->response ) || empty( $update_cache->response[ $this->name ] ) ) {
158
159 $version_info = $this->get_cached_version_info();
160
161 if ( false === $version_info ) {
162 $version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
163
164 $this->set_version_info_cache( $version_info );
165 }
166
167 if ( ! is_object( $version_info ) ) {
168 return;
169 }
170
171 if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
172
173 $update_cache->response[ $this->name ] = $version_info;
174
175 }
176
177 $update_cache->last_checked = current_time( 'timestamp' );
178 $update_cache->checked[ $this->name ] = $this->version;
179
180 set_site_transient( 'update_plugins', $update_cache );
181
182 } else {
183
184 $version_info = $update_cache->response[ $this->name ];
185
186 }
187
188 // Restore our filter
189 add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
190
191 if ( ! empty( $update_cache->response[ $this->name ] ) && version_compare( $this->version, $version_info->new_version, '<' ) ) {
192
193 // build a plugin list row, with update notification
194 $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
195 # <tr class="plugin-update-tr"><td colspan="' . $wp_list_table->get_column_count() . '" class="plugin-update colspanchange">
196 echo '<tr class="plugin-update-tr" id="' . $this->slug . '-update" data-slug="' . $this->slug . '" data-plugin="' . $this->slug . '/' . $file . '">';
197 echo '<td colspan="3" class="plugin-update colspanchange">';
198 echo '<div class="update-message notice inline notice-warning notice-alt">';
199
200 $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' );
201
202 if ( empty( $version_info->download_link ) ) {
203 printf(
204 /* translators: %1$s plugin name, %3$s plugin version, %2$s is link start tag, %4$s is link end tag. */
205 esc_html__( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s.', 'polylang' ),
206 esc_html( $version_info->name ),
207 '<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
208 esc_html( $version_info->new_version ),
209 '</a>'
210 );
211 } else {
212 printf(
213 /* 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. */
214 esc_html__( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s or %5$supdate now%6$s.', 'polylang' ),
215 esc_html( $version_info->name ),
216 '<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
217 esc_html( $version_info->new_version ),
218 '</a>',
219 '<a href="' . esc_url( wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' ) . $this->name, 'upgrade-plugin_' . $this->name ) ) .'">',
220 '</a>'
221 );
222 }
223
224 do_action( "in_plugin_update_message-{$file}", $plugin, $version_info );
225
226 echo '</div></td></tr>';
227 }
228 }
229
230 /**
231 * Updates information on the "View version x.x details" page with custom data.
232 *
233 * @uses api_request()
234 *
235 * @param mixed $_data
236 * @param string $_action
237 * @param object $_args
238 * @return object $_data
239 */
240 public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
241
242 if ( $_action != 'plugin_information' ) {
243
244 return $_data;
245
246 }
247
248 if ( ! isset( $_args->slug ) || ( $_args->slug != $this->slug ) ) {
249
250 return $_data;
251
252 }
253
254 $to_send = array(
255 'slug' => $this->slug,
256 'is_ssl' => is_ssl(),
257 'fields' => array(
258 'banners' => array(),
259 'reviews' => false
260 )
261 );
262
263 $cache_key = 'edd_api_request_' . md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) );
264
265 // Get the transient where we store the api request for this plugin for 24 hours
266 $edd_api_request_transient = $this->get_cached_version_info( $cache_key );
267
268 //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.
269 if ( empty( $edd_api_request_transient ) ) {
270
271 $api_response = $this->api_request( 'plugin_information', $to_send );
272
273 // Expires in 3 hours
274 $this->set_version_info_cache( $api_response, $cache_key );
275
276 if ( false !== $api_response ) {
277 $_data = $api_response;
278 }
279
280 } else {
281 $_data = $edd_api_request_transient;
282 }
283
284 // Convert sections into an associative array, since we're getting an object, but Core expects an array.
285 if ( isset( $_data->sections ) && ! is_array( $_data->sections ) ) {
286 $new_sections = array();
287 foreach ( $_data->sections as $key => $value ) {
288 $new_sections[ $key ] = $value;
289 }
290
291 $_data->sections = $new_sections;
292 }
293
294 // Convert banners into an associative array, since we're getting an object, but Core expects an array.
295 if ( isset( $_data->banners ) && ! is_array( $_data->banners ) ) {
296 $new_banners = array();
297 foreach ( $_data->banners as $key => $value ) {
298 $new_banners[ $key ] = $value;
299 }
300
301 $_data->banners = $new_banners;
302 }
303
304 return $_data;
305 }
306
307 /**
308 * Disable SSL verification in order to prevent download update failures
309 *
310 * @param array $args
311 * @param string $url
312 * @return object $array
313 */
314 public function http_request_args( $args, $url ) {
315
316 $verify_ssl = $this->verify_ssl();
317 if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) {
318 $args['sslverify'] = $verify_ssl;
319 }
320 return $args;
321
322 }
323
324 /**
325 * Calls the API and, if successfull, returns the object delivered by the API.
326 *
327 * @uses get_bloginfo()
328 * @uses wp_remote_post()
329 * @uses is_wp_error()
330 *
331 * @param string $_action The requested action.
332 * @param array $_data Parameters for the API action.
333 * @return false|object
334 */
335 private function api_request( $_action, $_data ) {
336
337 global $wp_version;
338
339 $data = array_merge( $this->api_data, $_data );
340
341 if ( $data['slug'] != $this->slug ) {
342 return;
343 }
344
345 if( $this->api_url == trailingslashit (home_url() ) ) {
346 return false; // Don't allow a plugin to ping itself
347 }
348
349 $api_params = array(
350 'edd_action' => 'get_version',
351 'license' => ! empty( $data['license'] ) ? $data['license'] : '',
352 'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
353 'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
354 'version' => isset( $data['version'] ) ? $data['version'] : false,
355 'slug' => $data['slug'],
356 'author' => $data['author'],
357 'url' => home_url(),
358 'beta' => ! empty( $data['beta'] ),
359 );
360
361 $verify_ssl = $this->verify_ssl();
362 $request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
363
364 if ( ! is_wp_error( $request ) ) {
365 $request = json_decode( wp_remote_retrieve_body( $request ) );
366 }
367
368 if ( $request && isset( $request->sections ) ) {
369 $request->sections = maybe_unserialize( $request->sections );
370 } else {
371 $request = false;
372 }
373
374 if ( $request && isset( $request->banners ) ) {
375 $request->banners = maybe_unserialize( $request->banners );
376 }
377
378 if( ! empty( $request->sections ) ) {
379 foreach( $request->sections as $key => $section ) {
380 $request->$key = (array) $section;
381 }
382 }
383
384 return $request;
385 }
386
387 public function show_changelog() {
388
389 global $edd_plugin_data;
390
391 if( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' != $_REQUEST['edd_sl_action'] ) {
392 return;
393 }
394
395 if( empty( $_REQUEST['plugin'] ) ) {
396 return;
397 }
398
399 if( empty( $_REQUEST['slug'] ) ) {
400 return;
401 }
402
403 if( ! current_user_can( 'update_plugins' ) ) {
404 wp_die( __( 'You do not have permission to install plugin updates', 'polylang' ), __( 'Error', 'polylang' ), array( 'response' => 403 ) );
405 }
406
407 $data = $edd_plugin_data[ $_REQUEST['slug'] ];
408 $beta = ! empty( $data['beta'] ) ? true : false;
409 $cache_key = md5( 'edd_plugin_' . sanitize_key( $_REQUEST['plugin'] ) . '_' . $beta . '_version_info' );
410 $version_info = $this->get_cached_version_info( $cache_key );
411
412 if( false === $version_info ) {
413
414 $api_params = array(
415 'edd_action' => 'get_version',
416 'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
417 'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
418 'slug' => $_REQUEST['slug'],
419 'author' => $data['author'],
420 'url' => home_url(),
421 'beta' => ! empty( $data['beta'] )
422 );
423
424 $verify_ssl = $this->verify_ssl();
425 $request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
426
427 if ( ! is_wp_error( $request ) ) {
428 $version_info = json_decode( wp_remote_retrieve_body( $request ) );
429 }
430
431
432 if ( ! empty( $version_info ) && isset( $version_info->sections ) ) {
433 $version_info->sections = maybe_unserialize( $version_info->sections );
434 } else {
435 $version_info = false;
436 }
437
438 if( ! empty( $version_info ) ) {
439 foreach( $version_info->sections as $key => $section ) {
440 $version_info->$key = (array) $section;
441 }
442 }
443
444 $this->set_version_info_cache( $version_info, $cache_key );
445
446 }
447
448 if( ! empty( $version_info ) && isset( $version_info->sections['changelog'] ) ) {
449 echo '<div style="background:#fff;padding:10px;">' . $version_info->sections['changelog'] . '</div>';
450 }
451
452 exit;
453 }
454
455 public function get_cached_version_info( $cache_key = '' ) {
456
457 if( empty( $cache_key ) ) {
458 $cache_key = $this->cache_key;
459 }
460
461 $cache = get_option( $cache_key );
462
463 if( empty( $cache['timeout'] ) || current_time( 'timestamp' ) > $cache['timeout'] ) {
464 return false; // Cache is expired
465 }
466
467 return json_decode( $cache['value'] );
468
469 }
470
471 public function set_version_info_cache( $value = '', $cache_key = '' ) {
472
473 if( empty( $cache_key ) ) {
474 $cache_key = $this->cache_key;
475 }
476
477 $data = array(
478 'timeout' => strtotime( '+3 hours', current_time( 'timestamp' ) ),
479 'value' => json_encode( $value )
480 );
481
482 update_option( $cache_key, $data, 'no' );
483
484 }
485
486 /**
487 * Returns if the SSL of the store should be verified.
488 *
489 * @since 1.6.13
490 * @return bool
491 */
492 private function verify_ssl() {
493 return (bool) apply_filters( 'edd_sl_api_request_verify_ssl', true, $this );
494 }
495
496 }
497