PluginProbe
Borderless – Addons and Templates for Elementor / trunk
Borderless – Addons and Templates for Elementor vtrunk
trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.2.1 1.2.2 1.2.3 All 78 releases
borderless / includes / templates / license / license-client.php

license-client.php in Borderless – Addons and Templates for Elementor trunk, at includes/templates/license/license-client.php

1,346 lines 49.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Kestrel API Manager for WooCommerce PHP Client Library
4 *
5 * Designed to be used with Kestrel API Manager for WooCommerce, to be initialized and loaded from a WordPress plugin or theme.
6 * Look into the ./examples directory for examples of how to use this library.
7 *
8 * This source file is subject to the GNU General Public License v3.0 that is bundled with this plugin in the file license.txt.
9 *
10 * Please do not modify this file if you want to upgrade the SDK to newer versions in the future.
11 * If you want to customize the SDK for your needs, please review our developer documentation at:
12 *
13 * @link https://kestrelwp.com/docs/woocommerce-api-manager-php-library-for-plugins-and-themes-documentation/
14 *
15 * You can join our developer program at:
16 *
17 * @link https://kestrelwp.com/developers
18 *
19 * @version 2.11.1
20 * @author Kestrel
21 * @copyright Copyright (c) 2013-2025 Kestrel
22 * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23 * @package Kestrel API Manager for WooCommerce
24 */
25
26 defined( 'ABSPATH' ) || exit;
27
28 if ( ! class_exists( 'BORDERLESS_LICENSE' ) ) {
29
30 /**
31 * API Manager for WooCommerce client class.
32 *
33 * @since 1.0.0
34 */
35 class BORDERLESS_LICENSE {
36
37 /** @var string API URL. */
38 private $api_url = '';
39
40 /** @var string Data key. */
41 private $data_key = '';
42
43 /** @var string File path. */
44 private $file = '';
45
46 /** @var string Plugin name. */
47 private $plugin_name = '';
48
49 /** @var string<"plugin"|"theme"> Product type. Either "plugin" or "theme". */
50 private $plugin_or_theme = '';
51
52 /** @var int|null */
53 private $product_id = null;
54
55 /** @var int|null */
56 private $product_parent_id = null;
57
58 /** @var string */
59 private $slug = '';
60
61 /** @var string */
62 private $software_title = '';
63
64 /** @var string */
65 private $software_version = '';
66
67 /** @var string */
68 private $text_domain = '';
69
70 /** @var array */
71 private $data = array();
72
73 /** @var string */
74 private $identifier = '';
75
76 /** @var bool */
77 private $no_product_id = false;
78
79 /** @var int */
80 private $product_id_chosen = 0;
81
82 /** @var string */
83 private $wc_am_activated_key = '';
84
85 /** @var string */
86 private $wc_am_activation_tab_key = '';
87
88 /** @var string */
89 private $wc_am_api_key_key = '';
90
91 /** @var string */
92 private $wc_am_deactivate_checkbox_key = '';
93
94 /** @var string */
95 private $wc_am_deactivation_tab_key = '';
96
97 /** @var string */
98 private $wc_am_auto_update_key = '';
99
100 /** @var string */
101 private $wc_am_domain = '';
102
103 /** @var string */
104 private $wc_am_instance_id = '';
105
106 /** @var string */
107 private $wc_am_instance_key = '';
108
109 /** @var string */
110 private $wc_am_menu_tab_activation_title = '';
111
112 /** @var string */
113 private $wc_am_menu_tab_deactivation_title = '';
114
115 /** @var string */
116 private $wc_am_plugin_name = '';
117
118 /** @var string */
119 private $wc_am_product_id = '';
120
121 /** @var string */
122 private $wc_am_renew_license_url = '';
123
124 /** @var string */
125 private $wc_am_settings_menu_title = '';
126
127 /** @var string */
128 private $wc_am_settings_title = '';
129
130 /** @var string */
131 private $wc_am_software_version = '';
132
133 /** @var array */
134 private $menu = array();
135
136 /** @var bool */
137 private $inactive_notice = true;
138
139 /**
140 * Cache for the status result to prevent multiple API calls.
141 *
142 * @var string|null
143 */
144 private $status_result = null;
145
146 /**
147 * Client constructor.
148 *
149 * @param string $file The main plugin or theme __FILE__ path.
150 * @param int|null $product_id The product ID. If null, it should be provided by the customer in the API settings.
151 * @param int|null $product_parent_id Optional if the product is a variable product and the product ID is neither specified nor entered by the customer (not advisable).
152 * @param string $software_version This should be a semantic version number.
153 * @param string<"plugin"|"theme"> $plugin_or_theme The type of product licensed.
154 * @param string $api_url The URL to the site that is running the API Manager - must be the root URL.
155 * @param string $software_title The name of the product licensed.
156 * @param string $text_domain The textdomain of the product licensed.
157 * @param array<string, mixed> $custom_menu Optional, default menu will be used when empty or not set.
158 * @param bool $inactive_notice Optional, default true - use false to hide the inactive license notice.
159 */
160 public function __construct( $file, $product_id, $product_parent_id, $software_version, $plugin_or_theme, $api_url, $software_title = '', $text_domain = '', $custom_menu = array(), $inactive_notice = true ) {
161
162 $this->menu = $this->clean( $custom_menu );
163 $this->inactive_notice = $inactive_notice;
164 $this->product_parent_id = is_numeric( $product_parent_id ) ? absint( $product_parent_id ) : null;
165 $this->no_product_id = empty( $product_id );
166 $this->plugin_or_theme = esc_attr( strtolower( $plugin_or_theme ) );
167
168 if ( $this->no_product_id ) {
169 $this->identifier = $this->plugin_or_theme === 'plugin' ? dirname( untrailingslashit( plugin_basename( $file ) ) ) : basename( dirname( plugin_basename( $file ) ) );
170 $product_id = strtolower( str_ireplace( array( ' ', '_', '&', '?', '-' ), '_', $this->identifier ) );
171 $this->wc_am_product_id = 'wc_am_product_id_' . $product_id;
172 $this->product_id_chosen = get_option( $this->wc_am_product_id );
173 } elseif ( ! empty( $product_id ) && is_numeric( $product_id ) ) {
174 $this->product_id = absint( $product_id );
175 } elseif ( ! empty( $product_id ) && is_string( $product_id ) ) {
176 /**
177 * Preserve the value of $product_id to use for API requests. Pre 2.0 product_id is a string, and >= 2.0 is an integer.
178 */
179 $this->product_id = esc_attr( $product_id );
180 }
181
182 // If the product_id was not provided, but was saved by the customer, used the saved product_id from the customer.
183 if ( empty( $this->product_id ) && ! empty( $this->product_id_chosen ) ) {
184 $this->product_id = $this->product_id_chosen;
185 }
186
187 $this->file = $file;
188 $this->software_title = esc_attr( $software_title );
189 $this->software_version = esc_attr( $software_version );
190 $this->api_url = esc_url( $api_url );
191 $this->text_domain = esc_attr( $text_domain );
192
193 // If the product_id is a pre 2.0 string, format it to be used as an option key, otherwise it will be an integer if >= 2.0.
194 $this->data_key = 'wc_am_client_' . strtolower( str_ireplace( array( ' ', '_', '&', '?', '-' ), '_', $product_id ) );
195 $this->wc_am_activated_key = $this->data_key . '_activated';
196
197 if ( is_admin() ) {
198 if ( ! empty( $this->plugin_or_theme ) && $this->plugin_or_theme === 'theme' ) {
199 add_action( 'admin_init', array( $this, 'activation' ) );
200 }
201 if ( ! empty( $this->plugin_or_theme ) && $this->plugin_or_theme === 'plugin' ) {
202 if ( did_action( 'plugins_loaded' ) || doing_action( 'plugins_loaded' ) || 'plugins_loaded' === current_action() ) {
203 add_action( 'admin_init', array( $this, 'activation' ) );
204 } else {
205 register_activation_hook( $this->file, array( $this, 'activation' ) );
206 }
207 }
208
209 add_action( 'admin_menu', array( $this, 'register_menu' ) );
210 add_action( 'admin_init', array( $this, 'load_settings' ) );
211
212 // Check for external connection blocking.
213 add_action( 'admin_notices', array( $this, 'check_external_blocking' ) );
214
215 /**
216 * Set all data defaults here.
217 */
218 $this->wc_am_api_key_key = $this->data_key . '_api_key';
219 $this->wc_am_instance_key = $this->data_key . '_instance';
220
221 /**
222 * Set all admin menu data.
223 */
224 $this->wc_am_deactivate_checkbox_key = $this->data_key . '_deactivate_checkbox';
225 $this->wc_am_activation_tab_key = $this->data_key . '_dashboard';
226 $this->wc_am_deactivation_tab_key = $this->data_key . '_deactivation';
227 $this->wc_am_auto_update_key = $this->data_key . '_auto_update';
228
229 $this->wc_am_settings_title = sprintf( __( '%s', $this->text_domain ), ! empty( $this->menu['page_title'] ) ? $this->menu['page_title'] : $this->software_title . ' license key activation', $this->text_domain ); // phpcs:ignore
230 $this->wc_am_settings_menu_title = sprintf( __( '%s', $this->text_domain ), ! empty( $this->menu['menu_title'] ) ? $this->menu['menu_title'] : $this->software_title . ' activation', $this->text_domain ); // phpcs:ignore
231 $this->wc_am_menu_tab_activation_title = esc_html__( 'License key activation', $this->text_domain ); // phpcs:ignore
232 $this->wc_am_menu_tab_deactivation_title = esc_html__( 'License key deactivation', $this->text_domain ); // phpcs:ignore
233
234 /**
235 * Set all software update data here.
236 */
237 $this->data = get_option( $this->data_key );
238
239 $this->wc_am_plugin_name = $this->plugin_or_theme === 'plugin' ? untrailingslashit( plugin_basename( $this->file ) ) : basename( dirname( plugin_basename( $file ) ) ); // Same as plugin slug. If a theme, use a theme name like 'twentytwentyfive'.
240
241 $this->wc_am_renew_license_url = $this->api_url . 'my-account'; // URL to renew an API Key. Trailing slash in the upgrade_url is required.
242 $this->wc_am_instance_id = get_option( $this->wc_am_instance_key ); // Instance ID (unique to each blog activation).
243
244 /**
245 * Some web hosts have security policies that block the : (colon) and // (slashes) in http://, so only the host portion of the URL can be sent.
246 * For example the host portion might be www.example.com or example.com. http://www.example.com includes the scheme http, and the host www.example.com.
247 * Sending only the host also eliminates issues when a client site changes from http to https, but their activation still uses the original scheme.
248 * To send only the host, use a line like the one below:
249 *
250 * $this->wc_am_domain = str_ireplace( array( 'http://', 'https://' ), '', home_url() ); // blog domain name
251 */
252
253 $this->wc_am_domain = str_ireplace( array( 'http://', 'https://' ), '', home_url() ); // Blog domain name.
254 $this->wc_am_software_version = $this->software_version; // The software version.
255
256 /**
257 * Check for software updates
258 */
259 $this->check_for_update();
260
261 if ( $this->inactive_notice ) {
262 if ( ! empty( $this->wc_am_activated_key ) && get_option( $this->wc_am_activated_key ) !== 'Activated' ) {
263 add_action( 'admin_notices', array( $this, 'inactive_notice' ) );
264 }
265 }
266
267 /**
268 * Makes auto updates available if WordPress >= 5.5.
269 */
270 $this->try_automatic_updates();
271
272 if ( $this->plugin_or_theme === 'plugin' ) {
273 add_filter( 'plugin_auto_update_setting_html', array( $this, 'auto_update_message' ), 10, 3 );
274 }
275 }
276
277 /**
278 * Deletes all data if plugin deactivated.
279 *
280 * Uncomment if applicable.
281 */
282 if ( $this->plugin_or_theme == 'plugin' ) { // phpcs:ignore
283 // phpcs:ignore
284 // register_deactivation_hook( $this->file, array( $this, 'uninstall' ) );
285 } elseif ( $this->plugin_or_theme === 'theme' ) { // phpcs:ignore
286 // phpcs:ignore
287 // add_action( 'switch_theme', array( $this, 'uninstall' ) );
288 }
289 }
290
291 /**
292 * Clean variables using sanitize_text_field. Arrays are cleaned recursively.
293 *
294 * Non-scalar values are ignored.
295 *
296 * @since 2.9
297 *
298 * @param string|array $item Data to sanitize.
299 * @return string|array
300 */
301 private function clean( $item ) {
302 if ( is_array( $item ) ) {
303 return array_map( array( $this, 'clean' ), $item );
304 } else {
305 return is_scalar( $item ) ? sanitize_text_field( $item ) : $item;
306 }
307 }
308
309 /**
310 * Register a menu or submenu specific to this product.
311 *
312 * @updated 2.9
313 */
314 public function register_menu() {
315 $page_title = $this->wc_am_settings_title;
316 $menu_title = $this->wc_am_settings_menu_title;
317 $capability = ! empty( $this->menu['capability'] ) ? $this->menu['capability'] : 'manage_options';
318 $menu_slug = ! empty( $this->menu['menu_slug'] ) ? $this->menu['menu_slug'] : $this->wc_am_activation_tab_key;
319 $callback = ! empty( $this->menu['callback'] ) ? $this->menu['callback'] : array( $this, 'config_page' );
320 $icon_url = ! empty( $this->menu['icon_url'] ) ? $this->menu['icon_url'] : '';
321 $position = ! empty( $this->menu['position'] ) ? $this->menu['position'] : null;
322
323 if ( is_array( $this->menu ) && ! empty( $this->menu['menu_type'] ) ) {
324 if ( $this->menu['menu_type'] === 'add_submenu_page' ) {
325 add_submenu_page( $this->menu['parent_slug'], $page_title, $menu_title, $capability, $menu_slug, $callback, $position );
326 } elseif ( $this->menu['menu_type'] === 'add_options_page' ) {
327 add_options_page( $page_title, $menu_title, $capability, $menu_slug, $callback, $position );
328 } elseif ( $this->menu['menu_type'] === 'add_menu_page' ) {
329 add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $callback, $icon_url, $position );
330 }
331 } else {
332 add_options_page( sprintf( __( '%s', $this->text_domain ), $this->wc_am_settings_menu_title ), sprintf( __( '%s', $this->text_domain ), $this->wc_am_settings_menu_title ), 'manage_options', $this->wc_am_activation_tab_key, array( $this, 'config_page' ) ); // phpcs:ignore
333 }
334 }
335
336 /**
337 * Tries auto updates.
338 *
339 * @since 2.8
340 */
341 public function try_automatic_updates() {
342 global $wp_version;
343
344 if ( version_compare( $wp_version, '5.5', '>=' ) ) {
345 if ( $this->plugin_or_theme === 'plugin' ) {
346 add_filter( 'auto_update_plugin', array( $this, 'maybe_auto_update' ), 10, 2 );
347 } elseif ( $this->plugin_or_theme === 'theme' ) {
348 add_filter( 'auto_update_theme', array( $this, 'maybe_auto_update' ), 10, 2 );
349 }
350 }
351 }
352
353 /**
354 * Tries to set auto updates.
355 *
356 * @since 2.8
357 *
358 * @param bool|null $update Whether to update.
359 * @param object $item The item to update.
360 * @return bool
361 */
362 public function maybe_auto_update( $update, $item ) {
363 if ( strpos( $this->wc_am_plugin_name, '.php' ) !== 0 ) {
364 $slug = dirname( $this->wc_am_plugin_name );
365 } else {
366 $slug = $this->wc_am_plugin_name;
367 }
368
369 if ( isset( $item->slug ) && $item->slug === $slug ) {
370 if ( $this->is_auto_update_disabled() ) {
371 return false;
372 }
373
374 if ( ! $this->get_api_key_status() || ! $this->get_api_key_status( true ) ) {
375 return false;
376 }
377
378 return true;
379 }
380
381 return $update;
382 }
383
384 /**
385 * Checks if auto updates are disabled.
386 *
387 * @since 2.8
388 *
389 * @return bool
390 */
391 public function is_auto_update_disabled() {
392
393 /*
394 * WordPress will not offer to update if background updates are disabled.
395 * WordPress background updates are disabled if file changes are not allowed.
396 */
397 if ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS ) {
398 return true;
399 }
400
401 if ( defined( 'WP_INSTALLING' ) ) {
402 return true;
403 }
404
405 $wp_updates_disabled = defined( 'AUTOMATIC_UPDATER_DISABLED' ) && AUTOMATIC_UPDATER_DISABLED;
406
407 /**
408 * Overrides the WordPress AUTOMATIC_UPDATER_DISABLED constant.
409 *
410 * @param bool $wp_updates_disabled true if disables. false otherwise.
411 */
412 $wp_updates_disabled = apply_filters( 'automatic_updater_disabled', $wp_updates_disabled );
413
414 if ( $wp_updates_disabled ) {
415 return true;
416 }
417
418 // Return true if this plugin or theme background update is disabled.
419 // phpcs:ignore
420 // return get_option( $this->wc_am_auto_update_key ) !== 'on';
421
422 return false;
423 }
424
425 /**
426 * Filter the auto-update message on the plugins page.
427 *
428 * Plugin updates stored in 'auto_update_plugins' array.
429 *
430 * @see 'wp-admin/includes/class-wp-plugins-list-table.php'
431 *
432 * @since 2.8
433 *
434 * @param string $html HTML of the auto-update message.
435 * @param string $plugin_file Plugin file.
436 * @param array $plugin_data Plugin details.
437 *
438 * @return mixed|string
439 */
440 public function auto_update_message( $html, $plugin_file, $plugin_data ) {
441 global $status, $page;
442
443 if ( $this->wc_am_plugin_name === $plugin_file ) {
444 if ( ! $this->get_api_key_status() || ! $this->get_api_key_status( true ) ) {
445 /* translators: Context: Software product (eg. WordPress plugin or theme) auto-updates are unavailable. */
446 return esc_html__( 'Auto-updates unavailable.', $this->text_domain ); // phpcs:ignore
447 }
448
449 $auto_updates = (array) get_site_option( 'auto_update_plugins', array() );
450 $html = array();
451
452 if ( isset( $plugin_data['auto-update-forced'] ) ) {
453 if ( $plugin_data['auto-update-forced'] ) {
454 // Forced on.
455 $text = __( 'Auto-updates enabled', $this->text_domain ); // phpcs:ignore
456 } else {
457 $text = __( 'Auto-updates disabled', $this->text_domain ); // phpcs:ignore
458 }
459
460 $action = 'unavailable';
461 $time_class = ' hidden';
462 } elseif ( in_array( $plugin_file, $auto_updates, true ) ) {
463 $text = __( 'Disable auto-updates' );
464 $action = 'disable';
465 $time_class = '';
466 } else {
467 $text = __( 'Enable auto-updates' );
468 $action = 'enable';
469 $time_class = ' hidden';
470 }
471
472 $query_args = array(
473 'action' => "{$action}-auto-update",
474 'plugin' => $plugin_file,
475 'paged' => $page,
476 'plugin_status' => $status,
477 );
478
479 $url = add_query_arg( $query_args, 'plugins.php' );
480
481 if ( 'unavailable' === $action ) {
482 $html[] = '<span class="label">' . $text . '</span>';
483 } else {
484 $html[] = sprintf( '<a href="%s" class="toggle-auto-update aria-button-if-js" data-wp-action="%s">', wp_nonce_url( $url, 'updates' ), $action );
485 $html[] = '<span class="dashicons dashicons-update spin hidden" aria-hidden="true"></span>';
486 $html[] = '<span class="label">' . $text . '</span>';
487 $html[] = '</a>';
488 }
489
490 if ( ! empty( $plugin_data['update'] ) ) {
491 $html[] = sprintf( '<div class="auto-update-time%s">%s</div>', $time_class, wp_get_auto_update_message() );
492 }
493
494 $html = implode( '', $html );
495 }
496
497 return $html;
498 }
499
500 /**
501 * Generate the default data.
502 */
503 public function activation() {
504 $instance_exists = get_option( $this->wc_am_instance_key );
505
506 if ( get_option( $this->data_key ) === false || $instance_exists === false ) {
507
508 if ( $instance_exists === false ) {
509 $this->wc_am_instance_id = wp_generate_password( 12, false );
510 update_option( $this->wc_am_instance_key, $this->wc_am_instance_id );
511 }
512
513 update_option( $this->wc_am_deactivate_checkbox_key, 'on' );
514 update_option( $this->wc_am_activated_key, 'Deactivated' );
515 }
516 }
517
518 /**
519 * Deletes all data if plugin deactivated.
520 *
521 * @return void
522 */
523 public function uninstall() {
524
525 /**
526 * Filter wc_am_client_uninstall_disable.
527 *
528 * @since 2.5.1
529 *
530 * If set to false uninstall() method will be disabled.
531 *
532 * @param bool $uninstall
533 */
534 if ( apply_filters( 'wc_am_client_uninstall_disable', true ) ) {
535 $this->license_key_deactivation();
536
537 $options = array(
538 $this->wc_am_instance_key,
539 $this->wc_am_deactivate_checkbox_key,
540 $this->wc_am_activated_key,
541 );
542
543 // Remove options pre API Manager 2.0.
544 if ( is_multisite() ) {
545 global $blog_id;
546 switch_to_blog( $blog_id );
547 foreach ( $options as $option ) {
548 delete_option( $option );
549 }
550 restore_current_blog();
551 } else {
552 foreach ( $options as $option ) {
553 delete_option( $option );
554 }
555 }
556 }
557 }
558
559 /**
560 * Deactivates the license on the API server.
561 */
562 public function license_key_deactivation() {
563 $activation_status = get_option( $this->wc_am_activated_key );
564 $api_key = ! empty( $this->data[ $this->wc_am_api_key_key ] ) ? $this->data[ $this->wc_am_api_key_key ] : '';
565
566 $args = array(
567 'api_key' => $api_key,
568 );
569
570 if ( ! empty( $api_key ) && $activation_status === 'Activated' ) {
571 if ( empty( $this->deactivate( $args ) ) ) { // phpcs:ignore
572 add_settings_error( 'not_deactivated_text', 'not_deactivated_error', esc_html__( 'The API Key could not be deactivated. Use the API Key Deactivation tab to manually deactivate the API Key before activating a new API Key. If all else fails, go to Plugins, then deactivate and reactivate this plugin, or if a theme change themes, then change back to this theme, then go to the Settings for this plugin/theme and enter the API Key information again to activate it. Also check the My Account dashboard to see if the API Key for this site was still active before the error message was displayed.', $this->text_domain ), 'updated' ); // phpcs:ignore
573 }
574 }
575 }
576
577 /**
578 * Displays an inactive notice when the software is inactive.
579 */
580
581 public function inactive_notice() {
582 /**
583 * Filters the inactive notice.
584 *
585 * @since 2.5.1
586 *
587 * Filter wc_am_client_inactive_notice_override
588 * If set to false inactive_notice() method will be disabled.
589 *
590 * @param bool $inactive_notice
591 */
592 if ( apply_filters( 'wc_am_client_inactive_notice_override', true ) ) {
593 if ( ! current_user_can( 'manage_options' ) ) {
594 return;
595 }
596 // phpcs:ignore
597 if ( isset( $_GET['page'] ) && $this->wc_am_activation_tab_key === $_GET['page'] ) {
598 return;
599 }
600 /*
601 ?>
602 <div class="notice notice-error is-dismissible">
603 <p><?php printf( __( 'Do you have a <strong>%1$s</strong> license? %3$sActivate it now%4$s to enjoy all premium features and additional benefits.', $this->text_domain ), esc_attr( $this->software_title ), esc_attr( $this->plugin_or_theme ), '<a href="' . esc_url( admin_url( 'options-general.php?page=' . $this->wc_am_activation_tab_key ) ) . '">', '</a>', esc_attr( $this->software_title ) ); // phpcs:ignore ?></p>
604 </div>
605 <?php
606 */
607 }
608 }
609
610 /**
611 * Check for external blocking contstant.
612 */
613 public function check_external_blocking() {
614 // Show a notice if external requests are blocked through the WP_HTTP_BLOCK_EXTERNAL constant.
615 if ( defined( 'WP_HTTP_BLOCK_EXTERNAL' ) && WP_HTTP_BLOCK_EXTERNAL === true ) {
616
617 // Check if our API endpoint is in the allowed hosts.
618 $host = wp_parse_url( $this->api_url, PHP_URL_HOST );
619
620 if ( ! defined( 'WP_ACCESSIBLE_HOSTS' ) || stristr( WP_ACCESSIBLE_HOSTS, $host ) === false ) :
621 ?>
622 <div class="notice notice-error">
623 <p>
624 <?php
625 printf(
626 __( '<b>Warning!</b> You\'re blocking external requests which means you won\'t be able to get %1$s updates. Please add %2$s to %3$s.', $this->text_domain ),
627 $this->software_title,
628 '<strong>' . $host . '</strong>',
629 '<code>WP_ACCESSIBLE_HOSTS</code>'
630 ); // phpcs:ignore
631 ?>
632 </p>
633 </div>
634 <?php
635 endif;
636 }
637 }
638
639 /**
640 * Outputs the settings page.
641 *
642 * @return void
643 */
644 public function config_page() {
645 $settings_tabs = array(
646 $this->wc_am_activation_tab_key => esc_html__( $this->wc_am_menu_tab_activation_title, $this->text_domain ), // phpcs:ignore
647 $this->wc_am_deactivation_tab_key => esc_html__( $this->wc_am_menu_tab_deactivation_title, $this->text_domain ), // phpcs:ignore
648 );
649
650 $current_tab = isset( $_GET['tab'] ) ? $_GET['tab'] : $this->wc_am_activation_tab_key; // phpcs:ignore
651 $tab = isset( $_GET['tab'] ) ? $_GET['tab'] : $this->wc_am_activation_tab_key; // phpcs:ignore
652 ?>
653 <div class='wrap'>
654 <h2><?php esc_html_e( $this->wc_am_settings_title, $this->text_domain ); // phpcs:ignore ?></h2>
655 <h2 class="nav-tab-wrapper">
656 <?php
657 foreach ( $settings_tabs as $tab_page => $tab_name ) :
658 $active_tab = $current_tab === $tab_page ? 'nav-tab-active' : '';
659 echo '<a class="nav-tab ' . esc_attr( $active_tab ) . '" href="?page=' . esc_attr( $this->wc_am_activation_tab_key ) . '&tab=' . esc_attr( $tab_page ) . '">' . esc_attr( $tab_name ) . '</a>';
660 endforeach;
661 ?>
662 </h2>
663 <form action='options.php' method='post'>
664 <div class="main">
665 <?php
666 if ( $tab === $this->wc_am_activation_tab_key ) {
667 settings_fields( $this->data_key );
668 do_settings_sections( $this->wc_am_activation_tab_key );
669 submit_button( esc_html__( 'Save changes', $this->text_domain ) ); // phpcs:ignore
670 } else {
671 settings_fields( $this->wc_am_deactivate_checkbox_key );
672 do_settings_sections( $this->wc_am_deactivation_tab_key );
673 submit_button( esc_html__( 'Save changes', $this->text_domain ) ); // phpcs:ignore
674 }
675 ?>
676 </div>
677 </form>
678 </div>
679 <?php
680 }
681
682 /**
683 * Register settings.
684 *
685 * @return void
686 */
687 public function load_settings() {
688 global $wp_version;
689
690 register_setting( $this->data_key, $this->data_key, array( $this, 'validate_options' ) );
691
692 // API Key.
693 add_settings_section( $this->wc_am_api_key_key, esc_html__( 'License key activation', $this->text_domain ), array( $this, 'wc_am_api_key_text' ), $this->wc_am_activation_tab_key ); // phpcs:ignore
694 add_settings_field( $this->wc_am_api_key_key, esc_html__( 'License key', $this->text_domain ), array( $this, 'wc_am_api_key_field' ), $this->wc_am_activation_tab_key, $this->wc_am_api_key_key ); // phpcs:ignore
695
696 if ( $this->no_product_id ) {
697 add_settings_field( 'product_id', esc_html__( 'Product', $this->text_domain ), array( $this, 'wc_am_product_id_field' ), $this->wc_am_activation_tab_key, $this->wc_am_api_key_key ); // phpcs:ignore
698 }
699
700 add_settings_field( 'status', esc_html__( 'License key status', $this->text_domain ), array( $this, 'wc_am_api_key_status' ), $this->wc_am_activation_tab_key, $this->wc_am_api_key_key ); // phpcs:ignore
701 add_settings_field( 'info', esc_html__( 'Activation info', $this->text_domain ), array( $this, 'wc_am_activation_info' ), $this->wc_am_activation_tab_key, $this->wc_am_api_key_key ); // phpcs:ignore
702
703 // Activation settings.
704 register_setting( $this->wc_am_deactivate_checkbox_key, $this->wc_am_deactivate_checkbox_key, array( $this, 'wc_am_license_key_deactivation' ) );
705
706 add_settings_section( 'deactivate_button', esc_html__( 'License deactivation', $this->text_domain ), array( $this, 'wc_am_deactivate_text' ), $this->wc_am_deactivation_tab_key ); // phpcs:ignore
707 add_settings_field( 'deactivate_button', esc_html__( 'Deactivate license key', $this->text_domain ), array( $this, 'wc_am_deactivate_textarea' ), $this->wc_am_deactivation_tab_key, 'deactivate_button' ); // phpcs:ignore
708 }
709
710 /**
711 * Provides text for api key section.
712 *
713 * @return void
714 */
715 public function wc_am_api_key_text() {
716 // Empty text. Override if needed.
717 }
718
719 /**
720 * Returns the API Key status from the WooCommerce API Manager on the server.
721 *
722 * @return void
723 */
724 public function wc_am_api_key_status() {
725 if ( $this->get_api_key_status( true ) ) {
726 $license_status_check = esc_html__( 'Activated', $this->text_domain ); // phpcs:ignore
727 update_option( $this->wc_am_activated_key, 'Activated' );
728 update_option( $this->wc_am_deactivate_checkbox_key, 'off' );
729 } else {
730 $license_status_check = esc_html__( 'Deactivated', $this->text_domain ); // phpcs:ignore
731 }
732
733 echo esc_attr( $license_status_check );
734 }
735
736 /**
737 * Returns the API Key status by querying the Status API function from the WooCommerce API Manager on the server.
738 *
739 * @return array|mixed|object
740 */
741 public function license_key_status() {
742 if ( $this->status_result !== null ) {
743 $status_result = $this->status_result;
744 } else {
745 $status_result = $this->status();
746 }
747
748 return ! empty( $status_result ) ? json_decode( $status_result, true ) : $status_result;
749 }
750
751 /**
752 * Returns true if the API Key status is Activated.
753 *
754 * @since 2.1
755 *
756 * @param bool $live Do not set to true if using to activate software. True is for live status checks after activation.
757 * @return bool
758 */
759 public function get_api_key_status( $live = false ) {
760
761 /**
762 * Real-time result.
763 *
764 * @since 2.5.1
765 */
766 if ( $live ) {
767 $license_status = $this->license_key_status();
768
769 return ! empty( $license_status ) && ! empty( $license_status['data']['activated'] ) && $license_status['data']['activated'];
770 }
771
772 /**
773 * If $live === false.
774 *
775 * Stored result when first activating software.
776 */
777 return get_option( $this->wc_am_activated_key ) === 'Activated';
778 }
779
780 /**
781 * Displays activation data returned by shop or local server.
782 *
783 * @since 2.9
784 * @internal
785 *
786 * @return void
787 */
788 public function wc_am_activation_info() {
789 $result_error = get_option( 'wc_am_' . $this->product_id . '_activate_error' );
790 $live_status = $this->license_key_status();
791
792 if ( ! empty( $live_status ) && isset( $live_status['success'], $live_status['data']['error'] ) && $live_status['success'] === false ) {
793 /* translators: Placeholder: %1$s - Opening <strong> HTML tag, %2$s - Closing </strong> HTML link tag, %3$s - Error message */
794 echo wp_kses_post( sprintf( __( '%1$sError:%2$s %3$s', $this->text_domain ), '<strong style="color:#b32d2e;">', '</strong>', $live_status['data']['error'] ) ) . '<br><br>'; // phpcs:ignore
795 }
796
797 if ( $this->get_api_key_status() ) {
798 $result_success = get_option( 'wc_am_' . $this->product_id . '_activate_success' );
799
800 if ( ! empty( $live_status ) && isset( $live_status['status_check'] ) && $live_status['success'] === 'active' ) {
801
802 /* translators: Placeholder: %s - license activations purchased count */
803 echo esc_html( sprintf( __( 'Activations purchased: %s', $this->text_domain ), $live_status['data']['total_activations_purchased'] ) ); // phpcs:ignore
804
805 /* translators: Placeholder: %s - total license activations count */
806 echo esc_html( sprintf( __( 'Total activations: %s', $this->text_domain ), $live_status['data']['total_activations'] ) ); // phpcs:ignore
807
808 /* translators: Placeholder: %s - total license activations remaining count */
809 echo esc_html( sprintf( __( 'Activations remaining: %s', $this->text_domain ), $live_status['data']['activations_remaining'] ) ); // phpcs:ignore
810 } elseif ( ! empty( $result_success ) ) {
811 echo esc_html( $result_success ) . '<br>';
812 }
813 } elseif ( ! $this->get_api_key_status() && ! empty( $live_status ) && isset( $live_status['status_check'] ) && $live_status['status_check'] === 'inactive' ) {
814
815 /* translators: Placeholder: %s - total license activations purchased count */
816 echo esc_html( sprintf( __( 'Activations purchased: %s', $this->text_domain ), $live_status['data']['total_activations_purchased'] ) ) . '<br>'; // phpcs:ignore
817
818 /* translators: Placeholder: %s - total license activations count */
819 echo esc_html( sprintf( __( 'Total activations: %s', $this->text_domain ), $live_status['data']['total_activations'] ) ) . '<br>'; // phpcs:ignore
820
821 /* translators: Placeholder: %s - total license activations remaining count */
822 echo esc_html( sprintf( __( 'Activations remaining: %s', $this->text_domain ), $live_status['data']['activations_remaining'] ) ) . '<br>'; // phpcs:ignore
823 } elseif ( ! $this->get_api_key_status() && ! empty( $result_error ) ) {
824
825 /* translators: Context: List of errors for a license activation failure */
826 echo esc_html__( 'Previous activation attempt errors:', $this->text_domain ) . '<br>'; // phpcs:ignore
827 echo wp_kses_post( print_r( $result_error, true ) ); // phpcs:ignore
828 }
829 }
830
831 /**
832 * Outputs the API Key field.
833 *
834 * @return void
835 */
836 public function wc_am_api_key_field() {
837 $value = ! empty( $this->data[ $this->wc_am_api_key_key ] ) ? $this->data[ $this->wc_am_api_key_key ] : '';
838
839 /**
840 * Filters the API Key field value.
841 *
842 * @since 2.9.2
843 *
844 * @param string $value The API Key field value.
845 */
846 $value = apply_filters( 'wc_am_api_key_field_value', $value, $this->data_key, $this->data );
847
848 if ( $value ) {
849 echo "<input id='api_key' name='" . esc_attr( $this->data_key ) . '[' . esc_attr( $this->wc_am_api_key_key ) . "]' size='25' type='text' value='" . esc_attr( $value ) . "' />";
850 } else {
851 echo "<input id='api_key' name='" . esc_attr( $this->data_key ) . '[' . esc_attr( $this->wc_am_api_key_key ) . "]' size='25' type='text' value='' />";
852 }
853 }
854
855 /**
856 * Outputs the product ID field.
857 *
858 * @since 2.3
859 */
860 public function wc_am_product_id_field() {
861 $product_id = get_option( $this->wc_am_product_id );
862 if ( ! empty( $product_id ) ) {
863 $this->product_id = $product_id;
864 }
865
866 // Pre defined products.
867 $products = array(
868 '168804' => 'Borderless Pro',
869 '168805' => 'Visualmodo Pass',
870 );
871
872 echo "<select id='product_id' name='" . esc_attr( $this->wc_am_product_id ) . "'>";
873
874 foreach ( $products as $value => $label ) {
875 echo '<option value="' . esc_attr( $value ) . '" ' . selected( (string) $product_id, (string) $value, false ) . '>' . esc_html( $label ) . '</option>';
876 }
877
878 echo '</select>';
879 }
880
881 /**
882 * Sanitizes and validates all input and output for Dashboard.
883 *
884 * @since 2.0
885 *
886 * @param array $input Input data.
887 * @return mixed|string
888 */
889 public function validate_options( $input ) {
890 // Load existing options, validate, and update with changes from input before returning.
891
892 $options = $this->data;
893 $options[ $this->wc_am_api_key_key ] = trim( $input[ $this->wc_am_api_key_key ] );
894
895 $api_key = trim( $input[ $this->wc_am_api_key_key ] );
896 $activation_status = get_option( $this->wc_am_activated_key );
897 $checkbox_status = get_option( $this->wc_am_deactivate_checkbox_key );
898 $current_api_key = ! empty( $this->data[ $this->wc_am_api_key_key ] ) ? $this->data[ $this->wc_am_api_key_key ] : '';
899
900 if ( $this->no_product_id ) {
901 $new_product_id = absint( $_REQUEST[ $this->wc_am_product_id ] ); // phpcs:ignore
902 if ( ! empty( $new_product_id ) ) {
903 update_option( $this->wc_am_product_id, $new_product_id );
904 $this->product_id = $new_product_id;
905 }
906 }
907
908 // Should match the settings_fields() value.
909 if ( ! empty( $_REQUEST['option_page'] ) && $_REQUEST['option_page'] !== $this->wc_am_deactivate_checkbox_key ) { // phpcs:ignore
910 if ( $activation_status === 'Deactivated' || $activation_status === '' || $api_key === '' || $checkbox_status === 'on' || $current_api_key !== $api_key ) {
911
912 /**
913 * If this is a new key, and an existing key already exists in the database, try to deactivate the existing key before activating the new key.
914 */
915 if ( ! empty( $current_api_key ) && $current_api_key !== $api_key ) {
916 $this->replace_license_key( $current_api_key );
917 }
918
919 $args = array(
920 'api_key' => $api_key,
921 );
922 $activation_result = $this->activate( $args );
923
924 if ( ! empty( $activation_result ) ) {
925 $activate_results = json_decode( $activation_result, true );
926
927 if ( $activate_results['success'] === true && $activate_results['activated'] === true ) {
928 add_settings_error( 'activate_text', 'activate_msg', sprintf( __( '%s activated. ', $this->text_domain ), esc_attr( $this->software_title ) ) . esc_attr( "{$activate_results['message']}." ), 'updated' ); // phpcs:ignore
929 update_option( 'wc_am_' . $this->product_id . '_activate_success', $activate_results['message'] );
930 update_option( $this->wc_am_activated_key, 'Activated' );
931 update_option( $this->wc_am_deactivate_checkbox_key, 'off' );
932 }
933
934 if ( $activate_results === false && ! empty( $this->data ) && ! empty( $this->wc_am_activated_key ) ) {
935 add_settings_error( 'api_key_check_text', 'api_key_check_error', esc_html__( 'Connection failed to the licensing server. See the "Activation error" section below for details. There may be a problem on your server preventing outgoing requests, or the store is blocking your request to activate the plugin/theme.', $this->text_domain ), 'error' ); // phpcs:ignore
936 update_option( $this->wc_am_activated_key, 'Deactivated' );
937 }
938
939 if ( isset( $activate_results['data']['error_code'] ) && ! empty( $this->data ) && ! empty( $this->wc_am_activated_key ) ) {
940 add_settings_error( 'wc_am_client_error_text', 'wc_am_client_error', esc_attr( "{$activate_results['data']['error']}" ), 'error' );
941 update_option( $this->wc_am_activated_key, 'Deactivated' );
942 }
943 } else {
944 add_settings_error( 'not_activated_empty_response_text', 'not_activated_empty_response_error', esc_html__( 'The license key activation could not be completed due to an error on the store server or your server. See the "Activation error" section below for details. The activation results were empty.', $this->text_domain ), 'updated' ); // phpcs:ignore
945 }
946 }
947 }
948
949 return $options;
950 }
951
952 /**
953 * Allow other actors to activate a new key programmatically.
954 *
955 * @since 2.9.2
956 *
957 * @param string $api_key API Key.
958 * @return void
959 */
960 public function activate_new_key( $api_key ) {
961 $result = $this->activate( array( 'api_key' => $api_key ) );
962
963 if ( ! empty( $result ) ) {
964 $result = json_decode( $result, true );
965
966 if ( $result['success'] === true && $result['activated'] === true ) {
967 update_option( 'wc_am_' . $this->product_id . '_activate_success', $result['message'] );
968 update_option( $this->wc_am_activated_key, 'Activated' );
969 update_option( $this->wc_am_deactivate_checkbox_key, 'off' );
970 update_option( $this->data_key, array( "{$this->data_key}_api_key" => $api_key ) );
971 } elseif ( function_exists( 'wc_get_logger' ) ) {
972 wc_get_logger()->error( print_r( $result, true ), array( 'source' => 'wc_product_sample' ) ); // phpcs:ignore
973 } else {
974 error_log( print_r( $result, true ) ); // phpcs:ignore
975 }
976 }
977 }
978
979 /**
980 * Deactivates the API Key to allow key to be used on another blog.
981 *
982 * @param string|mixed $input Checkbox value.
983 * @return string
984 */
985 public function wc_am_license_key_deactivation( $input ) {
986 $activation_status = get_option( $this->wc_am_activated_key );
987 $options = ( $input === 'on' ? 'on' : 'off' );
988
989 $args = array(
990 'api_key' => ! empty( $this->data[ $this->wc_am_api_key_key ] ) ? $this->data[ $this->wc_am_api_key_key ] : '',
991 );
992
993 if ( ! empty( $this->data[ $this->wc_am_api_key_key ] ) && $options === 'on' && $activation_status === 'Activated' ) {
994
995 // Deactivates API Key key activation.
996 $deactivation_result = $this->deactivate( $args );
997
998 if ( ! empty( $deactivation_result ) ) {
999 $activate_results = json_decode( $deactivation_result, true );
1000
1001 if ( $activate_results['success'] === true && $activate_results['deactivated'] === true ) {
1002
1003 if ( ! empty( $this->wc_am_activated_key ) ) {
1004 update_option( $this->wc_am_activated_key, 'Deactivated' );
1005 add_settings_error( 'wc_am_deactivate_text', 'deactivate_msg', esc_html__( 'License key deactivated. ', $this->text_domain ) . esc_attr( "{$activate_results['activations_remaining']}." ), 'updated' ); // phpcs:ignore
1006 }
1007
1008 return $options;
1009 }
1010
1011 if ( isset( $activate_results['data']['error_code'] ) && ! empty( $this->data ) && ! empty( $this->wc_am_activated_key ) ) {
1012 add_settings_error( 'wc_am_client_error_text', 'wc_am_client_error', esc_attr( "{$activate_results['data']['error']}" ), 'error' );
1013 update_option( $this->wc_am_activated_key, 'Deactivated' );
1014 }
1015 } else {
1016 add_settings_error( 'not_deactivated_empty_response_text', 'not_deactivated_empty_response_error', esc_html__( 'The license key activation could not be completed due to an unknown error possibly on the store server The activation results were empty.', $this->text_domain ), 'updated' ); // phpcs:ignore
1017 }
1018 }
1019
1020 return $options;
1021 }
1022
1023 /**
1024 * Deactivate the current API Key before activating the new API Key.
1025 *
1026 * @param string|mixed $current_api_key Current API Key.
1027 * @return void
1028 */
1029 public function replace_license_key( $current_api_key ) {
1030 $args = array(
1031 'api_key' => $current_api_key,
1032 );
1033
1034 $this->deactivate( $args );
1035 }
1036
1037 /**
1038 * Outputs the deactivation text.
1039 *
1040 * @return void
1041 */
1042 public function wc_am_deactivate_text() {
1043 // Empty text. Override if needed.
1044 }
1045
1046 /**
1047 * Outputs the deactivation checkbox.
1048 *
1049 * @return void
1050 */
1051 public function wc_am_deactivate_textarea() {
1052 echo '<input type="checkbox" id="' . esc_attr( $this->wc_am_deactivate_checkbox_key ) . '" name="' . esc_attr( $this->wc_am_deactivate_checkbox_key ) . '" value="on"';
1053 echo checked( get_option( $this->wc_am_deactivate_checkbox_key ), 'on' );
1054 echo '/>';
1055 ?>
1056 <span class="description"><?php esc_html_e( 'Deactivates a license key so it can be used on another site.', $this->text_domain ); // phpcs:ignore ?></span>
1057 <?php
1058 }
1059
1060 /**
1061 * Builds the URL containing the API query string for activation, deactivation, and status requests.
1062 *
1063 * @param array $args Arguments.
1064 * @return string
1065 */
1066 public function create_software_api_url( $args ) {
1067 return add_query_arg( 'wc-api', 'wc-am-api', $this->api_url ) . '&' . http_build_query( $args );
1068 }
1069
1070 /**
1071 * Sends the request to activate to the API Manager.
1072 *
1073 * @param array $args Arguments.
1074 * @return string
1075 */
1076 public function activate( $args ) {
1077 if ( empty( $args ) ) {
1078 add_settings_error( 'not_activated_text', 'not_activated_error', esc_html__( 'The license key is missing from the deactivation request.', $this->text_domain ), 'updated' ); // phpcs:ignore
1079 return '';
1080 }
1081
1082 $defaults = array(
1083 'wc_am_action' => 'activate',
1084 'product_id' => $this->product_id,
1085 'product_parent_id' => $this->product_parent_id,
1086 'instance' => $this->wc_am_instance_id,
1087 'object' => $this->wc_am_domain,
1088 'software_version' => $this->wc_am_software_version,
1089 );
1090
1091 $args = wp_parse_args( $defaults, $args );
1092 $target_url = esc_url_raw( $this->create_software_api_url( $args ) );
1093
1094 $request = wp_safe_remote_post( $target_url, array( 'timeout' => 15 ) );
1095
1096 // Request failed.
1097 if ( ! is_wp_error( $request ) && wp_remote_retrieve_response_code( $request ) !== 200 ) {
1098 update_option( 'wc_am_' . $this->product_id . '_activate_error', $request );
1099 return '';
1100 } elseif ( is_wp_error( $request ) || wp_remote_retrieve_response_code( $request ) !== 200 ) {
1101 update_option( 'wc_am_' . $this->product_id . '_activate_error', 'Error code: ' . $request->get_error_code() . '.<br> Error message: ' . $request->get_error_message() . '.<br> Error data: ' . $request->get_error_data() );
1102 return '';
1103 }
1104
1105 delete_option( 'wc_am_' . $this->product_id . '_activate_error' );
1106
1107 return wp_remote_retrieve_body( $request );
1108 }
1109
1110 /**
1111 * Sends the request to deactivate to the API Manager.
1112 *
1113 * @param array $args Arguments.
1114 * @return string
1115 */
1116 public function deactivate( $args ) {
1117 if ( empty( $args ) ) {
1118 add_settings_error( 'not_deactivated_text', 'not_deactivated_error', esc_html__( 'The license key is missing from the deactivation request.', $this->text_domain ), 'updated' ); // phpcs:ignore
1119 return '';
1120 }
1121
1122 $defaults = array(
1123 'wc_am_action' => 'deactivate',
1124 'product_id' => $this->product_id,
1125 'product_parent_id' => $this->product_parent_id,
1126 'instance' => $this->wc_am_instance_id,
1127 'object' => $this->wc_am_domain,
1128 );
1129
1130 $args = wp_parse_args( $defaults, $args );
1131 $target_url = esc_url_raw( $this->create_software_api_url( $args ) );
1132
1133 $request = wp_safe_remote_post( $target_url, array( 'timeout' => 15 ) );
1134
1135 if ( is_wp_error( $request ) || wp_remote_retrieve_response_code( $request ) !== 200 ) {
1136 // Request failed.
1137 return '';
1138 }
1139
1140 return wp_remote_retrieve_body( $request );
1141 }
1142
1143 /**
1144 * Sends the status check request to the API Manager.
1145 *
1146 * @return string|null
1147 */
1148 public function status() {
1149 if ( $this->status_result !== null ) {
1150 return $this->status_result;
1151 }
1152
1153 if ( empty( $this->data[ $this->wc_am_api_key_key ] ) ) {
1154 $this->status_result = '';
1155 return $this->status_result;
1156 }
1157
1158 $defaults = array(
1159 'wc_am_action' => 'status',
1160 'api_key' => $this->data[ $this->wc_am_api_key_key ],
1161 'product_id' => $this->product_id,
1162 'product_parent_id' => $this->product_parent_id,
1163 'instance' => $this->wc_am_instance_id,
1164 'object' => $this->wc_am_domain,
1165 );
1166
1167 $target_url = esc_url_raw( $this->create_software_api_url( $defaults ) );
1168
1169 $request = wp_safe_remote_post( $target_url, array( 'timeout' => 15 ) );
1170
1171 if ( is_wp_error( $request ) || wp_remote_retrieve_response_code( $request ) !== 200 ) {
1172 // Request failed.
1173 $this->status_result = '';
1174 return $this->status_result;
1175 }
1176
1177 $this->status_result = wp_remote_retrieve_body( $request );
1178
1179 return $this->status_result;
1180 }
1181
1182 /**
1183 * Check for software updates.
1184 */
1185 public function check_for_update() {
1186 $this->plugin_name = $this->wc_am_plugin_name;
1187 // Slug should be the same as the plugin/theme directory name.
1188 if ( strpos( $this->plugin_name, '.php' ) !== 0 ) {
1189 $this->slug = dirname( $this->plugin_name );
1190 } else {
1191 $this->slug = $this->plugin_name;
1192 }
1193
1194 // Uses the flag above to determine if this is a plugin or a theme update request.
1195 if ( $this->plugin_or_theme === 'plugin' ) {
1196 add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'update_check' ) );
1197 add_filter( 'plugins_api', array( $this, 'information_request' ), 10, 3 );
1198 } elseif ( $this->plugin_or_theme === 'theme' ) {
1199 add_filter( 'pre_set_site_transient_update_themes', array( $this, 'update_check' ) ); // phpcs:ignore
1200 // add_filter( 'themes_api', array( $this, 'information_request' ), 10, 3 );
1201 }
1202 }
1203
1204 /**
1205 * Sends and receives data to and from the server API.
1206 *
1207 * @since 2.0
1208 *
1209 * @param array $args Arguments.
1210 * @return bool|string $response
1211 */
1212 public function send_query( $args ) {
1213 $target_url = esc_url_raw( add_query_arg( 'wc-api', 'wc-am-api', $this->api_url ) . '&' . http_build_query( $args ) );
1214
1215 $request = wp_safe_remote_post( $target_url, array( 'timeout' => 15 ) );
1216
1217 if ( is_wp_error( $request ) || wp_remote_retrieve_response_code( $request ) !== 200 ) {
1218 return false;
1219 }
1220
1221 $response = wp_remote_retrieve_body( $request );
1222
1223 return ! empty( $response ) ? $response : false;
1224 }
1225
1226 /**
1227 * Check for updates against the remote server.
1228 *
1229 * @since 2.0
1230 *
1231 * @param object $transient Transient object.
1232 * @return object $transient
1233 */
1234 public function update_check( $transient ) {
1235 if ( empty( $transient->checked ) ) {
1236 return $transient;
1237 }
1238
1239 $args = array(
1240 'wc_am_action' => 'update',
1241 'slug' => $this->slug,
1242 'plugin_name' => $this->plugin_name,
1243 'version' => $this->wc_am_software_version,
1244 'product_id' => $this->product_id,
1245 'product_parent_id' => $this->product_parent_id,
1246 'api_key' => ! empty( $this->data[ $this->wc_am_api_key_key ] ) ? $this->data[ $this->wc_am_api_key_key ] : '',
1247 'instance' => $this->wc_am_instance_id,
1248 );
1249
1250 // Check for a plugin update.
1251 $response = json_decode( $this->send_query( $args ), true );
1252
1253 if ( isset( $response['data']['error_code'] ) ) {
1254 if ( isset( $response['data']['error'] ) ) {
1255 $error_message = $response['data']['error'];
1256 } elseif ( isset( $response['data']['message'] ) ) {
1257 $error_message = $response['data']['message'];
1258 } else {
1259 $error_message = null;
1260 }
1261
1262 if ( $error_message ) {
1263 add_settings_error( 'wc_am_client_error_text', 'wc_am_client_error', $error_message, 'error' );
1264 }
1265 }
1266
1267 if ( $response !== false && isset( $response['success'] ) && $response['success'] === true ) {
1268 // New plugin version from the API.
1269 $new_ver = (string) $response['data']['package']['new_version'];
1270
1271 // Current installed plugin version.
1272 $curr_ver = (string) $this->wc_am_software_version;
1273
1274 $package = array(
1275 'id' => $response['data']['package']['id'],
1276 'slug' => $response['data']['package']['slug'],
1277 'plugin' => $response['data']['package']['plugin'],
1278 'new_version' => $response['data']['package']['new_version'],
1279 'url' => $response['data']['package']['url'],
1280 'tested' => $response['data']['package']['tested'],
1281 'package' => $response['data']['package']['package'],
1282 'upgrade_notice' => $response['data']['package']['upgrade_notice'],
1283 );
1284
1285 if ( isset( $new_ver, $curr_ver ) ) {
1286 if ( version_compare( $new_ver, $curr_ver, '>' ) ) {
1287 if ( $this->plugin_or_theme === 'plugin' ) {
1288 $transient->response[ $this->plugin_name ] = (object) $package;
1289 unset( $transient->no_update[ $this->plugin_name ] );
1290 } elseif ( $this->plugin_or_theme === 'theme' ) {
1291 $transient->response[ $this->plugin_name ]['new_version'] = $response['data']['package']['new_version'];
1292 $transient->response[ $this->plugin_name ]['url'] = $response['data']['package']['url'];
1293 $transient->response[ $this->plugin_name ]['package'] = $response['data']['package']['package'];
1294 }
1295 }
1296 }
1297 }
1298
1299 return $transient;
1300 }
1301
1302 /**
1303 * API request for information.
1304 *
1305 * If $action is 'query_plugins' or 'plugin_information', an object MUST be passed.
1306 * If $action is 'hot_tags or 'hot_categories', an array should be passed.
1307 *
1308 * @param false|object|array $result The result object or array. Default false.
1309 * @param string $action The type of information being requested from the Plugin Install API.
1310 * @param object $args Arguments.
1311 *
1312 * @return object
1313 */
1314 public function information_request( $result, $action, $args ) {
1315
1316 // Check if this plugins API is about this plugin.
1317 if ( isset( $args->slug ) ) {
1318 if ( $args->slug !== $this->slug ) {
1319 return $result;
1320 }
1321 } else {
1322 return $result;
1323 }
1324
1325 $args = array(
1326 'wc_am_action' => 'plugininformation',
1327 'plugin_name' => $this->plugin_name,
1328 'version' => $this->wc_am_software_version,
1329 'product_id' => $this->product_id,
1330 'product_parent_id' => $this->product_parent_id,
1331 'api_key' => ! empty( $this->data[ $this->wc_am_api_key_key ] ) ? $this->data[ $this->wc_am_api_key_key ] : '',
1332 'instance' => $this->wc_am_instance_id,
1333 'object' => $this->wc_am_domain,
1334 );
1335
1336 $response = unserialize( $this->send_query( $args ) ); // phpcs:ignore
1337
1338 if ( isset( $response ) && is_object( $response ) && $response !== false ) {
1339 return $response;
1340 }
1341
1342 return $result;
1343 }
1344 }
1345 }
1346