PluginProbe
Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder / 1.4.1
Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder v1.4.1
3.6.0 3.5.0 trunk 1.0.10 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.6.1 1.0.7 1.0.8 1.0.9 1.1 1.1.1 1.2 1.3 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.5 1.5.1 All 110 releases
buttonizer-multifunctional-button / freemius / includes / class-fs-plugin-updater.php

class-fs-plugin-updater.php in Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder 1.4.1, at freemius/includes/class-fs-plugin-updater.php

712 lines 21.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package Freemius
4 * @copyright Copyright (c) 2015, Freemius, Inc.
5 * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6 * @since 1.0.4
7 *
8 * @link https://github.com/easydigitaldownloads/EDD-License-handler/blob/master/EDD_SL_Plugin_Updater.php
9 */
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 // Uncomment this line for testing.
16 // set_site_transient( 'update_plugins', null );
17
18 class FS_Plugin_Updater {
19
20 /**
21 * @var Freemius
22 * @since 1.0.4
23 */
24 private $_fs;
25 /**
26 * @var FS_Logger
27 * @since 1.0.4
28 */
29 private $_logger;
30 /**
31 * @var object
32 * @since 1.1.8.1
33 */
34 private $_update_details;
35
36 function __construct( Freemius $freemius ) {
37 $this->_fs = $freemius;
38
39 $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $freemius->get_slug() . '_updater', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
40
41 $this->_filters();
42 }
43
44 /**
45 * Initiate required filters.
46 *
47 * @author Vova Feldman (@svovaf)
48 * @since 1.0.4
49 */
50 private function _filters() {
51 // Override request for plugin information
52 add_filter( 'plugins_api', array( &$this, 'plugins_api_filter' ), 10, 3 );
53
54 // WP 3.0+
55 add_filter( 'pre_set_site_transient_update_plugins', array(
56 &$this,
57 'pre_set_site_transient_update_plugins_filter'
58 ) );
59
60 add_filter( 'pre_set_site_transient_update_themes', array(
61 &$this,
62 'pre_set_site_transient_update_plugins_filter'
63 ) );
64
65 if ( ! $this->_fs->has_active_valid_license() ) {
66 /**
67 * If user has the premium plugin's code but do NOT have an active license,
68 * encourage him to upgrade by showing that there's a new release, but instead
69 * of showing an update link, show upgrade link to the pricing page.
70 *
71 * @since 1.1.6
72 *
73 */
74 // WP 2.9+
75 add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array(
76 &$this,
77 'catch_plugin_update_row'
78 ), 9 );
79 add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array(
80 &$this,
81 'edit_and_echo_plugin_update_row'
82 ), 11, 2 );
83 }
84
85 if ( ! WP_FS__IS_PRODUCTION_MODE ) {
86 add_filter( 'http_request_host_is_external', array(
87 $this,
88 'http_request_host_is_external_filter'
89 ), 10, 3 );
90 }
91
92 if ( $this->_fs->is_premium() && $this->is_correct_folder_name() ) {
93 add_filter( 'upgrader_post_install', array( &$this, '_maybe_update_folder_name' ), 10, 3 );
94 }
95 }
96
97 /**
98 * Capture plugin update row by turning output buffering.
99 *
100 * @author Vova Feldman (@svovaf)
101 * @since 1.1.6
102 */
103 function catch_plugin_update_row() {
104 ob_start();
105 }
106
107 /**
108 * Overrides default update message format with "renew your license" message.
109 *
110 * @author Vova Feldman (@svovaf)
111 * @since 1.1.6
112 *
113 * @param string $file
114 * @param array $plugin_data
115 */
116 function edit_and_echo_plugin_update_row( $file, $plugin_data ) {
117 $plugin_update_row = ob_get_clean();
118
119 $current = get_site_transient( 'update_plugins' );
120 if ( ! isset( $current->response[ $file ] ) ) {
121 echo $plugin_update_row;
122
123 return;
124 }
125
126 $r = $current->response[ $file ];
127
128 $plugin_update_row = preg_replace(
129 '/(\<div.+>)(.+)(\<a.+\<a.+)\<\/div\>/is',
130 '$1 $2 ' . sprintf(
131 $this->_fs->get_text_inline( '%sRenew your license now%s to access version %s features and support.', 'renew-license-now' ),
132 '<a href="' . $this->_fs->pricing_url() . '">', '</a>',
133 $r->new_version ) .
134 '$4',
135 $plugin_update_row
136 );
137
138 echo $plugin_update_row;
139 }
140
141 /**
142 * Since WP version 3.6, a new security feature was added that denies access to repository with a local ip.
143 * During development mode we want to be able updating plugin versions via our localhost repository. This
144 * filter white-list all domains including "api.freemius".
145 *
146 * @link http://www.emanueletessore.com/wordpress-download-failed-valid-url-provided/
147 *
148 * @author Vova Feldman (@svovaf)
149 * @since 1.0.4
150 *
151 * @param bool $allow
152 * @param string $host
153 * @param string $url
154 *
155 * @return bool
156 */
157 function http_request_host_is_external_filter( $allow, $host, $url ) {
158 return ( false !== strpos( $host, 'freemius' ) ) ? true : $allow;
159 }
160
161 /**
162 * Check for Updates at the defined API endpoint and modify the update array.
163 *
164 * This function dives into the update api just when WordPress creates its update array,
165 * then adds a custom API call and injects the custom plugin data retrieved from the API.
166 * It is reassembled from parts of the native WordPress plugin update code.
167 * See wp-includes/update.php line 121 for the original wp_update_plugins() function.
168 *
169 * @author Vova Feldman (@svovaf)
170 * @since 1.0.4
171 *
172 * @uses FS_Api
173 *
174 * @param object $transient_data Update array build by WordPress.
175 *
176 * @return object Modified update array with custom plugin data.
177 */
178 function pre_set_site_transient_update_plugins_filter( $transient_data ) {
179 $this->_logger->entrance();
180
181 /**
182 * "plugins" or "themes".
183 *
184 * @author Leo Fajardo (@leorw)
185 * @since 1.2.2
186 */
187 $module_type = $this->_fs->get_module_type() . 's';
188
189 /**
190 * Ensure that we don't mix plugins update info with themes update info.
191 *
192 * @author Leo Fajardo (@leorw)
193 * @since 1.2.2
194 */
195 if ( "pre_set_site_transient_update_{$module_type}" !== current_filter() ) {
196 return $transient_data;
197 }
198
199 if ( empty( $transient_data ) ||
200 defined( 'WP_FS__UNINSTALL_MODE' )
201 ) {
202 return $transient_data;
203 }
204
205 if ( ! isset( $this->_update_details ) ) {
206 // Get plugin's newest update.
207 $new_version = $this->_fs->get_update(
208 false,
209 fs_request_get_bool( 'force-check' ),
210 WP_FS__TIME_24_HOURS_IN_SEC / 24
211 );
212
213 $this->_update_details = false;
214
215 if ( is_object( $new_version ) ) {
216 $this->_logger->log( 'Found newer plugin version ' . $new_version->version );
217
218 $plugin_details = new stdClass();
219 $plugin_details->slug = $this->_fs->get_slug();
220 $plugin_details->new_version = $new_version->version;
221 $plugin_details->url = WP_FS__ADDRESS;
222 $plugin_details->package = $new_version->url;
223 $plugin_details->{ $this->_fs->get_module_type() } = $this->_fs->get_plugin_basename();
224
225 /**
226 * Cache plugin details locally since set_site_transient( 'update_plugins' )
227 * called multiple times and the non wp.org plugins are filtered after the
228 * call to .org.
229 *
230 * @since 1.1.8.1
231 */
232 $this->_update_details = $plugin_details;
233 }
234 }
235
236 if ( is_object( $this->_update_details ) ) {
237 // Add plugin to transient data.
238 if ( $this->_fs->is_plugin() ) {
239 $transient_data->response[ $this->_fs->get_plugin_basename() ] = $this->_update_details;
240 } else {
241 $transient_data->response[ $this->_update_details->theme ] = (array) $this->_update_details;
242 }
243 }
244
245 return $transient_data;
246 }
247
248 /**
249 * Try to fetch plugin's info from .org repository.
250 *
251 * @author Vova Feldman (@svovaf)
252 * @since 1.0.5
253 *
254 * @param string $action
255 * @param object $args
256 *
257 * @return bool|mixed
258 */
259 static function _fetch_plugin_info_from_repository( $action, $args ) {
260 $url = $http_url = 'http://api.wordpress.org/plugins/info/1.0/';
261 if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) {
262 $url = set_url_scheme( $url, 'https' );
263 }
264
265 $args = array(
266 'timeout' => 15,
267 'body' => array(
268 'action' => $action,
269 'request' => serialize( $args )
270 )
271 );
272
273 $request = wp_remote_post( $url, $args );
274
275 if ( is_wp_error( $request ) ) {
276 return false;
277 }
278
279 $res = maybe_unserialize( wp_remote_retrieve_body( $request ) );
280
281 if ( ! is_object( $res ) && ! is_array( $res ) ) {
282 return false;
283 }
284
285 return $res;
286 }
287
288 /**
289 * Updates information on the "View version x.x details" page with custom data.
290 *
291 * @author Vova Feldman (@svovaf)
292 * @since 1.0.4
293 *
294 * @uses FS_Api
295 *
296 * @param object $data
297 * @param string $action
298 * @param mixed $args
299 *
300 * @return object
301 */
302 function plugins_api_filter( $data, $action = '', $args = null ) {
303 $this->_logger->entrance();
304
305 if ( ( 'plugin_information' !== $action ) ||
306 ! isset( $args->slug )
307 ) {
308 return $data;
309 }
310
311 $addon = false;
312 $is_addon = false;
313
314 if ( $this->_fs->get_slug() !== $args->slug ) {
315 $addon = $this->_fs->get_addon_by_slug( $args->slug );
316
317 if ( ! is_object( $addon ) ) {
318 return $data;
319 }
320
321 $is_addon = true;
322 }
323
324 $plugin_in_repo = false;
325 if ( ! $is_addon ) {
326 // Try to fetch info from .org repository.
327 $data = self::_fetch_plugin_info_from_repository( $action, $args );
328
329 $plugin_in_repo = ( false !== $data );
330 }
331
332 if ( ! $plugin_in_repo ) {
333 $data = $args;
334
335 // Fetch as much as possible info from local files.
336 $plugin_local_data = $this->_fs->get_plugin_data();
337 $data->name = $plugin_local_data['Name'];
338 $data->author = $plugin_local_data['Author'];
339 $data->sections = array(
340 'description' => 'Upgrade ' . $plugin_local_data['Name'] . ' to latest.',
341 );
342
343 // @todo Store extra plugin info on Freemius or parse readme.txt markup.
344 /*$info = $this->_fs->get_api_site_scope()->call('/information.json');
345
346 if ( !isset($info->error) ) {
347 $data = $info;
348 }*/
349 }
350
351 // Get plugin's newest update.
352 $new_version = $this->get_latest_download_details( $is_addon ? $addon->id : false );
353
354 if ( ! is_object( $new_version ) || empty( $new_version->version ) ) {
355 $data->version = $this->_fs->get_plugin_version();
356 } else {
357 if ( $is_addon ) {
358 $data->name = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' );
359 $data->slug = $addon->slug;
360 $data->url = WP_FS__ADDRESS;
361 $data->package = $new_version->url;
362 }
363
364 if ( ! $plugin_in_repo ) {
365 $data->last_updated = ! is_null( $new_version->updated ) ? $new_version->updated : $new_version->created;
366 $data->requires = $new_version->requires_platform_version;
367 $data->tested = $new_version->tested_up_to_version;
368 }
369
370 $data->version = $new_version->version;
371 $data->download_link = $new_version->url;
372 }
373
374 return $data;
375 }
376
377 /**
378 * @author Vova Feldman (@svovaf)
379 * @since 1.2.1.7
380 *
381 * @param number|bool $addon_id
382 *
383 * @return object
384 */
385 private function get_latest_download_details( $addon_id = false ) {
386 return $this->_fs->_fetch_latest_version( $addon_id );
387 }
388
389 /**
390 * Checks if a given basename has a matching folder name
391 * with the current context plugin.
392 *
393 * @author Vova Feldman (@svovaf)
394 * @since 1.2.1.6
395 *
396 * @param string $basename Current plugin's basename.
397 *
398 * @return bool
399 */
400 private function is_correct_folder_name( $basename = '' ) {
401 if ( empty( $basename ) ) {
402 $basename = $this->_fs->get_plugin_basename();
403 }
404
405 return ( $this->_fs->get_target_folder_name() != trim( dirname( $basename ), '/\\' ) );
406 }
407
408 /**
409 * This is a special after upgrade handler for migrating modules
410 * that didn't use the '-premium' suffix folder structure before
411 * the migration.
412 *
413 * @author Vova Feldman (@svovaf)
414 * @since 1.2.1.6
415 *
416 * @param bool $response Install response.
417 * @param array $hook_extra Extra arguments passed to hooked filters.
418 * @param array $result Installation result data.
419 *
420 * @return bool
421 */
422 function _maybe_update_folder_name( $response, $hook_extra, $result ) {
423 $basename = $this->_fs->get_plugin_basename();
424
425 if ( true !== $response ||
426 empty( $hook_extra ) ||
427 empty( $hook_extra['plugin'] ) ||
428 $basename !== $hook_extra['plugin']
429 ) {
430 return $response;
431 }
432
433 $active_plugins_basenames = get_option( 'active_plugins' );
434
435 for ( $i = 0, $len = count( $active_plugins_basenames ); $i < $len; $i ++ ) {
436 if ( $basename === $active_plugins_basenames[ $i ] ) {
437 // Get filename including extension.
438 $filename = basename( $basename );
439
440 $new_basename = plugin_basename(
441 trailingslashit( $this->_fs->get_slug() . ( $this->_fs->is_premium() ? '-premium' : '' ) ) .
442 $filename
443 );
444
445 // Verify that the expected correct path exists.
446 if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $new_basename ) ) ) {
447 // Override active plugin name.
448 $active_plugins_basenames[ $i ] = $new_basename;
449 update_option( 'active_plugins', $active_plugins_basenames );
450 }
451
452 break;
453 }
454 }
455
456 return $response;
457 }
458
459 #----------------------------------------------------------------------------------
460 #region Auto Activation
461 #----------------------------------------------------------------------------------
462
463 /**
464 * Installs and active a plugin when explicitly requested that from a 3rd party service.
465 *
466 * This logic was inspired by the TGMPA GPL licensed library by Thomas Griffin.
467 *
468 * @link http://tgmpluginactivation.com/
469 *
470 * @author Vova Feldman
471 * @since 1.2.1.7
472 *
473 * @link https://make.wordpress.org/plugins/2017/03/16/clarification-of-guideline-8-executable-code-and-installs/
474 *
475 * @uses WP_Filesystem
476 * @uses WP_Error
477 * @uses WP_Upgrader
478 * @uses Plugin_Upgrader
479 * @uses Plugin_Installer_Skin
480 * @uses Plugin_Upgrader_Skin
481 *
482 * @param number|bool $plugin_id
483 *
484 * @return array
485 */
486 function install_and_activate_plugin( $plugin_id = false ) {
487 if ( ! empty( $plugin_id ) && ! FS_Plugin::is_valid_id( $plugin_id ) ) {
488 // Invalid plugin ID.
489 return array(
490 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ),
491 'code' => 'invalid_module_id',
492 );
493 }
494
495 $is_addon = false;
496 if ( FS_Plugin::is_valid_id( $plugin_id ) &&
497 $plugin_id != $this->_fs->get_id()
498 ) {
499 $addon = $this->_fs->get_addon( $plugin_id );
500
501 if ( ! is_object( $addon ) ) {
502 // Invalid add-on ID.
503 return array(
504 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ),
505 'code' => 'invalid_module_id',
506 );
507 }
508
509 $slug = $addon->slug;
510 $title = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' );
511
512 $is_addon = true;
513 } else {
514 $slug = $this->_fs->get_slug();
515 $title = $this->_fs->get_plugin_title() .
516 ( $this->_fs->is_addon() ? ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ) : '' );
517 }
518
519 if ( $this->is_premium_plugin_active( $plugin_id ) ) {
520 // Premium version already activated.
521 return array(
522 'message' => $is_addon ?
523 $this->_fs->get_text_inline( 'Premium add-on version already installed.', 'auto-install-error-premium-addon-activated' ) :
524 $this->_fs->get_text_inline( 'Premium version already active.', 'auto-install-error-premium-activated' ),
525 'code' => 'premium_installed',
526 );
527 }
528
529 $latest_version = $this->get_latest_download_details( $plugin_id );
530 $target_folder = "{$slug}-premium";
531
532 // Prep variables for Plugin_Installer_Skin class.
533 $extra = array();
534 $extra['slug'] = $target_folder;
535 $source = $latest_version->url;
536 $api = null;
537
538 $install_url = add_query_arg(
539 array(
540 'action' => 'install-plugin',
541 'plugin' => urlencode( $slug ),
542 ),
543 'update.php'
544 );
545
546 if ( ! class_exists( 'Plugin_Upgrader', false ) ) {
547 // Include required resources for the installation.
548 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
549 }
550
551 $skin_args = array(
552 'type' => 'web',
553 'title' => sprintf( $this->_fs->get_text_inline( 'Installing plugin: %s', 'installing-plugin-x' ), $title ),
554 'url' => esc_url_raw( $install_url ),
555 'nonce' => 'install-plugin_' . $slug,
556 'plugin' => '',
557 'api' => $api,
558 'extra' => $extra,
559 );
560
561 // $skin = new Automatic_Upgrader_Skin( $skin_args );
562 // $skin = new Plugin_Installer_Skin( $skin_args );
563 $skin = new WP_Ajax_Upgrader_Skin( $skin_args );
564
565 // Create a new instance of Plugin_Upgrader.
566 $upgrader = new Plugin_Upgrader( $skin );
567
568 // Perform the action and install the plugin from the $source urldecode().
569 add_filter( 'upgrader_source_selection', array( &$this, '_maybe_adjust_source_dir' ), 1, 3 );
570
571 $install_result = $upgrader->install( $source );
572
573 remove_filter( 'upgrader_source_selection', array( &$this, '_maybe_adjust_source_dir' ), 1 );
574
575 if ( is_wp_error( $install_result ) ) {
576 return array(
577 'message' => $install_result->get_error_message(),
578 'code' => $install_result->get_error_code(),
579 );
580 } elseif ( is_wp_error( $skin->result ) ) {
581 return array(
582 'message' => $skin->result->get_error_message(),
583 'code' => $skin->result->get_error_code(),
584 );
585 } elseif ( $skin->get_errors()->get_error_code() ) {
586 return array(
587 'message' => $skin->get_error_messages(),
588 'code' => 'unknown',
589 );
590 } elseif ( is_null( $install_result ) ) {
591 global $wp_filesystem;
592
593 $error_code = 'unable_to_connect_to_filesystem';
594 $error_message = $this->_fs->get_text_inline( 'Unable to connect to the filesystem. Please confirm your credentials.' );
595
596 // Pass through the error from WP_Filesystem if one was raised.
597 if ( $wp_filesystem instanceof WP_Filesystem_Base &&
598 is_wp_error( $wp_filesystem->errors ) &&
599 $wp_filesystem->errors->get_error_code()
600 ) {
601 $error_message = $wp_filesystem->errors->get_error_message();
602 }
603
604 return array(
605 'message' => $error_message,
606 'code' => $error_code,
607 );
608 }
609
610 // Grab the full path to the main plugin's file.
611 $plugin_activate = $upgrader->plugin_info();
612
613 // Try to activate the plugin.
614 $activation_result = $this->try_activate_plugin( $plugin_activate );
615
616 if ( is_wp_error( $activation_result ) ) {
617 return array(
618 'message' => $activation_result->get_error_message(),
619 'code' => $activation_result->get_error_code(),
620 );
621 }
622
623 return $skin->get_upgrade_messages();
624 }
625
626 /**
627 * Tries to activate a plugin. If fails, returns the error.
628 *
629 * @author Vova Feldman
630 * @since 1.2.1.7
631 *
632 * @param string $file_path Path within wp-plugins/ to main plugin file.
633 * This determines the styling of the output messages.
634 *
635 * @return bool|WP_Error
636 */
637 protected function try_activate_plugin( $file_path ) {
638 $activate = activate_plugin( $file_path );
639
640 return is_wp_error( $activate ) ?
641 $activate :
642 true;
643 }
644
645 /**
646 * Check if a premium module version is already active.
647 *
648 * @author Vova Feldman
649 * @since 1.2.1.7
650 *
651 * @param number|bool $plugin_id
652 *
653 * @return bool
654 */
655 private function is_premium_plugin_active( $plugin_id = false ) {
656 if ( $plugin_id != $this->_fs->get_id() ) {
657 return $this->_fs->is_addon_activated( $plugin_id, true );
658 }
659
660 return is_plugin_active( $this->_fs->premium_plugin_basename() );
661 }
662
663 /**
664 * Adjust the plugin directory name if necessary.
665 * Assumes plugin has a folder (not a single file plugin).
666 *
667 * The final destination directory of a plugin is based on the subdirectory name found in the
668 * (un)zipped source. In some cases this subdirectory name is not the same as the expected
669 * slug and the plugin will not be recognized as installed. This is fixed by adjusting
670 * the temporary unzipped source subdirectory name to the expected plugin slug.
671 *
672 * @author Vova Feldman
673 * @since 1.2.1.7
674 *
675 * @param string $source Path to upgrade/zip-file-name.tmp/subdirectory/.
676 * @param string $remote_source Path to upgrade/zip-file-name.tmp.
677 * @param \WP_Upgrader $upgrader Instance of the upgrader which installs the plugin.
678 *
679 * @return string|WP_Error
680 */
681 function _maybe_adjust_source_dir( $source, $remote_source, $upgrader ) {
682 if ( ! is_object( $GLOBALS['wp_filesystem'] ) ) {
683 return $source;
684 }
685
686 // Figure out what the slug is supposed to be.
687 $desired_slug = $upgrader->skin->options['extra']['slug'];
688
689 $subdir_name = untrailingslashit( str_replace( trailingslashit( $remote_source ), '', $source ) );
690
691 if ( ! empty( $subdir_name ) && $subdir_name !== $desired_slug ) {
692 $from_path = untrailingslashit( $source );
693 $to_path = trailingslashit( $remote_source ) . $desired_slug;
694
695 if ( true === $GLOBALS['wp_filesystem']->move( $from_path, $to_path ) ) {
696 return trailingslashit( $to_path );
697 } else {
698 return new WP_Error(
699 'rename_failed',
700 $this->_fs->get_text_inline( 'The remote plugin package does not contain a folder with the desired slug and renaming did not work.', 'module-package-rename-failure' ),
701 array(
702 'found' => $subdir_name,
703 'expected' => $desired_slug
704 ) );
705 }
706 }
707
708 return $source;
709 }
710
711 #endregion
712 }