PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.9.4
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.9.4
4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 3.10.3 All 149 releases
visualizer / vendor / codeinwp / themeisle-sdk / src / Modules / Licenser.php

Licenser.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 3.9.4, at vendor/codeinwp/themeisle-sdk/src/Modules/Licenser.php

1,156 lines 34.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The main loader class for license handling.
4 *
5 * @package ThemeIsleSDK
6 * @subpackage Modules
7 * @copyright Copyright (c) 2017, Marius Cristea
8 * @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
9 * @since 1.0.0
10 */
11
12 namespace ThemeisleSDK\Modules;
13
14 // Exit if accessed directly.
15 use ThemeisleSDK\Common\Abstract_Module;
16 use ThemeisleSDK\Loader;
17 use ThemeisleSDK\Product;
18
19 if ( ! defined( 'ABSPATH' ) ) {
20 exit;
21 }
22
23 /**
24 * Licenser module for ThemeIsle SDK.
25 */
26 class Licenser extends Abstract_Module {
27 /**
28 * License VALID status string.
29 */
30 const STATUS_VALID = 'valid';
31 /**
32 * License NOT_ACTIVE status string.
33 */
34 const STATUS_NOT_ACTIVE = 'not_active';
35 /**
36 * License active expired status string.
37 */
38 const STATUS_ACTIVE_EXPIRED = 'active_expired';
39 /**
40 * Number of max failed checks before showing the license message.
41 *
42 * @var int $max_failed Maximum failed checks allowed before show the notice
43 */
44 private static $max_failed = 1;
45 /**
46 * Flag to check if the global actions were loaded.
47 *
48 * @var bool If the globals actions were loaded.
49 */
50 private static $globals_loaded = false;
51 /**
52 * License key string.
53 *
54 * @var string $license_key The license key string
55 */
56 public $license_key;
57 /**
58 * This ensures that the custom API request only runs on the second time that WP fires the update check.
59 *
60 * @var bool $do_check Flag for request.
61 */
62 private $do_check = false;
63 /**
64 * Number of failed checks to the api endpoint.
65 *
66 * @var bool $failed_checks
67 */
68 private $failed_checks = 0;
69 /**
70 * The product update response key.
71 *
72 * @var string $product_key Product key.
73 */
74 private $product_key;
75
76 /**
77 * Holds local license object.
78 *
79 * @var null Local license object.
80 */
81 private $license_local = null;
82 /**
83 * Product namespace, used for fixed name filters/cli commands.
84 *
85 * @var string $namespace Product namespace.
86 */
87 private $namespace = null;
88
89 /**
90 * Disable wporg updates for premium products.
91 *
92 * @param string $r Update payload.
93 * @param string $url The api url.
94 *
95 * @return mixed List of themes to check for update.
96 */
97 public function disable_wporg_update( $r, $url ) {
98
99 if ( 0 !== strpos( $url, 'https://api.wordpress.org/themes/update-check/' ) ) {
100 return $r;
101 }
102
103 // Decode the JSON response.
104 $themes = json_decode( $r['body']['themes'] );
105
106 unset( $themes->themes->{$this->product->get_slug()} );
107
108 // Encode the updated JSON response.
109 $r['body']['themes'] = wp_json_encode( $themes );
110
111 return $r;
112 }
113
114 /**
115 * Register the setting for the license of the product.
116 *
117 * @return bool
118 */
119 public function register_settings() {
120 if ( ! is_admin() ) {
121 return false;
122 }
123 if ( apply_filters( $this->product->get_key() . '_hide_license_field', false ) ) {
124 return;
125 }
126 add_settings_field(
127 $this->product->get_key() . '_license',
128 $this->product->get_name() . ' license',
129 array( $this, 'license_view' ),
130 'general'
131 );
132 }
133
134 /**
135 * The license view field.
136 */
137 public function license_view() {
138 $status = $this->get_license_status();
139 $value = $this->license_key;
140
141 $activate_string = apply_filters( $this->product->get_key() . '_lc_activate_string', 'Activate' );
142 $deactivate_string = apply_filters( $this->product->get_key() . '_lc_deactivate_string', 'Deactivate' );
143 $valid_string = apply_filters( $this->product->get_key() . '_lc_valid_string', 'Valid' );
144 $invalid_string = apply_filters( $this->product->get_key() . '_lc_invalid_string', 'Invalid' );
145 $license_message = apply_filters( $this->product->get_key() . '_lc_license_message', 'Enter your license from %s purchase history in order to get %s updates' );
146 $error_message = $this->get_error();
147 ?>
148 <style type="text/css">
149 input.themeisle-sdk-text-input-valid {
150 border: 1px solid #7ad03a;
151 }
152
153 input.themeisle-sdk-license-input {
154 width: 300px;
155 padding: 0 8px;
156 line-height: 2;
157 min-height: 30px;
158 }
159
160 .themeisle-sdk-license-deactivate-cta {
161 color: #fff;
162 background: #7ad03a;
163 display: inline-block;
164 text-decoration: none;
165 font-size: 13px;
166 line-height: 30px;
167 height: 26px;
168 margin-left: 5px;
169 padding: 0 10px 3px;
170 -webkit-border-radius: 3px;
171 border-radius: 3px;
172 }
173
174 .themeisle-sdk-license-activate-cta {
175 color: #fff;
176 background: #dd3d36;
177 display: inline-block;
178 text-decoration: none;
179 font-size: 13px;
180 line-height: 30px;
181 height: 26px;
182 margin-left: 5px;
183 padding: 0 10px 3px;
184 -webkit-border-radius: 3px;
185 border-radius: 3px;
186 }
187
188 button.button.themeisle-sdk-licenser-button-cta {
189 line-height: 26px;
190 height: 29px;
191 vertical-align: top;
192 }
193
194 </style>
195 <?php
196 echo sprintf(
197 '<p>%s<input class="themeisle-sdk-license-input %s" type="text" id="%s_license" name="%s_license" value="%s" /><a class="%s">%s</a>&nbsp;&nbsp;&nbsp;<button name="%s_btn_trigger" class="button button-primary themeisle-sdk-licenser-button-cta" value="yes" type="submit" >%s</button></p><p class="description">%s</p>%s',
198 ( ( 'valid' === $status ) ? sprintf( '<input type="hidden" value="%s" name="%s_license" />', esc_attr( $value ), esc_attr( $this->product->get_key() ) ) : '' ),
199 ( ( 'valid' === $status ) ? 'themeisle-sdk-text-input-valid' : '' ),
200 esc_attr( $this->product->get_key() ),
201 esc_attr( ( ( 'valid' === $status ) ? $this->product->get_key() . '_mask' : $this->product->get_key() ) ),
202 esc_attr( ( ( 'valid' === $status ) ? ( str_repeat( '*', 30 ) . substr( $value, - 5 ) ) : $value ) ),
203 esc_attr( ( 'valid' === $status ? 'themeisle-sdk-license-deactivate-cta' : 'themeisle-sdk-license-activate-cta' ) ),
204 esc_attr( 'valid' === $status ? $valid_string : $invalid_string ),
205 esc_attr( $this->product->get_key() ),
206 esc_attr( 'valid' === $status ? $deactivate_string : $activate_string ),
207 sprintf( wp_kses_data( $license_message ), '<a href="' . esc_url( $this->get_api_url() ) . '">' . esc_attr( $this->get_distributor_name() ) . '</a> ', esc_attr( $this->product->get_type() ) ),
208 wp_kses_data( empty( $error_message ) ? '' : sprintf( '<p style="color:#dd3d36">%s</p>', ( $error_message ) ) )
209 ) . wp_nonce_field( $this->product->get_key() . 'nonce', $this->product->get_key() . 'nonce_field', false, false );//phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
210
211 }
212
213 /**
214 * Return the license status.
215 *
216 * @param bool $check_expiration Should check if license is valid, but expired.
217 *
218 * @return string The License status.
219 */
220 public function get_license_status( $check_expiration = false ) {
221
222 $license_data = get_option( $this->product->get_key() . '_license_data', '' );
223
224 if ( '' === $license_data ) {
225 return get_option( $this->product->get_key() . '_license_status', 'not_active' );
226 }
227 $status = isset( $license_data->license ) ? $license_data->license : get_option( $this->product->get_key() . '_license_status', 'not_active' );
228 if ( false === $check_expiration ) {
229 return $status;
230 }
231
232 return ( 'valid' === $status && isset( $license_data->is_expired ) && 'yes' === $license_data->is_expired ) ? 'active_expired' : $status;
233 }
234
235 /**
236 * Check status.
237 *
238 * @param string $product_file Product basefile.
239 *
240 * @return string Status license.
241 */
242 public static function status( $product_file ) {
243 $product = Product::get( $product_file );
244 if ( ! $product->requires_license() ) {
245 return self::STATUS_VALID;
246 }
247 $license_data = self::get_license_data( $product->get_key() );
248
249 $status = isset( $license_data->license ) ? $license_data->license : self::STATUS_NOT_ACTIVE;
250
251 return ( 'valid' === $status && isset( $license_data->is_expired ) && 'yes' === $license_data->is_expired ) ? 'active_expired' : $status;
252 }
253
254 /**
255 * Product license data.
256 *
257 * @param string $key Product key.
258 *
259 * @return false|mixed|null
260 */
261 private static function get_license_data( $key ) {
262 $license_data = get_option( $key . '_license_data', '' );
263
264 return isset( $license_data->license ) ? $license_data : false;
265 }
266
267 /**
268 * Check if license is valid.
269 *
270 * @param string $product_file Product basefile.
271 *
272 * @return bool Is valid?
273 */
274 public static function is_valid( $product_file ) {
275 return self::status( $product_file ) === self::STATUS_VALID;
276 }
277
278 /**
279 * Get product plan.
280 *
281 * @param string $product_file Product file.
282 *
283 * @return int Plan id.
284 */
285 public static function plan( $product_file ) {
286 $product = Product::get( $product_file );
287 $data = self::get_license_data( $product->get_key() );
288
289 return isset( $data->price_id ) ? (int) $data->price_id : - 1;
290 }
291
292 /**
293 * Get product license key.
294 *
295 * @param string $product_file Product file.
296 *
297 * @return string
298 */
299 public static function key( $product_file ) {
300 $product = Product::get( $product_file );
301
302 return $product->get_license();
303 }
304
305 /**
306 * Return the last error message.
307 *
308 * @return mixed Error message.
309 */
310 public function get_error() {
311 return get_transient( $this->product->get_key() . 'act_err' );
312 }
313
314 /**
315 * Get remote api url.
316 *
317 * @return string Remote api url.
318 */
319 public function get_api_url() {
320 if ( $this->is_from_partner( $this->product ) ) {
321 return 'https://themeisle.com';
322 }
323
324 return $this->product->get_store_url();
325 }
326
327 /**
328 * Get remote api url.
329 *
330 * @return string Remote api url.
331 */
332 public function get_distributor_name() {
333 if ( $this->is_from_partner( $this->product ) ) {
334 return 'Themeisle';
335 }
336
337 return $this->product->get_store_name();
338 }
339
340 /**
341 * License price id.
342 *
343 * @return int License plan.
344 */
345 public function get_plan() {
346 return self::plan( $this->product->get_basefile() );
347 }
348
349 /**
350 * Show the admin notice regarding the license status.
351 *
352 * @return bool Should we show the notice ?
353 */
354 public function show_notice() {
355 if ( ! is_admin() ) {
356 return false;
357 }
358
359 if ( apply_filters( $this->product->get_key() . '_hide_license_notices', false ) ) {
360 return false;
361 }
362
363 $status = $this->get_license_status( true );
364 $no_activations_string = apply_filters( $this->product->get_key() . '_lc_no_activations_string', 'No more activations left for %s. You need to upgrade your plan in order to use %s on more websites. If you need assistance, please get in touch with %s staff.' );
365 $no_valid_string = apply_filters( $this->product->get_key() . '_lc_no_valid_string', 'In order to benefit from updates and support for %s, please add your license code from your <a href="%s" target="_blank">purchase history</a> and validate it <a href="%s">here</a>. ' );
366 $expired_license_string = apply_filters( $this->product->get_key() . '_lc_expired_string', 'Your %s\'s License Key has expired. In order to continue receiving support and software updates you must <a href="%s" target="_blank">renew</a> your license key.' );
367 // No activations left for this license.
368 if ( 'valid' != $status && $this->check_activation() ) {
369 ?>
370 <div class="error">
371 <p><strong>
372 <?php
373 echo sprintf(
374 wp_kses_data( $no_activations_string ),
375 esc_attr( $this->product->get_name() ),
376 esc_attr( $this->product->get_name() ),
377 '<a href="' . esc_url( $this->get_api_url() ) . '" target="_blank">' . esc_attr( $this->get_distributor_name() ) . '</a>'
378 );
379 ?>
380 </strong>
381 </p>
382 </div>
383 <?php
384 return false;
385 }
386
387 // Invalid license key.
388 if ( 'active_expired' === $status ) {
389 ?>
390 <div class="error">
391 <p>
392 <strong><?php echo sprintf( wp_kses_data( $expired_license_string ), esc_attr( $this->product->get_name() . ' ' . $this->product->get_type() ), esc_url( $this->get_api_url() . '?license=' . $this->license_key ) ); ?> </strong>
393 </p>
394 </div>
395 <?php
396
397 return false;
398 }
399 // Invalid license key.
400 if ( 'valid' != $status ) {
401 ?>
402 <div class="error">
403 <p>
404 <strong><?php echo sprintf( wp_kses_data( $no_valid_string ), esc_attr( $this->product->get_name() . ' ' . $this->product->get_type() ), esc_url( $this->get_api_url() ), esc_url( admin_url( 'options-general.php' ) . '#' . $this->product->get_key() . '_license' ) ); ?> </strong>
405 </p>
406 </div>
407 <?php
408
409 return false;
410 }
411
412 return true;
413 }
414
415 /**
416 * Check if the license is active or not.
417 *
418 * @return bool
419 */
420 public function check_activation() {
421 $license_data = get_option( $this->product->get_key() . '_license_data', '' );
422 if ( '' === $license_data ) {
423 return false;
424 }
425
426 return isset( $license_data->license ) ? ( 'no_activations_left' == $license_data->license ) : false;
427
428 }
429
430 /**
431 * Check if the license is about to expire in the next month.
432 *
433 * @return bool
434 */
435 public function check_expiration() {
436 $license_data = get_option( $this->product->get_key() . '_license_data', '' );
437 if ( '' === $license_data ) {
438 return false;
439 }
440 if ( ! isset( $license_data->expires ) ) {
441 return false;
442 }
443 if ( strtotime( $license_data->expires ) - time() > 30 * 24 * 3600 ) {
444 return false;
445 }
446
447 return true;
448 }
449
450 /**
451 * Return the renew url from the store used.
452 *
453 * @return string The renew url.
454 */
455 public function renew_url() {
456 $license_data = get_option( $this->product->get_key() . '_license_data', '' );
457 if ( '' === $license_data ) {
458 return $this->get_api_url();
459 }
460 if ( ! isset( $license_data->download_id ) || ! isset( $license_data->key ) ) {
461 return $this->get_api_url();
462 }
463
464 return trim( $this->get_api_url(), '/' ) . '/checkout/?edd_license_key=' . $license_data->key . '&download_id=' . $license_data->download_id;
465 }
466
467 /**
468 * Run the license check call.
469 */
470 public function product_valid() {
471 if ( false !== ( $license = get_transient( $this->product->get_key() . '_license_data' ) ) ) { //phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure
472 return;
473 }
474 $license = $this->check_license();
475 set_transient( $this->product->get_key() . '_license_data', $license, 12 * HOUR_IN_SECONDS );
476 update_option( $this->product->get_key() . '_license_data', $license );
477 }
478
479 /**
480 * Check the license status.
481 *
482 * @return object The license data.
483 */
484 public function check_license() {
485 $status = $this->get_license_status();
486 if ( 'not_active' === $status ) {
487 $license_data = new \stdClass();
488 $license_data->license = 'not_active';
489
490 return $license_data;
491 }
492 $license = trim( $this->license_key );
493
494 $response = $this->do_license_process( $license, 'check' );
495
496 if ( is_wp_error( $response ) ) {
497 $license_data = new \stdClass();
498 $license_data->license = 'invalid';
499 } else {
500 $license_data = $response;
501 }
502
503 $license_old = get_option( $this->product->get_key() . '_license_data', '' );
504 if ( 'valid' === $license_old->license && ( $license_data->license !== $license_old->license ) && $this->failed_checks <= self::$max_failed ) {
505 $this->increment_failed_checks();
506
507 return $license_old;
508 }
509
510 if ( ! isset( $license_data->key ) ) {
511 $license_data->key = isset( $license_old->key ) ? $license_old->key : '';
512 }
513 $this->reset_failed_checks();
514
515 return $license_data;
516
517 }
518
519 /**
520 * Do license activation/deactivation.
521 *
522 * @param string $license License key.
523 * @param string $action What do to.
524 *
525 * @return bool|\WP_Error
526 */
527 public function do_license_process( $license, $action = 'toggle' ) {
528 if ( strlen( $license ) < 10 ) {
529 return new \WP_Error( 'themeisle-license-invalid-format', 'Invalid license.' );
530 }
531 $status = $this->get_license_status();
532
533 if ( 'valid' === $status && 'activate' === $action ) {
534 return new \WP_Error( 'themeisle-license-already-active', 'License is already active.' );
535 }
536 if ( 'valid' !== $status && 'deactivate' === $action ) {
537 return new \WP_Error( 'themeisle-license-already-deactivate', 'License not active.' );
538 }
539
540 if ( 'toggle' === $action ) {
541 $action = ( 'valid' !== $status ? ( 'activate' ) : ( 'deactivate' ) );
542 }
543
544 // Call the custom API.
545 if ( 'check' === $action ) {
546 $response = $this->safe_get( sprintf( '%slicense/check/%s/%s/%s/%s', Product::API_URL, rawurlencode( $this->product->get_name() ), $license, rawurlencode( home_url() ), Loader::get_cache_token() ) );
547 } else {
548 $response = wp_remote_post(
549 sprintf( '%slicense/%s/%s/%s', Product::API_URL, $action, rawurlencode( $this->product->get_name() ), $license ),
550 array(
551 'body' => wp_json_encode(
552 array(
553 'url' => rawurlencode( home_url() ),
554 )
555 ),
556 'headers' => array(
557 'Content-Type' => 'application/json',
558 ),
559 )
560 );
561 }
562
563 // make sure the response came back okay.
564 if ( is_wp_error( $response ) ) {
565 return new \WP_Error( 'themeisle-license-500', sprintf( 'ERROR: Failed to connect to the license service. Please try again later. Reason: %s', $response->get_error_message() ) );
566 }
567
568 $license_data = json_decode( wp_remote_retrieve_body( $response ) );
569
570 if ( ! is_object( $license_data ) ) {
571 return new \WP_Error( 'themeisle-license-404', 'ERROR: Failed to validate license. Please try again in one minute.' );
572 }
573 if ( 'check' === $action ) {
574 return $license_data;
575 }
576
577 Loader::clear_cache_token();
578
579 if ( ! isset( $license_data->license ) ) {
580 $license_data->license = 'invalid';
581 }
582
583 if ( ! isset( $license_data->key ) ) {
584 $license_data->key = $license;
585 }
586 if ( 'valid' === $license_data->license ) {
587 $this->reset_failed_checks();
588 }
589
590 if ( 'deactivate' === $action ) {
591
592 delete_option( $this->product->get_key() . '_license_data' );
593 delete_option( $this->product->get_key() . '_license_plan' );
594 delete_transient( $this->product->get_key() . '_license_data' );
595
596 return true;
597 }
598 if ( isset( $license_data->plan ) ) {
599 update_option( $this->product->get_key() . '_license_plan', $license_data->plan );
600 }
601 update_option( $this->product->get_key() . '_license_data', $license_data );
602 set_transient( $this->product->get_key() . '_license_data', $license_data, 12 * HOUR_IN_SECONDS );
603 if ( 'activate' === $action && 'valid' !== $license_data->license ) {
604 return new \WP_Error( 'themeisle-license-invalid', 'ERROR: Invalid license provided.' );
605 }
606
607 // Remove the versions transient upon activation so that newer version for rollback can be acquired.
608 $versions_cache = $this->product->get_cache_key();
609 delete_transient( $versions_cache );
610
611 return true;
612 }
613
614 /**
615 * Reset the failed checks
616 */
617 private function reset_failed_checks() {
618 $this->failed_checks = 1;
619 update_option( $this->product->get_key() . '_failed_checks', $this->failed_checks );
620 }
621
622 /**
623 * Increment the failed checks.
624 */
625 private function increment_failed_checks() {
626 $this->failed_checks ++;
627 update_option( $this->product->get_key() . '_failed_checks', $this->failed_checks );
628 }
629
630 /**
631 * Activate the license remotely.
632 */
633 public function process_license() {
634 // listen for our activate button to be clicked.
635 if ( ! isset( $_POST[ $this->product->get_key() . '_btn_trigger' ] ) ) {
636 return;
637 }
638 if ( ! isset( $_POST[ $this->product->get_key() . 'nonce_field' ] )
639 || ! wp_verify_nonce( $_POST[ $this->product->get_key() . 'nonce_field' ], $this->product->get_key() . 'nonce' ) //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
640 ) {
641 return;
642 }
643 if ( ! current_user_can( 'manage_options' ) ) {
644 return;
645 }
646 $license = isset( $_POST[ $this->product->get_key() . '_license' ] )
647 ? sanitize_text_field( $_POST[ $this->product->get_key() . '_license' ] )
648 : '';
649
650 $response = $this->do_license_process( $license, 'toggle' );
651 if ( is_wp_error( $response ) ) {
652 $this->set_error( $response->get_error_message() );
653
654 return;
655 }
656 if ( true === $response ) {
657 $this->set_error( '' );
658 }
659 }
660
661 /**
662 * Set license validation error message.
663 *
664 * @param string $message Error message.
665 */
666 public function set_error( $message = '' ) {
667 set_transient( $this->product->get_key() . 'act_err', $message, MINUTE_IN_SECONDS );
668
669 }
670
671 /**
672 * Load the Themes screen.
673 */
674 public function load_themes_screen() {
675 add_thickbox();
676 add_action( 'admin_notices', array( &$this, 'update_nag' ) );
677 }
678
679 /**
680 * Alter the nag for themes update.
681 */
682 public function update_nag() {
683 $theme = wp_get_theme( $this->product->get_slug() );
684 $api_response = get_transient( $this->product_key );
685 if ( false === $api_response || ! isset( $api_response->new_version ) ) {
686 return;
687 }
688 $update_url = wp_nonce_url( 'update.php?action=upgrade-theme&amp;theme=' . urlencode( $this->product->get_slug() ), 'upgrade-theme_' . $this->product->get_slug() );
689 $update_message = apply_filters( 'themeisle_sdk_license_update_message', 'Updating this theme will lose any customizations you have made. Cancel to stop, OK to update.' );
690 $update_onclick = ' onclick="if ( confirm(\'' . esc_js( $update_message ) . '\') ) {return true;}return false;"';
691 if ( version_compare( $this->product->get_version(), $api_response->new_version, '<' ) ) {
692 echo '<div id="update-nag">';
693 printf(
694 '<strong>%1$s %2$s</strong> is available. <a href="%3$s" class="thickbox" title="%4s">Check out what\'s new</a> or <a href="%5$s"%6$s>update now</a>.',
695 esc_attr( $theme->get( 'Name' ) ),
696 esc_attr( $api_response->new_version ),
697 esc_url( sprintf( '%s&TB_iframe=true&amp;width=1024&amp;height=800', $this->product->get_changelog() ) ),
698 esc_attr( $theme->get( 'Name' ) ),
699 esc_url( $update_url ),
700 $update_onclick // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped, Already escaped.
701 );
702 echo '</div>';
703 echo '<div id="' . esc_attr( $this->product->get_slug() ) . '_changelog" style="display:none;">';
704 echo wp_kses_data( wpautop( $api_response->sections['changelog'] ) );
705 echo '</div>';
706 }
707 }
708
709 /**
710 * Alter update transient.
711 *
712 * @param mixed $value The transient data.
713 *
714 * @return mixed
715 */
716 public function theme_update_transient( $value ) {
717 $update_data = $this->check_for_update();
718 if ( empty( $value ) ) {
719 return $value;
720 }
721
722 if ( ! isset( $value->response ) ) {
723 return $value;
724 }
725
726 if ( ! $update_data ) {
727 return $value;
728 }
729
730 $value->response[ $this->product->get_slug() ] = $update_data;
731 return $value;
732 }
733
734 /**
735 * Check for updates
736 *
737 * @return array|bool Either the update data or false in case of failure.
738 */
739 public function check_for_update() {
740 $update_data = get_transient( $this->product_key );
741
742 if ( false === $update_data ) {
743 $failed = false;
744 $update_data = $this->get_version_data();
745 if ( empty( $update_data ) ) {
746 $failed = true;
747 }
748 // If the response failed, try again in 30 minutes.
749 if ( $failed ) {
750 $data = new \stdClass();
751 $data->new_version = $this->product->get_version();
752 set_transient( $this->product_key, $data, 30 * MINUTE_IN_SECONDS );
753
754 return false;
755 }
756 $update_data->sections = isset( $update_data->sections ) ? maybe_unserialize( $update_data->sections ) : null;
757
758 set_transient( $this->product_key, $update_data, 12 * HOUR_IN_SECONDS );
759 }
760 if ( ! isset( $update_data->new_version ) ) {
761 return false;
762 }
763 if ( version_compare( $this->product->get_version(), $update_data->new_version, '>=' ) ) {
764 return false;
765 }
766
767 return (array) $update_data;
768 }
769
770 /**
771 * Check remote api for latest version.
772 *
773 * @return bool|mixed Update api response.
774 */
775 private function get_version_data() {
776
777 $response = $this->safe_get(
778 sprintf(
779 '%slicense/version/%s/%s/%s/%s',
780 Product::API_URL,
781 rawurlencode( $this->product->get_name() ),
782 ( empty( $this->license_key ) ? 'free' : $this->license_key ),
783 $this->product->get_version(),
784 rawurlencode( home_url() )
785 ),
786 array(
787 'timeout' => 15, //phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout, Inherited by wp_remote_get only, for vip environment we use defaults.
788 'sslverify' => false,
789 )
790 );
791 if ( is_wp_error( $response ) || 200 != wp_remote_retrieve_response_code( $response ) ) {
792 return false;
793 }
794 $update_data = json_decode( wp_remote_retrieve_body( $response ) );
795 if ( ! is_object( $update_data ) ) {
796 return false;
797 }
798 if ( isset( $update_data->slug ) ) {
799 $update_data->slug = $this->product->get_slug();
800 }
801 if ( isset( $update_data->icons ) ) {
802 $update_data->icons = (array) $update_data->icons;
803 }
804 if ( isset( $update_data->banners ) ) {
805 $update_data->banners = (array) $update_data->banners;
806 }
807
808 return $update_data;
809 }
810
811 /**
812 * Delete the update transient
813 */
814 public function delete_theme_update_transient() {
815 return delete_transient( $this->product_key );
816 }
817
818 /**
819 * Check for Updates at the defined API endpoint and modify the update array.
820 *
821 * @param array $_transient_data Update array build by WordPress.
822 *
823 * @return mixed Modified update array with custom plugin data.
824 */
825 public function pre_set_site_transient_update_plugins_filter( $_transient_data ) {
826 if ( empty( $_transient_data ) || ! $this->do_check ) {
827 $this->do_check = true;
828
829 return $_transient_data;
830 }
831 $api_response = $this->api_request();
832 if ( false !== $api_response && is_object( $api_response ) && isset( $api_response->new_version ) ) {
833 if ( ! isset( $api_response->plugin ) ) {
834 $api_response->plugin = $this->product->get_slug() . '/' . $this->product->get_file();
835 }
836 if ( version_compare( $this->product->get_version(), $api_response->new_version, '<' ) ) {
837 $_transient_data->response[ $this->product->get_slug() . '/' . $this->product->get_file() ] = $api_response;
838 } else {
839 $_transient_data->no_update[ $this->product->get_slug() . '/' . $this->product->get_file() ] = $api_response;
840 }
841 }
842
843 return $_transient_data;
844 }
845
846 /**
847 * Calls the API and, if successfull, returns the object delivered by the API.
848 *
849 * @param string $_action The requested action.
850 * @param array $_data Parameters for the API action.
851 *
852 * @return false||object
853 */
854 private function api_request( $_action = '', $_data = '' ) {
855 $update_data = $this->get_version_data();
856 if ( empty( $update_data ) ) {
857 return false;
858 }
859 if ( $update_data && isset( $update_data->sections ) ) {
860 $update_data->sections = maybe_unserialize( $update_data->sections );
861 }
862
863 return $update_data;
864 }
865
866 /**
867 * Updates information on the "View version x.x details" page with custom data.
868 *
869 * @param mixed $_data Plugin data.
870 * @param string $_action Action to send.
871 * @param object $_args Arguments to use.
872 *
873 * @return object $_data
874 */
875 public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
876 if ( ( 'plugin_information' !== $_action ) || ! isset( $_args->slug ) || ( $_args->slug !== $this->product->get_slug() ) ) {
877 return $_data;
878 }
879 $api_response = $this->api_request();
880 if ( false !== $api_response ) {
881 $_data = $api_response;
882 }
883
884 return $_data;
885 }
886
887 /**
888 * Disable SSL verification in order to prevent download update failures.
889 *
890 * @param array $args Http args.
891 * @param string $url Url to check.
892 *
893 * @return array $array
894 */
895 public function http_request_args( $args, $url ) {
896 // If it is an https request and we are performing a package download, disable ssl verification.
897 if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) {
898 $args['sslverify'] = false;
899 }
900
901 return $args;
902 }
903
904 /**
905 * Check if we should load the module for this product.
906 *
907 * @param Product $product Product data.
908 *
909 * @return bool Should we load the module?
910 */
911 public function can_load( $product ) {
912
913 if ( $product->is_wordpress_available() ) {
914 return false;
915 }
916
917 return ( apply_filters( $product->get_key() . '_enable_licenser', true ) === true );
918
919 }
920
921 /**
922 * Load module logic.
923 *
924 * @param Product $product Product to load the module for.
925 *
926 * @return Licenser Module object.
927 */
928 public function load( $product ) {
929 $this->product = $product;
930
931 $this->product_key = $this->product->get_key() . '-update-response';
932
933 $this->license_key = $this->product->get_license();
934 if ( $this->product->requires_license() ) {
935 $this->failed_checks = intval( get_option( $this->product->get_key() . '_failed_checks', 0 ) );
936 $this->register_license_hooks();
937 }
938 if ( ! self::$globals_loaded ) {
939 add_filter( 'themeisle_sdk_license/status', [ __CLASS__, 'status' ], 999, 1 );
940 add_filter( 'themeisle_sdk_license/is-valid', [ __CLASS__, 'is_valid' ], 999, 1 );
941 add_filter( 'themeisle_sdk_license/plan', [ __CLASS__, 'plan' ], 999, 1 );
942 add_filter( 'themeisle_sdk_license/key', [ __CLASS__, 'key' ], 999, 1 );
943 $globals_loaded = true;
944 }
945 $namespace = apply_filters( 'themesle_sdk_namespace_' . md5( $product->get_basefile() ), false );
946
947 if ( false !== $namespace ) {
948 $this->namespace = $namespace;
949 add_filter( 'themeisle_sdk_license_process_' . $namespace, [ $this, 'do_license_process' ], 10, 2 );
950 add_filter( 'product_' . $namespace . '_license_status', [ $this, 'get_license_status' ], PHP_INT_MAX );
951 add_filter( 'product_' . $namespace . '_license_key', [ $this->product, 'get_license' ] );
952 add_filter( 'product_' . $namespace . '_license_plan', [ $this, 'get_plan' ], PHP_INT_MAX );
953 if ( defined( 'WP_CLI' ) && WP_CLI ) {
954 \WP_CLI::add_command( $namespace . ' activate', [ $this, 'cli_activate' ] );
955 \WP_CLI::add_command( $namespace . ' deactivate', [ $this, 'cli_deactivate' ] );
956 \WP_CLI::add_command( $namespace . ' is-active', [ $this, 'cli_is_active' ] );
957 }
958 }
959
960 add_action( 'admin_head', [ $this, 'auto_activate' ] );
961 if ( $this->product->is_plugin() ) {
962 add_filter(
963 'pre_set_site_transient_update_plugins',
964 [
965 $this,
966 'pre_set_site_transient_update_plugins_filter',
967 ]
968 );
969 add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
970 add_filter( 'http_request_args', array( $this, 'http_request_args' ), 10, 2 ); //phpcs:ignore WordPressVIPMinimum.Hooks.RestrictedHooks.http_request_args
971 if ( ! self::is_valid( $product->get_basefile() ) ) {
972 add_filter(
973 'plugin_action_links_' . plugin_basename( $product->get_basefile() ),
974 function ( $actions ) {
975 if ( $this->get_license_status( true ) !== self::STATUS_ACTIVE_EXPIRED ) {
976 return $actions;
977 }
978 $new_actions['deactivate'] = $actions['deactivate'];
979 $new_actions['renew_link'] = '<a style="color:#d63638" href="' . esc_url( $this->renew_url() ) . '" target="_blank" rel="external noopener noreferrer">Renew license to update</a>';
980
981 return $new_actions;
982 }
983 );
984 }
985
986 return $this;
987 }
988 if ( $this->product->is_theme() ) {
989 add_filter( 'site_transient_update_themes', array( &$this, 'theme_update_transient' ) );
990 add_action( 'delete_site_transient_update_themes', array( &$this, 'delete_theme_update_transient' ) );
991 add_action( 'load-update-core.php', array( &$this, 'delete_theme_update_transient' ) );
992 add_action( 'load-themes.php', array( &$this, 'delete_theme_update_transient' ) );
993 add_action( 'load-themes.php', array( &$this, 'load_themes_screen' ) );
994 add_filter( 'http_request_args', array( $this, 'disable_wporg_update' ), 5, 2 ); //phpcs:ignore WordPressVIPMinimum.Hooks.RestrictedHooks.http_request_args
995
996 return $this;
997
998 }
999
1000 return $this;
1001 }
1002
1003 /**
1004 * Register license fields for the products.
1005 */
1006 public function register_license_hooks() {
1007 add_action( 'admin_init', array( $this, 'register_settings' ) );
1008 add_action( 'admin_init', array( $this, 'process_license' ) );
1009 add_action( 'admin_init', array( $this, 'product_valid' ), 99999999 );
1010 add_action( 'admin_notices', array( $this, 'show_notice' ) );
1011 add_filter( $this->product->get_key() . '_license_status', array( $this, 'get_license_status' ) );
1012 }
1013
1014 /**
1015 * Check license on filesystem.
1016 *
1017 * @return mixed License key.
1018 */
1019 public function get_file_license() {
1020
1021 $license_file = dirname( $this->product->get_basefile() ) . '/license.json';
1022
1023 global $wp_filesystem;
1024 if ( ! is_file( $license_file ) ) {
1025 return false;
1026 }
1027
1028 require_once ABSPATH . '/wp-admin/includes/file.php';
1029 \WP_Filesystem();
1030 $content = json_decode( $wp_filesystem->get_contents( $license_file ) );
1031 if ( ! is_object( $content ) ) {
1032 return false;
1033 }
1034 if ( ! isset( $content->key ) ) {
1035 return false;
1036 }
1037 return $content->key;
1038 }
1039 /**
1040 * Run license activation on plugin activate.
1041 */
1042 public function auto_activate() {
1043 $status = $this->get_license_status();
1044 if ( 'not_active' !== $status ) {
1045 return false;
1046 }
1047
1048 if ( ! empty( $this->namespace ) ) {
1049 $license_key = apply_filters( 'product_' . $this->namespace . '_license_key_constant', '' );
1050 }
1051
1052 if ( empty( $license_key ) ) {
1053 $license_key = $this->get_file_license();
1054 }
1055 if ( empty( $license_key ) ) {
1056 return;
1057 }
1058
1059
1060 $this->license_local = $license_key;
1061 $lock_key = $this->product->get_key() . '_autoactivated';
1062
1063 if ( 'yes' === get_option( $lock_key, '' ) ) {
1064 return;
1065 }
1066 if ( 'yes' === get_transient( $lock_key ) ) {
1067 return;
1068 }
1069 $response = $this->do_license_process( $license_key, 'activate' );
1070
1071 set_transient( $lock_key, 'yes', 6 * HOUR_IN_SECONDS );
1072
1073 if ( apply_filters( $this->product->get_key() . '_hide_license_notices', false ) ) {
1074 return;
1075 }
1076
1077 if ( true === $response ) {
1078 add_action( 'admin_notices', [ $this, 'autoactivate_notice' ] );
1079 }
1080 }
1081
1082 /**
1083 * Show auto-activate notice.
1084 */
1085 public function autoactivate_notice() {
1086 ?>
1087 <div class="notice notice-success is-dismissible">
1088 <p><?php echo sprintf( '<strong>%s</strong> has been successfully activated using <strong>%s</strong> license !', esc_attr( $this->product->get_name() ), esc_attr( str_repeat( '*', 20 ) . substr( $this->license_local, - 10 ) ) ); ?></p>
1089 </div>
1090 <?php
1091 }
1092
1093 /**
1094 * Activate product license on this site.
1095 *
1096 * ## OPTIONS
1097 *
1098 * @param array $args Command args.
1099 *
1100 * [<license-key>]
1101 * : Product license key.
1102 */
1103 public function cli_activate( $args ) {
1104 $license_key = isset( $args[0] ) ? trim( $args[0] ) : '';
1105 $response = $this->do_license_process( $license_key, 'activate' );
1106 if ( true !== $response ) {
1107 \WP_CLI::error( $response->get_error_message() );
1108
1109 return;
1110 }
1111
1112 \WP_CLI::success( 'Product successfully activated.' );
1113 }
1114
1115 /**
1116 * Deactivate product license on this site.
1117 *
1118 * @param array $args Command args.
1119 *
1120 * ## OPTIONS
1121 *
1122 * [<license-key>]
1123 * : Product license key.
1124 */
1125 public function cli_deactivate( $args ) {
1126 $license_key = isset( $args[0] ) ? trim( $args[0] ) : '';
1127 $response = $this->do_license_process( $license_key, 'deactivate' );
1128 if ( true !== $response ) {
1129 \WP_CLI::error( $response->get_error_message() );
1130
1131 return;
1132 }
1133
1134 \WP_CLI::success( 'Product successfully deactivated.' );
1135 }
1136
1137 /**
1138 * Checks if product has license activated.
1139 *
1140 * @param array $args Command args.
1141 *
1142 * @subcommand is-active
1143 */
1144 public function cli_is_active( $args ) {
1145
1146 $status = $this->get_license_status();
1147 if ( 'valid' === $status ) {
1148 \WP_CLI::halt( 0 );
1149
1150 return;
1151 }
1152
1153 \WP_CLI::halt( 1 );
1154 }
1155 }
1156