PluginProbe
Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder / 1.1.1
Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder v1.1.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.1.1, at freemius/includes/class-fs-plugin-updater.php

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