PluginProbe
Tiered Pricing Table for WooCommerce / 2.3.1
Tiered Pricing Table for WooCommerce v2.3.1
7.1.7 7.1.5 7.1.4 7.1.1 6.5.0 6.4.0 6.1.0 trunk 1.0 1.1 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.2.0 2.2.1 2.2.2 2.2.3 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 All 90 releases
tier-pricing-table / freemius / includes / class-fs-plugin-updater.php

class-fs-plugin-updater.php in Tiered Pricing Table for WooCommerce 2.3.1, at freemius/includes/class-fs-plugin-updater.php

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