PluginProbe
Tiered Pricing Table for WooCommerce / 2.1.1
Tiered Pricing Table for WooCommerce v2.1.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.1.1, at freemius/includes/class-fs-plugin-updater.php

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