PluginProbe
BlockSpare – Gutenberg Blocks for News, Magazine, Blog & Business Websites / 2.5.3
BlockSpare – Gutenberg Blocks for News, Magazine, Blog & Business Websites v2.5.3
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 2.5.3, at freemius/includes/class-fs-plugin-updater.php

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