PluginProbe
BlockSpare – Gutenberg Blocks for News, Magazine, Blog & Business Websites / 1.3.0
BlockSpare – Gutenberg Blocks for News, Magazine, Blog & Business Websites v1.3.0
4.1.0 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.1.0 1.1.1 1.1.2 1.2.0 1.2.1 1.2.2 1.2.3 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 2.1.2 2.5.0 2.5.3 2.6.1 All 38 releases
blockspare / freemius / includes / class-fs-plugin-updater.php

class-fs-plugin-updater.php in BlockSpare – Gutenberg Blocks for News, Magazine, Blog & Business Websites 1.3.0, at freemius/includes/class-fs-plugin-updater.php

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