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