PluginProbe
Polylang / 2.5
Polylang v2.5
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.5, at install/plugin-updater.php

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