PluginProbe
StreamCast – bring live radio to your site with a sleek player / 2.3.2
StreamCast – bring live radio to your site with a sleek player v2.3.2
2.4.5 2.4.4 trunk 1.0 1.1 2.0.0 2.1.10 2.1.2 2.1.4 2.1.5 2.1.8 2.2.1 2.2.3 2.2.4 2.2.5 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.3.5 2.3.6 2.3.7 2.3.8 2.3.9 All 29 releases
streamcast / freemius / includes / class-fs-plugin-updater.php

class-fs-plugin-updater.php in StreamCast – bring live radio to your site with a sleek player 2.3.2, at freemius/includes/class-fs-plugin-updater.php

1,621 lines 64.1 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 class FS_Plugin_Updater {
16
17 /**
18 * @var Freemius
19 * @since 1.0.4
20 */
21 private $_fs;
22 /**
23 * @var FS_Logger
24 * @since 1.0.4
25 */
26 private $_logger;
27 /**
28 * @var object
29 * @since 1.1.8.1
30 */
31 private $_update_details;
32 /**
33 * @var array
34 * @since 2.1.2
35 */
36 private $_translation_updates;
37
38 private static $_upgrade_basename = null;
39
40 const UPDATES_CHECK_CACHE_EXPIRATION = ( WP_FS__TIME_24_HOURS_IN_SEC / 24 );
41
42 #--------------------------------------------------------------------------------
43 #region Singleton
44 #--------------------------------------------------------------------------------
45
46 /**
47 * @var FS_Plugin_Updater[]
48 * @since 2.0.0
49 */
50 private static $_INSTANCES = array();
51
52 /**
53 * @param Freemius $freemius
54 *
55 * @return FS_Plugin_Updater
56 */
57 static function instance( Freemius $freemius ) {
58 $key = $freemius->get_id();
59
60 if ( ! isset( self::$_INSTANCES[ $key ] ) ) {
61 self::$_INSTANCES[ $key ] = new self( $freemius );
62 }
63
64 return self::$_INSTANCES[ $key ];
65 }
66
67 #endregion
68
69 private function __construct( Freemius $freemius ) {
70 $this->_fs = $freemius;
71
72 $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $freemius->get_slug() . '_updater', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
73
74 $this->filters();
75 }
76
77 /**
78 * Initiate required filters.
79 *
80 * @author Vova Feldman (@svovaf)
81 * @since 1.0.4
82 */
83 private function filters() {
84 // Override request for plugin information
85 add_filter( 'plugins_api', array( &$this, 'plugins_api_filter' ), 10, 3 );
86
87 $this->add_transient_filters();
88
89 /**
90 * If user has the premium plugin's code but do NOT have an active license,
91 * encourage him to upgrade by showing that there's a new release, but instead
92 * of showing an update link, show upgrade link to the pricing page.
93 *
94 * @since 1.1.6
95 *
96 */
97 // WP 2.9+
98 add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array(
99 &$this,
100 'catch_plugin_update_row'
101 ), 9 );
102 add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array(
103 &$this,
104 'edit_and_echo_plugin_update_row'
105 ), 11, 2 );
106
107 if ( ! $this->_fs->has_any_active_valid_license() ) {
108 add_action( 'admin_head', array( &$this, 'catch_plugin_information_dialog_contents' ) );
109 } else {
110 add_action( 'admin_footer', array( &$this, '_add_fs_allow_updater_and_dialog_request_param' ) );
111 }
112
113 if ( ! WP_FS__IS_PRODUCTION_MODE ) {
114 add_filter( 'http_request_host_is_external', array(
115 $this,
116 'http_request_host_is_external_filter'
117 ), 10, 3 );
118 }
119
120 if ( $this->_fs->is_premium() ) {
121 if ( ! $this->is_correct_folder_name() ) {
122 add_filter( 'upgrader_post_install', array( &$this, '_maybe_update_folder_name' ), 10, 3 );
123 }
124
125 add_filter( 'upgrader_pre_install', array( 'FS_Plugin_Updater', '_store_basename_for_source_adjustment' ), 1, 2 );
126 add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 );
127
128 if ( ! $this->_fs->has_any_active_valid_license() ) {
129 add_filter( 'wp_prepare_themes_for_js', array( &$this, 'change_theme_update_info_html' ), 10, 1 );
130 }
131 }
132 }
133
134 /**
135 * @author Leo Fajardo (@leorw)
136 * @since 2.7.4
137 */
138 function _add_fs_allow_updater_and_dialog_request_param() {
139 if ( ! $this->is_plugin_information_dialog_for_plugin() ) {
140 return;
141 }
142 ?>
143 <script type="text/javascript">
144 if ( typeof jQuery !== 'undefined' ) {
145 jQuery( document ).on( 'wp-plugin-updating', function( event, args ) {
146 if ( typeof args === 'object' && args.slug && typeof args.slug === 'string' ) {
147 if ( <?php echo json_encode( $this->_fs->get_slug() ) ?> === args.slug ) {
148 args.fs_allow_updater_and_dialog = true;
149 }
150 }
151 } );
152 }
153 </script>
154 <?php
155 }
156
157 /**
158 * @author Leo Fajardo (@leorw)
159 * @since 2.7.4
160 *
161 * @return bool
162 */
163 private function is_plugin_information_dialog_for_plugin() {
164 return (
165 'plugin-information' === fs_request_get( 'tab', false ) &&
166 $this->_fs->get_slug() === fs_request_get_raw( 'plugin', false )
167 );
168 }
169
170 /**
171 * @author Leo Fajardo (@leorw)
172 * @since 2.1.4
173 */
174 function catch_plugin_information_dialog_contents() {
175 if ( ! $this->is_plugin_information_dialog_for_plugin() ) {
176 return;
177 }
178
179 add_action( 'admin_footer', array( &$this, 'edit_and_echo_plugin_information_dialog_contents' ), 0, 1 );
180
181 ob_start();
182 }
183
184 /**
185 * @author Leo Fajardo (@leorw)
186 * @since 2.1.4
187 *
188 * @param string $hook_suffix
189 */
190 function edit_and_echo_plugin_information_dialog_contents( $hook_suffix ) {
191 if (
192 'plugin-information' !== fs_request_get( 'tab', false ) ||
193 $this->_fs->get_slug() !== fs_request_get_raw( 'plugin', false )
194 ) {
195 return;
196 }
197
198 $license = $this->_fs->_get_license();
199
200 $subscription = ( is_object( $license ) && ! $license->is_lifetime() ) ?
201 $this->_fs->_get_subscription( $license->id ) :
202 null;
203
204 $contents = ob_get_clean();
205
206 $install_or_update_button_id_attribute_pos = strpos( $contents, 'id="plugin_install_from_iframe"' );
207
208 if ( false === $install_or_update_button_id_attribute_pos ) {
209 $install_or_update_button_id_attribute_pos = strpos( $contents, 'id="plugin_update_from_iframe"' );
210 }
211
212 if ( false !== $install_or_update_button_id_attribute_pos ) {
213 $install_or_update_button_start_pos = strrpos(
214 substr( $contents, 0, $install_or_update_button_id_attribute_pos ),
215 '<a'
216 );
217
218 $install_or_update_button_end_pos = ( strpos( $contents, '</a>', $install_or_update_button_id_attribute_pos ) + strlen( '</a>' ) );
219
220 /**
221 * The part of the contents without the update button.
222 *
223 * @author Leo Fajardo (@leorw)
224 * @since 2.2.5
225 */
226 $modified_contents = substr( $contents, 0, $install_or_update_button_start_pos );
227
228 $install_or_update_button = substr( $contents, $install_or_update_button_start_pos, ( $install_or_update_button_end_pos - $install_or_update_button_start_pos ) );
229
230 /**
231 * Replace the plugin information dialog's "Install Update Now" button's text and URL. If there's a license,
232 * the text will be "Renew license" and will link to the checkout page with the license's billing cycle
233 * and quota. If there's no license, the text will be "Buy license" and will link to the pricing page.
234 */
235 $install_or_update_button = preg_replace(
236 '/(\<a.+)(id="plugin_(install|update)_from_iframe")(.+href=")([^\s]+)(".*\>)(.+)(\<\/a>)/is',
237 is_object( $license ) ?
238 sprintf(
239 '$1$4%s$6%s$8',
240 $this->_fs->checkout_url(
241 is_object( $subscription ) ?
242 ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) :
243 WP_FS__PERIOD_LIFETIME,
244 false,
245 array( 'licenses' => $license->quota )
246 ),
247 fs_text_inline( 'Renew license', 'renew-license', $this->_fs->get_slug() )
248 ) :
249 sprintf(
250 '$1$4%s$6%s$8',
251 $this->_fs->pricing_url(),
252 fs_text_inline( 'Buy license', 'buy-license', $this->_fs->get_slug() )
253 ),
254 $install_or_update_button
255 );
256
257 /**
258 * Append the modified button.
259 *
260 * @author Leo Fajardo (@leorw)
261 * @since 2.2.5
262 */
263 $modified_contents .= $install_or_update_button;
264
265 /**
266 * Append the remaining part of the contents after the update button.
267 *
268 * @author Leo Fajardo (@leorw)
269 * @since 2.2.5
270 */
271 $modified_contents .= substr( $contents, $install_or_update_button_end_pos );
272
273 $contents = $modified_contents;
274 }
275
276 echo $contents;
277 }
278
279 /**
280 * @author Vova Feldman (@svovaf)
281 * @since 2.0.0
282 */
283 private function add_transient_filters() {
284 if (
285 $this->_fs->is_premium() &&
286 $this->_fs->is_registered() &&
287 ! FS_Permission_Manager::instance( $this->_fs )->is_essentials_tracking_allowed()
288 ) {
289 $this->_logger->log( 'Opted out sites cannot receive automatic software updates.' );
290
291 return;
292 }
293
294 add_filter( 'pre_set_site_transient_update_plugins', array(
295 &$this,
296 'pre_set_site_transient_update_plugins_filter'
297 ) );
298
299 add_filter( 'pre_set_site_transient_update_themes', array(
300 &$this,
301 'pre_set_site_transient_update_plugins_filter'
302 ) );
303 }
304
305 /**
306 * @author Vova Feldman (@svovaf)
307 * @since 2.0.0
308 */
309 private function remove_transient_filters() {
310 remove_filter( 'pre_set_site_transient_update_plugins', array(
311 &$this,
312 'pre_set_site_transient_update_plugins_filter'
313 ) );
314
315 remove_filter( 'pre_set_site_transient_update_themes', array(
316 &$this,
317 'pre_set_site_transient_update_plugins_filter'
318 ) );
319 }
320
321 /**
322 * Capture plugin update row by turning output buffering.
323 *
324 * @author Vova Feldman (@svovaf)
325 * @since 1.1.6
326 */
327 function catch_plugin_update_row() {
328 ob_start();
329 }
330
331 /**
332 * Overrides default update message format with "renew your license" message.
333 *
334 * @author Vova Feldman (@svovaf)
335 * @since 1.1.6
336 *
337 * @param string $file
338 * @param array $plugin_data
339 */
340 function edit_and_echo_plugin_update_row( $file, $plugin_data ) {
341 $plugin_update_row = ob_get_clean();
342
343 $current = get_site_transient( 'update_plugins' );
344 if ( ! isset( $current->response[ $file ] ) ) {
345 echo $plugin_update_row;
346
347 return;
348 }
349
350 $r = $current->response[ $file ];
351
352 $has_beta_update = $this->_fs->has_beta_update();
353
354 if ( $this->_fs->has_any_active_valid_license() ) {
355 if ( $has_beta_update ) {
356 /**
357 * Turn the "new version" text into "new Beta version".
358 *
359 * Sample input:
360 * There is a new version of Awesome Plugin available. <a href="...>View version x.y.z details</a> or <a href="...>update now</a>.
361 * Output:
362 * There is a new Beta version of Awesome Plugin available. <a href="...>View version x.y.z details</a> or <a href="...>update now</a>.
363 *
364 * @author Leo Fajardo (@leorw)
365 * @since 2.3.0
366 */
367 $plugin_update_row = preg_replace(
368 '/(\<div.+>)(.+)(\<a.+href="([^\s]+)"([^\<]+)\>.+\<a.+)(\<\/div\>)/is',
369 (
370 '$1' .
371 sprintf(
372 fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ),
373 $has_beta_update ?
374 fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) :
375 fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() ),
376 $this->_fs->get_plugin_title()
377 ) .
378 ' ' .
379 '$3' .
380 '$6'
381 ),
382 $plugin_update_row
383 );
384 }
385 } else {
386 /**
387 * Turn the "new version" text into a link that opens the plugin information dialog when clicked and
388 * make the "View version x details" text link to the checkout page instead of opening the plugin
389 * information dialog when clicked.
390 *
391 * Sample input:
392 * There is a new version of Awesome Plugin available. <a href="...>View version x.y.z details</a> or <a href="...>update now</a>.
393 * Output:
394 * There is a <a href="...>new version</a> of Awesome Plugin available. <a href="...>Buy a license now</a> to access version x.y.z security & feature updates, and support.
395 * OR
396 * There is a <a href="...>new Beta version</a> of Awesome Plugin available. <a href="...>Buy a license now</a> to access version x.y.z security & feature updates, and support.
397 *
398 * @author Leo Fajardo (@leorw)
399 */
400 $plugin_update_row = preg_replace(
401 '/(\<div.+>)(.+)(\<a.+href="([^\s]+)"([^\<]+)\>.+\<a.+)(\<\/div\>)/is',
402 (
403 '$1' .
404 sprintf(
405 fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ),
406 sprintf(
407 '<a href="$4"%s>%s</a>',
408 '$5',
409 $has_beta_update ?
410 fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) :
411 fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() )
412 ),
413 $this->_fs->get_plugin_title()
414 ) .
415 ' ' .
416 $this->_fs->version_upgrade_checkout_link( $r->new_version ) .
417 '$6'
418 ),
419 $plugin_update_row
420 );
421 }
422
423 if (
424 $this->_fs->is_plugin() &&
425 isset( $r->upgrade_notice ) &&
426 strlen( trim( $r->upgrade_notice ) ) > 0
427 ) {
428 $slug = $this->_fs->get_slug();
429
430 $upgrade_notice_html = sprintf(
431 '<p class="notice fs-upgrade-notice fs-slug-%1$s fs-type-%2$s" data-slug="%1$s" data-type="%2$s"><strong>%3$s</strong> %4$s</p>',
432 $slug,
433 $this->_fs->get_module_type(),
434 fs_text_inline( 'Important Upgrade Notice:', 'upgrade_notice', $slug ),
435 esc_html( $r->upgrade_notice )
436 );
437
438 $plugin_update_row = str_replace( '</div>', '</div>' . $upgrade_notice_html, $plugin_update_row );
439 }
440
441 echo $plugin_update_row;
442 }
443
444 /**
445 * @author Leo Fajardo (@leorw)
446 * @since 2.0.2
447 *
448 * @param array $prepared_themes
449 *
450 * @return array
451 */
452 function change_theme_update_info_html( $prepared_themes ) {
453 $theme_basename = $this->_fs->get_plugin_basename();
454
455 if ( ! isset( $prepared_themes[ $theme_basename ] ) ) {
456 return $prepared_themes;
457 }
458
459 $themes_update = get_site_transient( 'update_themes' );
460 if ( ! isset( $themes_update->response[ $theme_basename ] ) ||
461 empty( $themes_update->response[ $theme_basename ]['package'] )
462 ) {
463 return $prepared_themes;
464 }
465
466 $prepared_themes[ $theme_basename ]['update'] = preg_replace(
467 '/(\<p.+>)(.+)(\<a.+\<a.+)\.(.+\<\/p\>)/is',
468 '$1 $2 ' . $this->_fs->version_upgrade_checkout_link( $themes_update->response[ $theme_basename ]['new_version'] ) .
469 '$4',
470 $prepared_themes[ $theme_basename ]['update']
471 );
472
473 // Set to false to prevent the "Update now" link for the context theme from being shown on the "Themes" page.
474 $prepared_themes[ $theme_basename ]['hasPackage'] = false;
475
476 return $prepared_themes;
477 }
478
479 /**
480 * Since WP version 3.6, a new security feature was added that denies access to repository with a local ip.
481 * During development mode we want to be able updating plugin versions via our localhost repository. This
482 * filter white-list all domains including "api.freemius".
483 *
484 * @link http://www.emanueletessore.com/wordpress-download-failed-valid-url-provided/
485 *
486 * @author Vova Feldman (@svovaf)
487 * @since 1.0.4
488 *
489 * @param bool $allow
490 * @param string $host
491 * @param string $url
492 *
493 * @return bool
494 */
495 function http_request_host_is_external_filter( $allow, $host, $url ) {
496 return ( false !== strpos( $host, 'freemius' ) ) ? true : $allow;
497 }
498
499 /**
500 * Check for Updates at the defined API endpoint and modify the update array.
501 *
502 * This function dives into the update api just when WordPress creates its update array,
503 * then adds a custom API call and injects the custom plugin data retrieved from the API.
504 * It is reassembled from parts of the native WordPress plugin update code.
505 * See wp-includes/update.php line 121 for the original wp_update_plugins() function.
506 *
507 * @author Vova Feldman (@svovaf)
508 * @since 1.0.4
509 *
510 * @uses FS_Api
511 *
512 * @param object $transient_data Update array build by WordPress.
513 *
514 * @return object Modified update array with custom plugin data.
515 */
516 function pre_set_site_transient_update_plugins_filter( $transient_data ) {
517 $this->_logger->entrance();
518
519 /**
520 * "plugins" or "themes".
521 *
522 * @author Leo Fajardo (@leorw)
523 * @since 1.2.2
524 */
525 $module_type = $this->_fs->get_module_type() . 's';
526
527 /**
528 * Ensure that we don't mix plugins update info with themes update info.
529 *
530 * @author Leo Fajardo (@leorw)
531 * @since 1.2.2
532 */
533 if ( "pre_set_site_transient_update_{$module_type}" !== current_filter() ) {
534 return $transient_data;
535 }
536
537 if ( empty( $transient_data ) ||
538 defined( 'WP_FS__UNINSTALL_MODE' )
539 ) {
540 return $transient_data;
541 }
542
543 global $wp_current_filter;
544
545 if ( ! empty( $wp_current_filter ) && in_array( 'upgrader_process_complete', $wp_current_filter ) ) {
546 return $transient_data;
547 }
548
549 if ( ! isset( $this->_update_details ) ) {
550 // Get plugin's newest update.
551 $new_version = $this->_fs->get_update(
552 false,
553 fs_request_get_bool( 'force-check' ),
554 FS_Plugin_Updater::UPDATES_CHECK_CACHE_EXPIRATION,
555 $this->_fs->get_plugin_version()
556 );
557
558 $this->_update_details = false;
559
560 if ( is_object( $new_version ) && $this->is_new_version_premium( $new_version ) ) {
561 $this->_logger->log( 'Found newer plugin version ' . $new_version->version );
562
563 /**
564 * Cache plugin details locally since set_site_transient( 'update_plugins' )
565 * called multiple times and the non wp.org plugins are filtered after the
566 * call to .org.
567 *
568 * @since 1.1.8.1
569 */
570 $this->_update_details = $this->get_update_details( $new_version );
571 }
572 }
573
574 // Alias.
575 $basename = $this->_fs->premium_plugin_basename();
576
577 if ( is_object( $this->_update_details ) ) {
578 if ( isset( $transient_data->no_update ) ) {
579 unset( $transient_data->no_update[ $basename ] );
580 }
581
582 if ( ! isset( $transient_data->response ) ) {
583 $transient_data->response = array();
584 }
585
586 // Add plugin to transient data.
587 $transient_data->response[ $basename ] = $this->_fs->is_plugin() ?
588 $this->_update_details :
589 (array) $this->_update_details;
590 } else {
591 if ( isset( $transient_data->response ) ) {
592 /**
593 * Ensure that there's no update data for the plugin to prevent upgrading the premium version to the latest free version.
594 *
595 * @author Leo Fajardo (@leorw)
596 * @since 2.3.0
597 */
598 unset( $transient_data->response[ $basename ] );
599 }
600
601 if ( ! isset( $transient_data->no_update ) ) {
602 $transient_data->no_update = array();
603 }
604
605 /**
606 * Add product to no_update transient data to properly integrate with WP 5.5 auto-updates UI.
607 *
608 * @since 2.4.1
609 * @link https://make.wordpress.org/core/2020/07/30/recommended-usage-of-the-updates-api-to-support-the-auto-updates-ui-for-plugins-and-themes-in-wordpress-5-5/
610 */
611 $transient_data->no_update[ $basename ] = $this->_fs->is_plugin() ?
612 (object) array(
613 'id' => $basename,
614 'slug' => $this->_fs->get_slug(),
615 'plugin' => $basename,
616 'new_version' => $this->_fs->get_plugin_version(),
617 'url' => '',
618 'package' => '',
619 'icons' => array(),
620 'banners' => array(),
621 'banners_rtl' => array(),
622 'tested' => '',
623 'requires_php' => '',
624 'compatibility' => new stdClass(),
625 ) :
626 array(
627 'theme' => $basename,
628 'new_version' => $this->_fs->get_plugin_version(),
629 'url' => '',
630 'package' => '',
631 'requires' => '',
632 'requires_php' => '',
633 );
634 }
635
636 $slug = $this->_fs->get_slug();
637
638 if ( $this->can_fetch_data_from_wp_org() ) {
639 if ( ! isset( $this->_translation_updates ) ) {
640 $this->_translation_updates = array();
641
642 $translation_updates = $this->fetch_wp_org_module_translation_updates( $module_type, $slug );
643 if ( ! empty( $translation_updates ) ) {
644 $this->_translation_updates = $translation_updates;
645 }
646 }
647
648 if ( ! empty( $this->_translation_updates ) ) {
649 $all_translation_updates = ( isset( $transient_data->translations ) && is_array( $transient_data->translations ) ) ?
650 $transient_data->translations :
651 array();
652
653 $current_plugin_translation_updates_map = array();
654 foreach ( $all_translation_updates as $key => $translation_update ) {
655 if ( $module_type === ( $translation_update['type'] . 's' ) && $slug === $translation_update['slug'] ) {
656 $current_plugin_translation_updates_map[ $translation_update['language'] ] = $translation_update;
657 unset( $all_translation_updates[ $key ] );
658 }
659 }
660
661 foreach ( $this->_translation_updates as $translation_update ) {
662 $lang = $translation_update['language'];
663 if ( ! isset( $current_plugin_translation_updates_map[ $lang ] ) ||
664 version_compare( $translation_update['version'], $current_plugin_translation_updates_map[ $lang ]['version'], '>' )
665 ) {
666 $current_plugin_translation_updates_map[ $lang ] = $translation_update;
667 }
668 }
669
670 $transient_data->translations = array_merge( $all_translation_updates, array_values( $current_plugin_translation_updates_map ) );
671 }
672 }
673
674 return $transient_data;
675 }
676
677 /**
678 * Get module's required data for the updates' mechanism.
679 *
680 * @author Vova Feldman (@svovaf)
681 * @since 2.0.0
682 *
683 * @param \FS_Plugin_Tag $new_version
684 *
685 * @return object
686 */
687 function get_update_details( FS_Plugin_Tag $new_version ) {
688 $update = new stdClass();
689 $update->slug = $this->_fs->get_slug();
690 $update->new_version = $new_version->version;
691 $update->url = WP_FS__ADDRESS;
692 $update->package = $new_version->url;
693 $update->tested = self::get_tested_wp_version( $new_version->tested_up_to_version );
694 $update->requires = $new_version->requires_platform_version;
695 $update->requires_php = $new_version->requires_programming_language_version;
696
697 $icon = $this->_fs->get_local_icon_url();
698
699 if ( ! empty( $icon ) ) {
700 $update->icons = array(
701 // '1x' => $icon,
702 // '2x' => $icon,
703 'default' => $icon,
704 );
705 }
706
707 if ( $this->_fs->is_premium() ) {
708 $latest_tag = $this->_fs->_fetch_latest_version( $this->_fs->get_id(), false );
709
710 if (
711 isset( $latest_tag->readme ) &&
712 isset( $latest_tag->readme->upgrade_notice ) &&
713 ! empty( $latest_tag->readme->upgrade_notice )
714 ) {
715 $update->upgrade_notice = $latest_tag->readme->upgrade_notice;
716 }
717 }
718
719 $update->{$this->_fs->get_module_type()} = $this->_fs->get_plugin_basename();
720
721 return $update;
722 }
723
724 /**
725 * @author Leo Fajardo (@leorw)
726 * @since 2.3.0
727 *
728 * @param FS_Plugin_Tag $new_version
729 *
730 * @return bool
731 */
732 private function is_new_version_premium( FS_Plugin_Tag $new_version ) {
733 $params = fs_parse_url_params( $new_version->url );
734
735 return ( isset( $params['is_premium'] ) && 'true' == $params['is_premium'] );
736 }
737
738 /**
739 * Update the updates transient with the module's update information.
740 *
741 * This method is required for multisite environment.
742 * If a module is site activated (not network) and not on the main site,
743 * the module will NOT be executed on the network level, therefore, the
744 * custom updates logic will not be executed as well, so unless we force
745 * the injection of the update into the updates transient, premium updates
746 * will not work.
747 *
748 * @author Vova Feldman (@svovaf)
749 * @since 2.0.0
750 *
751 * @param \FS_Plugin_Tag $new_version
752 */
753 function set_update_data( FS_Plugin_Tag $new_version ) {
754 $this->_logger->entrance();
755
756 if ( ! $this->is_new_version_premium( $new_version ) ) {
757 return;
758 }
759
760 $transient_key = "update_{$this->_fs->get_module_type()}s";
761
762 $transient_data = get_site_transient( $transient_key );
763
764 $transient_data = is_object( $transient_data ) ?
765 $transient_data :
766 new stdClass();
767
768 // Alias.
769 $basename = $this->_fs->get_plugin_basename();
770 $is_plugin = $this->_fs->is_plugin();
771
772 if ( ! isset( $transient_data->response ) ||
773 ! is_array( $transient_data->response )
774 ) {
775 $transient_data->response = array();
776 } else if ( ! empty( $transient_data->response[ $basename ] ) ) {
777 $version = $is_plugin ?
778 ( ! empty( $transient_data->response[ $basename ]->new_version ) ?
779 $transient_data->response[ $basename ]->new_version :
780 null
781 ) : ( ! empty( $transient_data->response[ $basename ]['new_version'] ) ?
782 $transient_data->response[ $basename ]['new_version'] :
783 null
784 );
785
786 if ( $version == $new_version->version ) {
787 // The update data is already set.
788 return;
789 }
790 }
791
792 // Remove the added filters.
793 $this->remove_transient_filters();
794
795 $this->_update_details = $this->get_update_details( $new_version );
796
797 // Set update data in transient.
798 $transient_data->response[ $basename ] = $is_plugin ?
799 $this->_update_details :
800 (array) $this->_update_details;
801
802 if ( ! isset( $transient_data->checked ) ||
803 ! is_array( $transient_data->checked )
804 ) {
805 $transient_data->checked = array();
806 }
807
808 // Flag the module as if it was already checked.
809 $transient_data->checked[ $basename ] = $this->_fs->get_plugin_version();
810 $transient_data->last_checked = time();
811
812 set_site_transient( $transient_key, $transient_data );
813
814 $this->add_transient_filters();
815 }
816
817 /**
818 * @author Leo Fajardo (@leorw)
819 * @since 2.0.2
820 */
821 function delete_update_data() {
822 $this->_logger->entrance();
823
824 $transient_key = "update_{$this->_fs->get_module_type()}s";
825
826 $transient_data = get_site_transient( $transient_key );
827
828 // Alias
829 $basename = $this->_fs->get_plugin_basename();
830
831 if ( ! is_object( $transient_data ) ||
832 ! isset( $transient_data->response ) ||
833 ! is_array( $transient_data->response ) ||
834 empty( $transient_data->response[ $basename ] )
835 ) {
836 return;
837 }
838
839 unset( $transient_data->response[ $basename ] );
840
841 // Remove the added filters.
842 $this->remove_transient_filters();
843
844 set_site_transient( $transient_key, $transient_data );
845
846 $this->add_transient_filters();
847 }
848
849 /**
850 * Try to fetch plugin's info from .org repository.
851 *
852 * @author Vova Feldman (@svovaf)
853 * @since 1.0.5
854 *
855 * @param string $action
856 * @param object $args
857 *
858 * @return bool|mixed
859 */
860 static function _fetch_plugin_info_from_repository( $action, $args ) {
861 $url = $http_url = 'http://api.wordpress.org/plugins/info/1.2/';
862 $url = add_query_arg(
863 array(
864 'action' => $action,
865 'request' => $args,
866 ),
867 $url
868 );
869
870 if ( wp_http_supports( array( 'ssl' ) ) ) {
871 $url = set_url_scheme( $url, 'https' );
872 }
873
874 // The new endpoint version serves only GET requests.
875 $request = wp_remote_get( $url, array( 'timeout' => 15 ) );
876
877 if ( is_wp_error( $request ) ) {
878 return false;
879 }
880
881 $res = json_decode( wp_remote_retrieve_body( $request ), true );
882
883 if ( is_array( $res ) ) {
884 // Object casting is required in order to match the info/1.0 format. We are not decoding directly into an object as we need some fields to remain an array (e.g., $res->sections).
885 $res = (object) $res;
886 }
887
888 if ( ! is_object( $res ) || isset( $res->error ) ) {
889 return false;
890 }
891
892 return $res;
893 }
894
895 /**
896 * Returns true if the product can fetch data from WordPress.org.
897 *
898 * @author Leo Fajardo (@leorw)
899 * @since 2.7.4
900 */
901 private function can_fetch_data_from_wp_org() {
902 return ( $this->_fs->is_org_repo_compliant() && $this->_fs->is_freemium() );
903 }
904
905 /**
906 * Fetches module translation updates from wordpress.org.
907 *
908 * @author Leo Fajardo (@leorw)
909 * @since 2.1.2
910 *
911 * @param string $module_type
912 * @param string $slug
913 *
914 * @return array|null
915 */
916 private function fetch_wp_org_module_translation_updates( $module_type, $slug ) {
917 $plugin_data = $this->_fs->get_plugin_data();
918
919 $locales = array_values( get_available_languages() );
920 $locales = apply_filters( "{$module_type}_update_check_locales", $locales );
921 $locales = array_unique( $locales );
922
923 $plugin_basename = $this->_fs->get_plugin_basename();
924 if ( 'themes' === $module_type ) {
925 $plugin_basename = $slug;
926 }
927
928 global $wp_version;
929
930 $request_args = array(
931 'timeout' => 15,
932 'body' => array(
933 "{$module_type}" => json_encode(
934 array(
935 "{$module_type}" => array(
936 $plugin_basename => array(
937 'Name' => trim( str_replace( $this->_fs->get_plugin()->premium_suffix, '', $plugin_data['Name'] ) ),
938 'Author' => $plugin_data['Author'],
939 )
940 )
941 )
942 ),
943 'translations' => json_encode( $this->get_installed_translations( $module_type, $slug ) ),
944 'locale' => json_encode( $locales )
945 ),
946 'user-agent' => ( 'WordPress/' . $wp_version . '; ' . home_url( '/' ) )
947 );
948
949 $url = "http://api.wordpress.org/{$module_type}/update-check/1.1/";
950 if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) {
951 $url = set_url_scheme( $url, 'https' );
952 }
953
954 $raw_response = Freemius::safe_remote_post(
955 $url,
956 $request_args,
957 WP_FS__TIME_24_HOURS_IN_SEC,
958 WP_FS__TIME_12_HOURS_IN_SEC,
959 false
960 );
961
962 if ( is_wp_error( $raw_response ) ) {
963 return null;
964 }
965
966 $response = json_decode( wp_remote_retrieve_body( $raw_response ), true );
967
968 if ( ! is_array( $response ) ) {
969 return null;
970 }
971
972 if ( ! isset( $response['translations'] ) || empty( $response['translations'] ) ) {
973 return null;
974 }
975
976 return $response['translations'];
977 }
978
979 /**
980 * @author Leo Fajardo (@leorw)
981 * @since 2.1.2
982 *
983 * @param string $module_type
984 * @param string $slug
985 *
986 * @return array
987 */
988 private function get_installed_translations( $module_type, $slug ) {
989 if ( function_exists( 'wp_get_installed_translations' ) ) {
990 return wp_get_installed_translations( $module_type );
991 }
992
993 $dir = "/{$module_type}";
994
995 if ( ! is_dir( WP_LANG_DIR . $dir ) )
996 return array();
997
998 $files = scandir( WP_LANG_DIR . $dir );
999 if ( ! $files )
1000 return array();
1001
1002 $language_data = array();
1003
1004 foreach ( $files as $file ) {
1005 if ( 0 !== strpos( $file, $slug ) ) {
1006 continue;
1007 }
1008
1009 if ( '.' === $file[0] || is_dir( WP_LANG_DIR . "{$dir}/{$file}" ) ) {
1010 continue;
1011 }
1012
1013 if ( substr( $file, -3 ) !== '.po' ) {
1014 continue;
1015 }
1016
1017 if ( ! preg_match( '/(?:(.+)-)?([a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?).po/', $file, $match ) ) {
1018 continue;
1019 }
1020
1021 if ( ! in_array( substr( $file, 0, -3 ) . '.mo', $files ) ) {
1022 continue;
1023 }
1024
1025 list( , $textdomain, $language ) = $match;
1026
1027 if ( '' === $textdomain ) {
1028 $textdomain = 'default';
1029 }
1030
1031 $language_data[ $textdomain ][ $language ] = wp_get_pomo_file_data( WP_LANG_DIR . "{$dir}/{$file}" );
1032 }
1033
1034 return $language_data;
1035 }
1036
1037 /**
1038 * Updates information on the "View version x.x details" page with custom data.
1039 *
1040 * @author Vova Feldman (@svovaf)
1041 * @since 1.0.4
1042 *
1043 * @uses FS_Api
1044 *
1045 * @param object $data
1046 * @param string $action
1047 * @param mixed $args
1048 *
1049 * @return object
1050 */
1051 function plugins_api_filter( $data, $action = '', $args = null ) {
1052 $this->_logger->entrance();
1053
1054 if ( ( 'plugin_information' !== $action ) ||
1055 ! isset( $args->slug )
1056 ) {
1057 return $data;
1058 }
1059
1060 $addon = false;
1061 $is_addon = false;
1062 $addon_version = false;
1063
1064 if ( $this->_fs->get_slug() !== $args->slug ) {
1065 $addon = $this->_fs->get_addon_by_slug( $args->slug );
1066
1067 if ( ! is_object( $addon ) ) {
1068 return $data;
1069 }
1070
1071 if ( $this->_fs->is_addon_activated( $addon->id ) ) {
1072 $addon_version = $this->_fs->get_addon_instance( $addon->id )->get_plugin_version();
1073 } else if ( $this->_fs->is_addon_installed( $addon->id ) ) {
1074 $addon_plugin_data = get_plugin_data(
1075 ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $addon->id ) ),
1076 false,
1077 false
1078 );
1079
1080 if ( ! empty( $addon_plugin_data ) ) {
1081 $addon_version = $addon_plugin_data['Version'];
1082 }
1083 }
1084
1085 $is_addon = true;
1086 }
1087
1088 $plugin_in_repo = false;
1089 if ( ! $is_addon && $this->can_fetch_data_from_wp_org() ) {
1090 // Try to fetch info from .org repository.
1091 $data = self::_fetch_plugin_info_from_repository( $action, $args );
1092
1093 $plugin_in_repo = ( false !== $data );
1094 }
1095
1096 if ( ! $plugin_in_repo ) {
1097 $data = $args;
1098
1099 // Fetch as much as possible info from local files.
1100 $plugin_local_data = $this->_fs->get_plugin_data();
1101 $data->name = $plugin_local_data['Name'];
1102 $data->author = $plugin_local_data['Author'];
1103 $data->sections = array(
1104 'description' => 'Upgrade ' . $plugin_local_data['Name'] . ' to latest.',
1105 );
1106
1107 // @todo Store extra plugin info on Freemius or parse readme.txt markup.
1108 /*$info = $this->_fs->get_api_site_scope()->call('/information.json');
1109
1110 if ( !isset($info->error) ) {
1111 $data = $info;
1112 }*/
1113 }
1114
1115 $plugin_version = $is_addon ?
1116 $addon_version :
1117 $this->_fs->get_plugin_version();
1118
1119 // Get plugin's newest update.
1120 $new_version = $this->get_latest_download_details( $is_addon ? $addon->id : false, $plugin_version );
1121
1122 if ( ! is_object( $new_version ) || empty( $new_version->version ) ) {
1123 $data->version = $plugin_version;
1124 } else {
1125 if ( $is_addon ) {
1126 $data->name = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' );
1127 $data->slug = $addon->slug;
1128 $data->url = WP_FS__ADDRESS;
1129 $data->package = $new_version->url;
1130 }
1131
1132 if ( ! $plugin_in_repo ) {
1133 $data->last_updated = ! is_null( $new_version->updated ) ? $new_version->updated : $new_version->created;
1134 $data->requires = $new_version->requires_platform_version;
1135 $data->requires_php = $new_version->requires_programming_language_version;
1136 $data->tested = $new_version->tested_up_to_version;
1137 }
1138
1139 $data->version = $new_version->version;
1140 $data->download_link = $new_version->url;
1141
1142 if ( isset( $new_version->readme ) && is_object( $new_version->readme ) ) {
1143 $new_version_readme_data = $new_version->readme;
1144 if ( isset( $new_version_readme_data->sections ) ) {
1145 $new_version_readme_data->sections = (array) $new_version_readme_data->sections;
1146 } else {
1147 $new_version_readme_data->sections = array();
1148 }
1149
1150 if ( isset( $data->sections ) ) {
1151 if ( isset( $data->sections['screenshots'] ) ) {
1152 $new_version_readme_data->sections['screenshots'] = $data->sections['screenshots'];
1153 }
1154
1155 if ( isset( $data->sections['reviews'] ) ) {
1156 $new_version_readme_data->sections['reviews'] = $data->sections['reviews'];
1157 }
1158 }
1159
1160 if ( isset( $new_version_readme_data->banners ) ) {
1161 $new_version_readme_data->banners = (array) $new_version_readme_data->banners;
1162 } else if ( isset( $data->banners ) ) {
1163 $new_version_readme_data->banners = $data->banners;
1164 }
1165
1166 $wp_org_sections = array(
1167 'author',
1168 'author_profile',
1169 'rating',
1170 'ratings',
1171 'num_ratings',
1172 'support_threads',
1173 'support_threads_resolved',
1174 'active_installs',
1175 'added',
1176 'homepage'
1177 );
1178
1179 foreach ( $wp_org_sections as $wp_org_section ) {
1180 if ( isset( $data->{$wp_org_section} ) ) {
1181 $new_version_readme_data->{$wp_org_section} = $data->{$wp_org_section};
1182 }
1183 }
1184
1185 $data = $new_version_readme_data;
1186 }
1187 }
1188
1189 if ( ! empty( $data->tested ) ) {
1190 $data->tested = self::get_tested_wp_version( $data->tested );
1191 }
1192
1193 return $data;
1194 }
1195
1196 /**
1197 * @since 2.5.3 If the current WordPress version is a patch of the tested version (e.g., 6.1.2 is a patch of 6.1), then override the tested version with the patch so developers won't need to release a new version just to bump the latest supported WP version.
1198 *
1199 * @param string|null $tested_up_to
1200 *
1201 * @return string|null
1202 */
1203 private static function get_tested_wp_version( $tested_up_to ) {
1204 $current_wp_version = get_bloginfo( 'version' );
1205
1206 return ( ! empty($tested_up_to) && fs_starts_with( $current_wp_version, $tested_up_to . '.' ) ) ?
1207 $current_wp_version :
1208 $tested_up_to;
1209 }
1210
1211 /**
1212 * @author Vova Feldman (@svovaf)
1213 * @since 1.2.1.7
1214 *
1215 * @param number|bool $addon_id
1216 * @param bool|string $newer_than Since 2.2.1
1217 * @param bool|string $fetch_readme Since 2.2.1
1218 *
1219 * @return object
1220 */
1221 private function get_latest_download_details( $addon_id = false, $newer_than = false, $fetch_readme = true ) {
1222 return $this->_fs->_fetch_latest_version( $addon_id, true, FS_Plugin_Updater::UPDATES_CHECK_CACHE_EXPIRATION, $newer_than, $fetch_readme );
1223 }
1224
1225 /**
1226 * Checks if a given basename has a matching folder name
1227 * with the current context plugin.
1228 *
1229 * @author Vova Feldman (@svovaf)
1230 * @since 1.2.1.6
1231 *
1232 * @return bool
1233 */
1234 private function is_correct_folder_name() {
1235 return ( $this->_fs->get_target_folder_name() == trim( dirname( $this->_fs->get_plugin_basename() ), '/\\' ) );
1236 }
1237
1238 /**
1239 * This is a special after upgrade handler for migrating modules
1240 * that didn't use the '-premium' suffix folder structure before
1241 * the migration.
1242 *
1243 * @author Vova Feldman (@svovaf)
1244 * @since 1.2.1.6
1245 *
1246 * @param bool $response Install response.
1247 * @param array $hook_extra Extra arguments passed to hooked filters.
1248 * @param array $result Installation result data.
1249 *
1250 * @return bool
1251 */
1252 function _maybe_update_folder_name( $response, $hook_extra, $result ) {
1253 $basename = $this->_fs->get_plugin_basename();
1254
1255 if ( true !== $response ||
1256 empty( $hook_extra ) ||
1257 empty( $hook_extra['plugin'] ) ||
1258 $basename !== $hook_extra['plugin']
1259 ) {
1260 return $response;
1261 }
1262
1263 $active_plugins_basenames = get_option( 'active_plugins' );
1264
1265 foreach ( $active_plugins_basenames as $key => $active_plugin_basename ) {
1266 if ( $basename === $active_plugin_basename ) {
1267 // Get filename including extension.
1268 $filename = basename( $basename );
1269
1270 $new_basename = plugin_basename(
1271 trailingslashit( $this->_fs->is_premium() ? $this->_fs->get_premium_slug() : $this->_fs->get_slug() ) .
1272 $filename
1273 );
1274
1275 // Verify that the expected correct path exists.
1276 if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $new_basename ) ) ) {
1277 // Override active plugin name.
1278 $active_plugins_basenames[ $key ] = $new_basename;
1279 update_option( 'active_plugins', $active_plugins_basenames );
1280 }
1281
1282 break;
1283 }
1284 }
1285
1286 return $response;
1287 }
1288
1289 #----------------------------------------------------------------------------------
1290 #region Auto Activation
1291 #----------------------------------------------------------------------------------
1292
1293 /**
1294 * Installs and active a plugin when explicitly requested that from a 3rd party service.
1295 *
1296 * This logic was inspired by the TGMPA GPL licensed library by Thomas Griffin.
1297 *
1298 * @link http://tgmpluginactivation.com/
1299 *
1300 * @author Vova Feldman
1301 * @since 1.2.1.7
1302 *
1303 * @link https://make.wordpress.org/plugins/2017/03/16/clarification-of-guideline-8-executable-code-and-installs/
1304 *
1305 * @uses WP_Filesystem
1306 * @uses WP_Error
1307 * @uses WP_Upgrader
1308 * @uses Plugin_Upgrader
1309 * @uses Plugin_Installer_Skin
1310 * @uses Plugin_Upgrader_Skin
1311 *
1312 * @param number|bool $plugin_id
1313 *
1314 * @return array
1315 */
1316 function install_and_activate_plugin( $plugin_id = false ) {
1317 if ( ! empty( $plugin_id ) && ! FS_Plugin::is_valid_id( $plugin_id ) ) {
1318 // Invalid plugin ID.
1319 return array(
1320 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ),
1321 'code' => 'invalid_module_id',
1322 );
1323 }
1324
1325 $is_addon = false;
1326 if ( FS_Plugin::is_valid_id( $plugin_id ) &&
1327 $plugin_id != $this->_fs->get_id()
1328 ) {
1329 $addon = $this->_fs->get_addon( $plugin_id );
1330
1331 if ( ! is_object( $addon ) ) {
1332 // Invalid add-on ID.
1333 return array(
1334 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ),
1335 'code' => 'invalid_module_id',
1336 );
1337 }
1338
1339 $slug = $addon->slug;
1340 $premium_slug = $addon->premium_slug;
1341 $title = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' );
1342
1343 $is_addon = true;
1344 } else {
1345 $slug = $this->_fs->get_slug();
1346 $premium_slug = $this->_fs->get_premium_slug();
1347 $title = $this->_fs->get_plugin_title() .
1348 ( $this->_fs->is_addon() ? ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ) : '' );
1349 }
1350
1351 if ( $this->is_premium_plugin_active( $plugin_id ) ) {
1352 // Premium version already activated.
1353 return array(
1354 'message' => $is_addon ?
1355 $this->_fs->get_text_inline( 'Premium add-on version already installed.', 'auto-install-error-premium-addon-activated' ) :
1356 $this->_fs->get_text_inline( 'Premium version already active.', 'auto-install-error-premium-activated' ),
1357 'code' => 'premium_installed',
1358 );
1359 }
1360
1361 $latest_version = $this->get_latest_download_details( $plugin_id, false, false );
1362 $target_folder = $premium_slug;
1363
1364 // Prep variables for Plugin_Installer_Skin class.
1365 $extra = array();
1366 $extra['slug'] = $target_folder;
1367 $source = $latest_version->url;
1368 $api = null;
1369
1370 $install_url = add_query_arg(
1371 array(
1372 'action' => 'install-plugin',
1373 'plugin' => urlencode( $slug ),
1374 ),
1375 'update.php'
1376 );
1377
1378 if ( ! class_exists( 'Plugin_Upgrader', false ) ) {
1379 // Include required resources for the installation.
1380 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
1381 }
1382
1383 $skin_args = array(
1384 'type' => 'web',
1385 'title' => sprintf( $this->_fs->get_text_inline( 'Installing plugin: %s', 'installing-plugin-x' ), $title ),
1386 'url' => esc_url_raw( $install_url ),
1387 'nonce' => 'install-plugin_' . $slug,
1388 'plugin' => '',
1389 'api' => $api,
1390 'extra' => $extra,
1391 );
1392
1393 // $skin = new Automatic_Upgrader_Skin( $skin_args );
1394 // $skin = new Plugin_Installer_Skin( $skin_args );
1395 $skin = new WP_Ajax_Upgrader_Skin( $skin_args );
1396
1397 // Create a new instance of Plugin_Upgrader.
1398 $upgrader = new Plugin_Upgrader( $skin );
1399
1400 // Perform the action and install the plugin from the $source urldecode().
1401 add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 );
1402
1403 $install_result = $upgrader->install( $source );
1404
1405 remove_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1 );
1406
1407 if ( is_wp_error( $install_result ) ) {
1408 return array(
1409 'message' => $install_result->get_error_message(),
1410 'code' => $install_result->get_error_code(),
1411 );
1412 } elseif ( is_wp_error( $skin->result ) ) {
1413 return array(
1414 'message' => $skin->result->get_error_message(),
1415 'code' => $skin->result->get_error_code(),
1416 );
1417 } elseif ( $skin->get_errors()->get_error_code() ) {
1418 return array(
1419 'message' => $skin->get_error_messages(),
1420 'code' => 'unknown',
1421 );
1422 } elseif ( is_null( $install_result ) ) {
1423 global $wp_filesystem;
1424
1425 $error_code = 'unable_to_connect_to_filesystem';
1426 $error_message = $this->_fs->get_text_inline( 'Unable to connect to the filesystem. Please confirm your credentials.' );
1427
1428 // Pass through the error from WP_Filesystem if one was raised.
1429 if ( $wp_filesystem instanceof WP_Filesystem_Base &&
1430 is_wp_error( $wp_filesystem->errors ) &&
1431 $wp_filesystem->errors->get_error_code()
1432 ) {
1433 $error_message = $wp_filesystem->errors->get_error_message();
1434 }
1435
1436 return array(
1437 'message' => $error_message,
1438 'code' => $error_code,
1439 );
1440 }
1441
1442 // Grab the full path to the main plugin's file.
1443 $plugin_activate = $upgrader->plugin_info();
1444
1445 // Try to activate the plugin.
1446 $activation_result = $this->try_activate_plugin( $plugin_activate );
1447
1448 if ( is_wp_error( $activation_result ) ) {
1449 return array(
1450 'message' => $activation_result->get_error_message(),
1451 'code' => $activation_result->get_error_code(),
1452 );
1453 }
1454
1455 return $skin->get_upgrade_messages();
1456 }
1457
1458 /**
1459 * Tries to activate a plugin. If fails, returns the error.
1460 *
1461 * @author Vova Feldman
1462 * @since 1.2.1.7
1463 *
1464 * @param string $file_path Path within wp-plugins/ to main plugin file.
1465 * This determines the styling of the output messages.
1466 *
1467 * @return bool|WP_Error
1468 */
1469 protected function try_activate_plugin( $file_path ) {
1470 $activate = activate_plugin( $file_path, '', $this->_fs->is_network_active() );
1471
1472 return is_wp_error( $activate ) ?
1473 $activate :
1474 true;
1475 }
1476
1477 /**
1478 * Check if a premium module version is already active.
1479 *
1480 * @author Vova Feldman
1481 * @since 1.2.1.7
1482 *
1483 * @param number|bool $plugin_id
1484 *
1485 * @return bool
1486 */
1487 private function is_premium_plugin_active( $plugin_id = false ) {
1488 if ( $plugin_id != $this->_fs->get_id() ) {
1489 return $this->_fs->is_addon_activated( $plugin_id, true );
1490 }
1491
1492 return is_plugin_active( $this->_fs->premium_plugin_basename() );
1493 }
1494
1495 /**
1496 * Store the basename since it's not always available in the `_maybe_adjust_source_dir` method below.
1497 *
1498 * @author Leo Fajardo (@leorw)
1499 * @since 2.2.1
1500 *
1501 * @param bool|WP_Error $response Response.
1502 * @param array $hook_extra Extra arguments passed to hooked filters.
1503 *
1504 * @return bool|WP_Error
1505 */
1506 static function _store_basename_for_source_adjustment( $response, $hook_extra ) {
1507 if ( isset( $hook_extra['plugin'] ) ) {
1508 self::$_upgrade_basename = $hook_extra['plugin'];
1509 } else if ( isset( $hook_extra['theme'] ) ) {
1510 self::$_upgrade_basename = $hook_extra['theme'];
1511 } else {
1512 self::$_upgrade_basename = null;
1513 }
1514
1515 return $response;
1516 }
1517
1518 /**
1519 * Adjust the plugin directory name if necessary.
1520 * Assumes plugin has a folder (not a single file plugin).
1521 *
1522 * The final destination directory of a plugin is based on the subdirectory name found in the
1523 * (un)zipped source. In some cases this subdirectory name is not the same as the expected
1524 * slug and the plugin will not be recognized as installed. This is fixed by adjusting
1525 * the temporary unzipped source subdirectory name to the expected plugin slug.
1526 *
1527 * @author Vova Feldman
1528 * @since 1.2.1.7
1529 * @since 2.2.1 The method was converted to static since when the admin update bulk products via the Updates section, the logic applies the `upgrader_source_selection` filter for every product that is being updated.
1530 *
1531 * @param string $source Path to upgrade/zip-file-name.tmp/subdirectory/.
1532 * @param string $remote_source Path to upgrade/zip-file-name.tmp.
1533 * @param \WP_Upgrader $upgrader Instance of the upgrader which installs the plugin.
1534 *
1535 * @return string|WP_Error
1536 */
1537 static function _maybe_adjust_source_dir( $source, $remote_source, $upgrader ) {
1538 if ( ! is_object( $GLOBALS['wp_filesystem'] ) ) {
1539 return $source;
1540 }
1541
1542 $basename = self::$_upgrade_basename;
1543 $is_theme = false;
1544
1545 // Figure out what the slug is supposed to be.
1546 if ( isset( $upgrader->skin->options['extra'] ) ) {
1547 // Set by the auto-install logic.
1548 $desired_slug = $upgrader->skin->options['extra']['slug'];
1549 } else if ( ! empty( $basename ) ) {
1550 /**
1551 * If it doesn't end with ".php", it's a theme.
1552 *
1553 * @author Leo Fajardo (@leorw)
1554 * @since 2.2.1
1555 */
1556 $is_theme = ( ! fs_ends_with( $basename, '.php' ) );
1557
1558 $desired_slug = ( ! $is_theme ) ?
1559 dirname( $basename ) :
1560 // Theme slug
1561 $basename;
1562 } else {
1563 // Can't figure out the desired slug, stop the execution.
1564 return $source;
1565 }
1566
1567 if ( is_multisite() ) {
1568 /**
1569 * If we are running in a multisite environment and the product is not network activated,
1570 * the instance will not exist anyway. Therefore, try to update the source if necessary
1571 * regardless if the Freemius instance of the product exists or not.
1572 *
1573 * @author Vova Feldman
1574 */
1575 } else if ( ! empty( $basename ) ) {
1576 $fs = Freemius::get_instance_by_file(
1577 $basename,
1578 $is_theme ?
1579 WP_FS__MODULE_TYPE_THEME :
1580 WP_FS__MODULE_TYPE_PLUGIN
1581 );
1582
1583 if ( ! is_object( $fs ) ) {
1584 /**
1585 * If the Freemius instance does not exist on a non-multisite network environment, it means that:
1586 * 1. The product is not powered by Freemius; OR
1587 * 2. The product is not activated, therefore, we don't mind if after the update the folder name will change.
1588 *
1589 * @author Leo Fajardo (@leorw)
1590 * @since 2.2.1
1591 */
1592 return $source;
1593 }
1594 }
1595
1596 $subdir_name = untrailingslashit( str_replace( trailingslashit( $remote_source ), '', $source ) );
1597
1598 if ( ! empty( $subdir_name ) && $subdir_name !== $desired_slug ) {
1599 $from_path = untrailingslashit( $source );
1600 $to_path = trailingslashit( $remote_source ) . $desired_slug;
1601
1602 if ( true === $GLOBALS['wp_filesystem']->move( $from_path, $to_path ) ) {
1603 return trailingslashit( $to_path );
1604 }
1605
1606 return new WP_Error(
1607 'rename_failed',
1608 fs_text_inline( 'The remote plugin package does not contain a folder with the desired slug and renaming did not work.', 'module-package-rename-failure' ),
1609 array(
1610 'found' => $subdir_name,
1611 'expected' => $desired_slug
1612 )
1613 );
1614 }
1615
1616 return $source;
1617 }
1618
1619 #endregion
1620 }
1621