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

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

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