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