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

483 lines 14.2 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.12
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 = 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 and %4$s are html tags */
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, %4$s, %5$s and %6$s are html 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 // If it is an https request and we are performing a package download, disable ssl verification
316 if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) {
317 $args['sslverify'] = false;
318 }
319 return $args;
320 }
321
322 /**
323 * Calls the API and, if successfull, returns the object delivered by the API.
324 *
325 * @uses get_bloginfo()
326 * @uses wp_remote_post()
327 * @uses is_wp_error()
328 *
329 * @param string $_action The requested action.
330 * @param array $_data Parameters for the API action.
331 * @return false|object
332 */
333 private function api_request( $_action, $_data ) {
334
335 global $wp_version;
336
337 $data = array_merge( $this->api_data, $_data );
338
339 if ( $data['slug'] != $this->slug ) {
340 return;
341 }
342
343 if( $this->api_url == trailingslashit (home_url() ) ) {
344 return false; // Don't allow a plugin to ping itself
345 }
346
347 $api_params = array(
348 'edd_action' => 'get_version',
349 'license' => ! empty( $data['license'] ) ? $data['license'] : '',
350 'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
351 'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
352 'version' => isset( $data['version'] ) ? $data['version'] : false,
353 'slug' => $data['slug'],
354 'author' => $data['author'],
355 'url' => home_url(),
356 'beta' => ! empty( $data['beta'] ),
357 );
358
359 $request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => false, 'body' => $api_params ) );
360
361 if ( ! is_wp_error( $request ) ) {
362 $request = json_decode( wp_remote_retrieve_body( $request ) );
363 }
364
365 if ( $request && isset( $request->sections ) ) {
366 $request->sections = maybe_unserialize( $request->sections );
367 } else {
368 $request = false;
369 }
370
371 if ( $request && isset( $request->banners ) ) {
372 $request->banners = maybe_unserialize( $request->banners );
373 }
374
375 if( ! empty( $request->sections ) ) {
376 foreach( $request->sections as $key => $section ) {
377 $request->$key = (array) $section;
378 }
379 }
380
381 return $request;
382 }
383
384 public function show_changelog() {
385
386 global $edd_plugin_data;
387
388 if( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' != $_REQUEST['edd_sl_action'] ) {
389 return;
390 }
391
392 if( empty( $_REQUEST['plugin'] ) ) {
393 return;
394 }
395
396 if( empty( $_REQUEST['slug'] ) ) {
397 return;
398 }
399
400 if( ! current_user_can( 'update_plugins' ) ) {
401 wp_die( __( 'You do not have permission to install plugin updates', 'polylang' ), __( 'Error', 'polylang' ), array( 'response' => 403 ) );
402 }
403
404 $data = $edd_plugin_data[ $_REQUEST['slug'] ];
405 $beta = ! empty( $data['beta'] ) ? true : false;
406 $cache_key = md5( 'edd_plugin_' . sanitize_key( $_REQUEST['plugin'] ) . '_' . $beta . '_version_info' );
407 $version_info = $this->get_cached_version_info( $cache_key );
408
409 if( false === $version_info ) {
410
411 $api_params = array(
412 'edd_action' => 'get_version',
413 'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
414 'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
415 'slug' => $_REQUEST['slug'],
416 'author' => $data['author'],
417 'url' => home_url(),
418 'beta' => ! empty( $data['beta'] )
419 );
420
421 $request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => false, 'body' => $api_params ) );
422
423 if ( ! is_wp_error( $request ) ) {
424 $version_info = json_decode( wp_remote_retrieve_body( $request ) );
425 }
426
427
428 if ( ! empty( $version_info ) && isset( $version_info->sections ) ) {
429 $version_info->sections = maybe_unserialize( $version_info->sections );
430 } else {
431 $version_info = false;
432 }
433
434 if( ! empty( $version_info ) ) {
435 foreach( $version_info->sections as $key => $section ) {
436 $version_info->$key = (array) $section;
437 }
438 }
439
440 $this->set_version_info_cache( $version_info, $cache_key );
441
442 }
443
444 if( ! empty( $version_info ) && isset( $version_info->sections['changelog'] ) ) {
445 echo '<div style="background:#fff;padding:10px;">' . $version_info->sections['changelog'] . '</div>';
446 }
447
448 exit;
449 }
450
451 public function get_cached_version_info( $cache_key = '' ) {
452
453 if( empty( $cache_key ) ) {
454 $cache_key = $this->cache_key;
455 }
456
457 $cache = get_option( $cache_key );
458
459 if( empty( $cache['timeout'] ) || current_time( 'timestamp' ) > $cache['timeout'] ) {
460 return false; // Cache is expired
461 }
462
463 return json_decode( $cache['value'] );
464
465 }
466
467 public function set_version_info_cache( $value = '', $cache_key = '' ) {
468
469 if( empty( $cache_key ) ) {
470 $cache_key = $this->cache_key;
471 }
472
473 $data = array(
474 'timeout' => strtotime( '+3 hours', current_time( 'timestamp' ) ),
475 'value' => json_encode( $value )
476 );
477
478 update_option( $cache_key, $data );
479
480 }
481
482 }
483