PluginProbe ʕ •ᴥ•ʔ
Starter Sites & Templates by Neve / 1.4.1
Starter Sites & Templates by Neve v1.4.1
1.4.1 1.4.0 1.3.0 1.2.29 1.2.28 1.2.6 1.2.7 1.2.8 1.2.9 trunk 1.0.10 1.0.11 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 1.1.20 1.1.21 1.1.22 1.1.23 1.1.24 1.1.25 1.1.26 1.1.27 1.1.28 1.1.29 1.1.3 1.1.30 1.1.31 1.1.32 1.1.33 1.1.34 1.1.35 1.1.36 1.1.37 1.1.38 1.1.39 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.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.3 1.2.4 1.2.5
templates-patterns-collection / vendor / codeinwp / themeisle-sdk / src / Modules / Licenser.php
templates-patterns-collection / vendor / codeinwp / themeisle-sdk / src / Modules Last commit date
About_us.php 3 weeks ago Abstract_Migration.php 2 months ago Announcements.php 2 months ago Compatibilities.php 2 years ago Dashboard_widget.php 3 weeks ago Featured_plugins.php 1 month ago Float_widget.php 1 year ago Licenser.php 2 months ago Logger.php 10 months ago Migrator.php 2 months ago Notification.php 3 years ago Promotions.php 3 weeks ago Recommendation.php 3 years ago Review.php 1 year ago Rollback.php 1 year ago Script_loader.php 1 year ago Translate.php 5 years ago Translations.php 1 year ago Uninstall_feedback.php 6 days ago Welcome.php 2 years ago
Licenser.php
1280 lines
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', Loader::$labels['licenser']['activate'] );
142 $deactivate_string = apply_filters( $this->product->get_key() . '_lc_deactivate_string', Loader::$labels['licenser']['deactivate'] );
143 $valid_string = apply_filters( $this->product->get_key() . '_lc_valid_string', Loader::$labels['licenser']['valid'] );
144 $invalid_string = apply_filters( $this->product->get_key() . '_lc_invalid_string', Loader::$labels['licenser']['invalid'] );
145 $license_message = apply_filters( $this->product->get_key() . '_lc_license_message', Loader::$labels['licenser']['notice'] );
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 * Get license hash.
269 *
270 * @param string $key Product key.
271 *
272 * @return bool|string
273 */
274 public static function create_license_hash( $key ) {
275 $data = self::get_license_data( $key );
276
277 if ( ! $data ) {
278 return false;
279 }
280
281 return isset( $data->key ) ? wp_hash( $data->key ) : false;
282 }
283
284 /**
285 * Check if license is valid.
286 *
287 * @param string $product_file Product basefile.
288 *
289 * @return bool Is valid?
290 */
291 public static function is_valid( $product_file ) {
292 return self::status( $product_file ) === self::STATUS_VALID;
293 }
294
295 /**
296 * Get product plan.
297 *
298 * @param string $product_file Product file.
299 *
300 * @return int Plan id.
301 */
302 public static function plan( $product_file ) {
303 $product = Product::get( $product_file );
304 $data = self::get_license_data( $product->get_key() );
305
306 return isset( $data->price_id ) ? (int) $data->price_id : - 1;
307 }
308
309 /**
310 * Get product license key.
311 *
312 * @param string $product_file Product file.
313 *
314 * @return string
315 */
316 public static function key( $product_file ) {
317 $product = Product::get( $product_file );
318
319 return $product->get_license();
320 }
321
322 /**
323 * Return the last error message.
324 *
325 * @return mixed Error message.
326 */
327 public function get_error() {
328 return get_transient( $this->product->get_key() . 'act_err' );
329 }
330
331 /**
332 * Get remote api url.
333 *
334 * @return string Remote api url.
335 */
336 public function get_api_url() {
337 if ( $this->is_from_partner( $this->product ) ) {
338 return 'https://themeisle.com';
339 }
340
341 return $this->product->get_store_url();
342 }
343
344 /**
345 * Get remote api url.
346 *
347 * @return string Remote api url.
348 */
349 public function get_distributor_name() {
350 if ( $this->is_from_partner( $this->product ) ) {
351 return 'Themeisle';
352 }
353
354 return $this->product->get_store_name();
355 }
356
357 /**
358 * License price id.
359 *
360 * @return int License plan.
361 */
362 public function get_plan() {
363 return self::plan( $this->product->get_basefile() );
364 }
365
366 /**
367 * Show the admin notice regarding the license status.
368 *
369 * @return bool Should we show the notice ?
370 */
371 public function show_notice() {
372 if ( ! is_admin() ) {
373 return false;
374 }
375
376 if ( apply_filters( $this->product->get_key() . '_hide_license_notices', false ) ) {
377 return false;
378 }
379
380 $status = $this->get_license_status( true );
381 $no_activations_string = apply_filters( $this->product->get_key() . '_lc_no_activations_string', Loader::$labels['licenser']['no_activations'] );
382 $no_valid_string = apply_filters( $this->product->get_key() . '_lc_no_valid_string', sprintf( Loader::$labels['licenser']['inactive'], '%s', '<a href="%s" target="_blank">', '</a>', '<a href="%s">', '</a>' ) );
383 $expired_license_string = apply_filters( $this->product->get_key() . '_lc_expired_heading_string', Loader::$labels['licenser']['expired'] );
384 // No activations left for this license.
385 if ( 'valid' != $status && $this->check_activation() ) {
386 ?>
387 <div class="error">
388 <p><strong>
389 <?php
390 echo sprintf(
391 wp_kses_data( $no_activations_string ),
392 esc_attr( $this->product->get_name() ),
393 esc_attr( $this->product->get_name() ),
394 '<a href="' . esc_url( $this->get_api_url() ) . '" target="_blank">' . esc_attr( $this->get_distributor_name() ) . '</a>'
395 );
396 ?>
397 </strong>
398 </p>
399 </div>
400 <?php
401 return false;
402 }
403
404 // Invalid license key.
405 if ( 'active_expired' === $status ) {
406 // Check if the notice was dismissed.
407 $dismiss_option_key = $this->product->get_key() . '_expired_notice_dismissed';
408 if ( get_option( $dismiss_option_key, false ) ) {
409 return false;
410 }
411
412 $license_data = get_option( $this->product->get_key() . '_license_data', '' );
413 $expiration_date = '';
414 if ( is_object( $license_data ) && isset( $license_data->expires ) ) {
415 $timestamp = strtotime( (string) $license_data->expires );
416 if ( false !== $timestamp ) {
417 $expiration_date = gmdate( 'F j, Y', $timestamp );
418 }
419 }
420
421 $discount_config = apply_filters( $this->product->get_key() . '_lc_renew_discount', false );
422
423 if ( is_array( $discount_config ) && isset( $discount_config['url'] ) && isset( $discount_config['renew_button'] ) ) {
424 $renew_url = $discount_config['url'];
425 $renew_button = $discount_config['renew_button'];
426 } else {
427 $renew_url = apply_filters( $this->product->get_key() . '_lc_renew_url', $this->renew_url() );
428 $renew_button = apply_filters( $this->product->get_key() . '_lc_renew_button_string', Loader::$labels['licenser']['renew_license'] );
429 }
430
431 $learn_more_url = apply_filters( $this->product->get_key() . '_lc_learn_more_url', $this->get_api_url() );
432 $learn_more_button = apply_filters( $this->product->get_key() . '_lc_learn_more_button_string', Loader::$labels['licenser']['learn_more'] );
433 $notice_message = apply_filters( $this->product->get_key() . '_lc_expired_notice_message', Loader::$labels['licenser']['expired_notice'] );
434
435 $expired_date_string = apply_filters( $this->product->get_key() . '_lc_expired_date_string', sprintf( Loader::$labels['licenser']['expired_date'], esc_html( $expiration_date ) ) );
436 $heading = apply_filters( $this->product->get_key() . '_lc_expired_heading_string', sprintf( Loader::$labels['licenser']['expired'], $this->product->get_name() ) );
437 $notice_id = $this->product->get_key() . '_expired_notice';
438 ?>
439 <div class="notice notice-warning notice-alt is-dismissible themeisle-sdk-license-notice" id="<?php echo esc_attr( $notice_id ); ?>" data-notice-id="<?php echo esc_attr( $notice_id ); ?>" style="position: relative; border-left: 4px solid #d63638; padding: 12px; background-color: #fff;">
440 <p style="margin: 0.5em 0; font-size: 13px;">
441 <strong><?php echo wp_kses_post( $heading ); ?></strong>
442 · <?php echo esc_html( $expired_date_string ); ?>
443 </p>
444 <p style="margin: 0.5em 0 1em 0; font-size: 13px;">
445 <?php echo esc_html( $notice_message ); ?>
446 </p>
447 <p style="margin: 0.5em 0;">
448 <a href="<?php echo esc_url( $renew_url ); ?>" class="button button-primary" target="_blank" rel="noopener noreferrer">
449 <?php echo esc_html( $renew_button ); ?>
450 </a>
451 <a href="<?php echo esc_url( $learn_more_url ); ?>" class="button" target="_blank" rel="noopener noreferrer" style="border: none; background-color: transparent;">
452 <?php echo esc_html( $learn_more_button ); ?>
453 </a>
454 </p>
455 </div>
456 <script type="text/javascript">
457 jQuery(document).ready(function($) {
458 $('#<?php echo esc_js( $notice_id ); ?>').on('click', '.notice-dismiss', function(e) {
459 const noticeId = '<?php echo esc_js( $notice_id ); ?>';
460 $.ajax({
461 url: ajaxurl,
462 type: 'POST',
463 data: {
464 action: 'themeisle_sdk_dismiss_license_notice',
465 notice_id: noticeId,
466 nonce: '<?php echo esc_js( wp_create_nonce( 'themeisle_sdk_dismiss_license_notice' ) ); ?>'
467 }
468 });
469 });
470 });
471 </script>
472 <?php
473
474 return false;
475 }
476 // Invalid license key.
477 if ( 'valid' != $status ) {
478 ?>
479 <div class="error">
480 <p>
481 <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>
482 </p>
483 </div>
484 <?php
485
486 return false;
487 }
488
489 return true;
490 }
491
492 /**
493 * Check if the license is active or not.
494 *
495 * @return bool
496 */
497 public function check_activation() {
498 $license_data = get_option( $this->product->get_key() . '_license_data', '' );
499 if ( '' === $license_data ) {
500 return false;
501 }
502
503 return isset( $license_data->license ) ? ( 'no_activations_left' == $license_data->license ) : false;
504
505 }
506
507 /**
508 * Check if the license is about to expire in the next month.
509 *
510 * @return bool
511 */
512 public function check_expiration() {
513 $license_data = get_option( $this->product->get_key() . '_license_data', '' );
514 if ( '' === $license_data ) {
515 return false;
516 }
517 if ( ! isset( $license_data->expires ) ) {
518 return false;
519 }
520 if ( strtotime( $license_data->expires ) - time() > 30 * 24 * 3600 ) {
521 return false;
522 }
523
524 return true;
525 }
526
527 /**
528 * Return the renew url from the store used.
529 *
530 * @return string The renew url.
531 */
532 public function renew_url() {
533 $license_data = get_option( $this->product->get_key() . '_license_data', '' );
534 if ( '' === $license_data ) {
535 return $this->get_api_url();
536 }
537 if ( ! isset( $license_data->download_id ) || ! isset( $license_data->key ) ) {
538 return $this->get_api_url();
539 }
540
541 return trim( $this->get_api_url(), '/' ) . '/checkout/?edd_license_key=' . $license_data->key . '&download_id=' . $license_data->download_id;
542 }
543
544 /**
545 * Run the license check call.
546 */
547 public function product_valid() {
548 if ( false !== ( $license = get_transient( $this->product->get_key() . '_license_data' ) ) ) { //phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure
549 return;
550 }
551 $license = $this->check_license();
552 set_transient( $this->product->get_key() . '_license_data', $license, 12 * HOUR_IN_SECONDS );
553 update_option( $this->product->get_key() . '_license_data', $license );
554 }
555
556 /**
557 * Check the license status.
558 *
559 * @return object The license data.
560 */
561 public function check_license() {
562 $status = $this->get_license_status();
563 if ( 'not_active' === $status ) {
564 $license_data = new \stdClass();
565 $license_data->license = 'not_active';
566
567 return $license_data;
568 }
569 $license = trim( $this->license_key );
570
571 $response = $this->do_license_process( $license, 'check' );
572
573 if ( is_wp_error( $response ) ) {
574 $license_data = new \stdClass();
575 $license_data->license = 'invalid';
576 } else {
577 $license_data = $response;
578 }
579
580 $license_old = get_option( $this->product->get_key() . '_license_data', '' );
581 if ( 'valid' === $license_old->license && ( $license_data->license !== $license_old->license ) && $this->failed_checks <= self::$max_failed ) {
582 $this->increment_failed_checks();
583
584 return $license_old;
585 }
586
587 if ( ! isset( $license_data->key ) ) {
588 $license_data->key = isset( $license_old->key ) ? $license_old->key : '';
589 }
590 $this->reset_failed_checks();
591
592 return $license_data;
593
594 }
595
596 /**
597 * Do license activation/deactivation.
598 *
599 * @param string $license License key.
600 * @param string $action What do to.
601 *
602 * @return bool|\WP_Error
603 */
604 public function do_license_process( $license, $action = 'toggle' ) {
605 if ( strlen( $license ) < 10 ) {
606 return new \WP_Error( 'themeisle-license-invalid-format', Loader::$labels['licenser']['invalid_msg'] );
607 }
608 $status = $this->get_license_status();
609
610 if ( 'valid' === $status && 'activate' === $action ) {
611 return new \WP_Error( 'themeisle-license-already-active', Loader::$labels['licenser']['already_active'] );
612 }
613 if ( 'valid' !== $status && 'deactivate' === $action ) {
614 return new \WP_Error( 'themeisle-license-already-deactivate', Loader::$labels['licenser']['not_active'] );
615 }
616
617 if ( 'toggle' === $action ) {
618 $action = ( 'valid' !== $status ? ( 'activate' ) : ( 'deactivate' ) );
619 }
620
621 // Call the custom API.
622 if ( 'check' === $action ) {
623 $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() ) );
624 } else {
625 $response = wp_remote_post(
626 sprintf( '%slicense/%s/%s/%s', Product::API_URL, $action, rawurlencode( $this->product->get_name() ), $license ),
627 array(
628 'body' => wp_json_encode(
629 array(
630 'url' => rawurlencode( home_url() ),
631 )
632 ),
633 'headers' => array(
634 'Content-Type' => 'application/json',
635 ),
636 )
637 );
638 }
639
640 // make sure the response came back okay.
641 if ( is_wp_error( $response ) ) {
642 return new \WP_Error( 'themeisle-license-500', sprintf( Loader::$labels['licenser']['error_notice'], $response->get_error_message() ) );
643 }
644
645 $license_data = json_decode( wp_remote_retrieve_body( $response ) );
646
647 if ( ! is_object( $license_data ) ) {
648 return new \WP_Error( 'themeisle-license-404', Loader::$labels['licenser']['error_notice2'] );
649 }
650 if ( 'check' === $action ) {
651 return $license_data;
652 }
653
654 Loader::clear_cache_token();
655
656 if ( ! isset( $license_data->license ) ) {
657 $license_data->license = 'invalid';
658 }
659
660 if ( ! isset( $license_data->key ) ) {
661 $license_data->key = $license;
662 }
663 if ( 'valid' === $license_data->license ) {
664 $this->reset_failed_checks();
665 }
666
667 if ( 'deactivate' === $action ) {
668
669 delete_option( $this->product->get_key() . '_license_data' );
670 delete_option( $this->product->get_key() . '_license_plan' );
671 delete_transient( $this->product->get_key() . '_license_data' );
672
673 return true;
674 }
675 if ( isset( $license_data->plan ) ) {
676 update_option( $this->product->get_key() . '_license_plan', $license_data->plan );
677 }
678 update_option( $this->product->get_key() . '_license_data', $license_data );
679 set_transient( $this->product->get_key() . '_license_data', $license_data, 12 * HOUR_IN_SECONDS );
680 if ( 'activate' === $action && 'valid' !== $license_data->license ) {
681 return new \WP_Error( 'themeisle-license-invalid', Loader::$labels['licenser']['error_invalid'] );
682 }
683
684 // Remove the versions transient upon activation so that newer version for rollback can be acquired.
685 $versions_cache = $this->product->get_cache_key();
686 delete_transient( $versions_cache );
687
688 return true;
689 }
690
691 /**
692 * Reset the failed checks
693 */
694 private function reset_failed_checks() {
695 $this->failed_checks = 1;
696 update_option( $this->product->get_key() . '_failed_checks', $this->failed_checks );
697 }
698
699 /**
700 * Increment the failed checks.
701 */
702 private function increment_failed_checks() {
703 $this->failed_checks ++;
704 update_option( $this->product->get_key() . '_failed_checks', $this->failed_checks );
705 }
706
707 /**
708 * Activate the license remotely.
709 */
710 public function process_license() {
711 // listen for our activate button to be clicked.
712 if ( ! isset( $_POST[ $this->product->get_key() . '_btn_trigger' ] ) ) {
713 return;
714 }
715 if ( ! isset( $_POST[ $this->product->get_key() . 'nonce_field' ] )
716 || ! wp_verify_nonce( $_POST[ $this->product->get_key() . 'nonce_field' ], $this->product->get_key() . 'nonce' ) //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
717 ) {
718 return;
719 }
720 if ( ! current_user_can( 'manage_options' ) ) {
721 return;
722 }
723 $license = isset( $_POST[ $this->product->get_key() . '_license' ] )
724 ? sanitize_text_field( $_POST[ $this->product->get_key() . '_license' ] )
725 : '';
726
727 $response = $this->do_license_process( $license, 'toggle' );
728 if ( is_wp_error( $response ) ) {
729 $this->set_error( $response->get_error_message() );
730
731 return;
732 }
733 if ( true === $response ) {
734 $this->set_error( '' );
735 }
736 }
737
738 /**
739 * Set license validation error message.
740 *
741 * @param string $message Error message.
742 */
743 public function set_error( $message = '' ) {
744 set_transient( $this->product->get_key() . 'act_err', $message, MINUTE_IN_SECONDS );
745
746 }
747
748 /**
749 * Load the Themes screen.
750 */
751 public function load_themes_screen() {
752 add_thickbox();
753 add_action( 'admin_notices', array( &$this, 'update_nag' ) );
754 }
755
756 /**
757 * Alter the nag for themes update.
758 */
759 public function update_nag() {
760 $theme = wp_get_theme( $this->product->get_slug() );
761 $api_response = get_transient( $this->product_key );
762 if ( false === $api_response || ! isset( $api_response->new_version ) ) {
763 return;
764 }
765 $update_url = wp_nonce_url( 'update.php?action=upgrade-theme&amp;theme=' . urlencode( $this->product->get_slug() ), 'upgrade-theme_' . $this->product->get_slug() );
766 $update_message = apply_filters( 'themeisle_sdk_license_update_message', Loader::$labels['licenser']['update_license'] );
767 $update_onclick = ' onclick="if ( confirm(\'' . esc_js( $update_message ) . '\') ) {return true;}return false;"';
768 if ( version_compare( $this->product->get_version(), $api_response->new_version, '<' ) ) {
769 echo '<div id="update-nag">';
770 printf(
771 esc_html( Loader::$labels['licenser']['notice_update'] ),
772 '<strong>' . esc_attr( $theme->get( 'Name' ) ) . ' ' . esc_attr( $api_response->new_version ) . '</strong>',
773 '<a href="' . esc_url( sprintf( '%s&TB_iframe=true&amp;width=1024&amp;height=800', $this->product->get_changelog() ) ) . '" class="thickbox" title="' . esc_attr( $theme->get( 'Name' ) ) . '">',
774 '</a>',
775 '<a href="' . esc_url( $update_url ) . '" ' . $update_onclick . '>' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped, Already escaped.
776 );
777 echo '</div>';
778 echo '<div id="' . esc_attr( $this->product->get_slug() ) . '_changelog" style="display:none;">';
779 echo wp_kses_data( wpautop( $api_response->sections['changelog'] ) );
780 echo '</div>';
781 }
782 }
783
784 /**
785 * Alter update transient.
786 *
787 * @param mixed $value The transient data.
788 *
789 * @return mixed
790 */
791 public function theme_update_transient( $value ) {
792 $update_data = $this->check_for_update();
793 if ( empty( $value ) ) {
794 return $value;
795 }
796
797 if ( ! isset( $value->response ) ) {
798 return $value;
799 }
800
801 if ( ! $update_data ) {
802 return $value;
803 }
804
805 $value->response[ $this->product->get_slug() ] = $update_data;
806
807 return $value;
808 }
809
810 /**
811 * Check for updates
812 *
813 * @return array|bool Either the update data or false in case of failure.
814 */
815 public function check_for_update() {
816 $update_data = get_transient( $this->product_key );
817
818 if ( false === $update_data ) {
819 $failed = false;
820 $update_data = $this->get_version_data();
821 if ( empty( $update_data ) ) {
822 $failed = true;
823 }
824 // If the response failed, try again in 30 minutes.
825 if ( $failed ) {
826 $data = new \stdClass();
827 $data->new_version = $this->product->get_version();
828 set_transient( $this->product_key, $data, 30 * MINUTE_IN_SECONDS );
829
830 return false;
831 }
832 $update_data->sections = isset( $update_data->sections ) ? maybe_unserialize( $update_data->sections ) : null;
833
834 set_transient( $this->product_key, $update_data, 12 * HOUR_IN_SECONDS );
835 }
836 if ( ! isset( $update_data->new_version ) ) {
837 return false;
838 }
839 if ( version_compare( $this->product->get_version(), $update_data->new_version, '>=' ) ) {
840 return false;
841 }
842
843 return (array) $update_data;
844 }
845
846 /**
847 * Check remote api for latest version.
848 *
849 * @return bool|mixed Update api response.
850 */
851 private function get_version_data() {
852
853 $response = $this->safe_get(
854 sprintf(
855 '%slicense/version/%s/%s/%s/%s',
856 Product::API_URL,
857 rawurlencode( $this->product->get_name() ),
858 ( empty( $this->license_key ) ? 'free' : $this->license_key ),
859 $this->product->get_version(),
860 rawurlencode( home_url() )
861 ),
862 array(
863 'timeout' => 15, //phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout, Inherited by wp_remote_get only, for vip environment we use defaults.
864 'sslverify' => false,
865 )
866 );
867 if ( is_wp_error( $response ) || 200 != wp_remote_retrieve_response_code( $response ) ) {
868 return false;
869 }
870 $update_data = json_decode( wp_remote_retrieve_body( $response ) );
871 if ( ! is_object( $update_data ) ) {
872 return false;
873 }
874 if ( isset( $update_data->slug ) ) {
875 $update_data->slug = $this->product->get_slug();
876 }
877 if ( isset( $update_data->icons ) ) {
878 $update_data->icons = (array) $update_data->icons;
879 }
880 if ( isset( $update_data->banners ) ) {
881 $update_data->banners = (array) $update_data->banners;
882 }
883
884 return $update_data;
885 }
886
887 /**
888 * Delete the update transient
889 */
890 public function delete_theme_update_transient() {
891 return delete_transient( $this->product_key );
892 }
893
894 /**
895 * Check for Updates at the defined API endpoint and modify the update array.
896 *
897 * @param array $_transient_data Update array build by WordPress.
898 *
899 * @return mixed Modified update array with custom plugin data.
900 */
901 public function pre_set_site_transient_update_plugins_filter( $_transient_data ) {
902 if ( empty( $_transient_data ) || ! $this->do_check ) {
903 $this->do_check = true;
904
905 return $_transient_data;
906 }
907 $api_response = $this->api_request();
908 if ( false !== $api_response && is_object( $api_response ) && isset( $api_response->new_version ) ) {
909 if ( ! isset( $api_response->plugin ) ) {
910 $api_response->plugin = $this->product->get_slug() . '/' . $this->product->get_file();
911 }
912 if ( version_compare( $this->product->get_version(), $api_response->new_version, '<' ) ) {
913 $_transient_data->response[ $this->product->get_slug() . '/' . $this->product->get_file() ] = $api_response;
914 } else {
915 $_transient_data->no_update[ $this->product->get_slug() . '/' . $this->product->get_file() ] = $api_response;
916 }
917 }
918
919 return $_transient_data;
920 }
921
922 /**
923 * Calls the API and, if successfull, returns the object delivered by the API.
924 *
925 * @param string $_action The requested action.
926 * @param array $_data Parameters for the API action.
927 *
928 * @return false||object
929 */
930 private function api_request( $_action = '', $_data = '' ) {
931 $update_data = $this->get_version_data();
932 if ( empty( $update_data ) ) {
933 return false;
934 }
935 if ( $update_data && isset( $update_data->sections ) ) {
936 $update_data->sections = maybe_unserialize( $update_data->sections );
937 }
938
939 return $update_data;
940 }
941
942 /**
943 * Updates information on the "View version x.x details" page with custom data.
944 *
945 * @param mixed $_data Plugin data.
946 * @param string $_action Action to send.
947 * @param object $_args Arguments to use.
948 *
949 * @return object $_data
950 */
951 public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
952 if ( ( 'plugin_information' !== $_action ) || ! isset( $_args->slug ) || ( $_args->slug !== $this->product->get_slug() ) ) {
953 return $_data;
954 }
955 $api_response = $this->api_request();
956 if ( false !== $api_response ) {
957 $_data = $api_response;
958 }
959
960 return $_data;
961 }
962
963 /**
964 * Disable SSL verification in order to prevent download update failures.
965 *
966 * @param array $args Http args.
967 * @param string $url Url to check.
968 *
969 * @return array $array
970 */
971 public function http_request_args( $args, $url ) {
972 // If it is an https request and we are performing a package download, disable ssl verification.
973 if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) {
974 $args['sslverify'] = false;
975 }
976
977 return $args;
978 }
979
980 /**
981 * Check if we should load the module for this product.
982 *
983 * @param Product $product Product data.
984 *
985 * @return bool Should we load the module?
986 */
987 public function can_load( $product ) {
988
989 if ( ! $product->requires_license() ) {
990 return false;
991 }
992
993 return ( apply_filters( $product->get_key() . '_enable_licenser', true ) === true );
994
995 }
996
997 /**
998 * Load module logic.
999 *
1000 * @param Product $product Product to load the module for.
1001 *
1002 * @return Licenser Module object.
1003 */
1004 public function load( $product ) {
1005 $this->product = $product;
1006
1007 $this->product_key = $this->product->get_key() . '-update-response';
1008
1009 $this->license_key = $this->product->get_license();
1010 if ( $this->product->requires_license() ) {
1011 $this->failed_checks = intval( get_option( $this->product->get_key() . '_failed_checks', 0 ) );
1012 $this->register_license_hooks();
1013 }
1014 if ( ! self::$globals_loaded ) {
1015 add_filter( 'themeisle_sdk_license/status', [ __CLASS__, 'status' ], 999, 1 );
1016 add_filter( 'themeisle_sdk_license/is-valid', [ __CLASS__, 'is_valid' ], 999, 1 );
1017 add_filter( 'themeisle_sdk_license/plan', [ __CLASS__, 'plan' ], 999, 1 );
1018 add_filter( 'themeisle_sdk_license/key', [ __CLASS__, 'key' ], 999, 1 );
1019 $globals_loaded = true;
1020 }
1021 $namespace = apply_filters( 'themesle_sdk_namespace_' . md5( $product->get_basefile() ), false );
1022
1023 if ( false !== $namespace ) {
1024 $this->namespace = $namespace;
1025 add_filter( 'themeisle_sdk_license_process_' . $namespace, [ $this, 'do_license_process' ], 10, 2 );
1026 add_filter( 'product_' . $namespace . '_license_status', [ $this, 'get_license_status' ], PHP_INT_MAX );
1027 add_filter( 'product_' . $namespace . '_license_key', [ $this->product, 'get_license' ] );
1028 add_filter( 'product_' . $namespace . '_license_plan', [ $this, 'get_plan' ], PHP_INT_MAX );
1029 if ( defined( 'WP_CLI' ) && WP_CLI ) {
1030 \WP_CLI::add_command( $namespace . ' activate', [ $this, 'cli_activate' ] );
1031 \WP_CLI::add_command( $namespace . ' deactivate', [ $this, 'cli_deactivate' ] );
1032 \WP_CLI::add_command( $namespace . ' is-active', [ $this, 'cli_is_active' ] );
1033 }
1034 }
1035
1036 add_action( 'admin_head', [ $this, 'auto_activate' ] );
1037 if ( $this->product->is_plugin() ) {
1038 if ( ! $product->is_wordpress_available() ) {
1039 add_filter(
1040 'pre_set_site_transient_update_plugins',
1041 [
1042 $this,
1043 'pre_set_site_transient_update_plugins_filter',
1044 ]
1045 );
1046 add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
1047 add_filter( //phpcs:ignore WordPressVIPMinimum.Hooks.RestrictedHooks.http_request_args
1048 'http_request_args',
1049 array(
1050 $this,
1051 'http_request_args',
1052 ),
1053 10,
1054 2
1055 );
1056 if ( ! self::is_valid( $product->get_basefile() ) ) {
1057 add_filter(
1058 'plugin_action_links_' . plugin_basename( $product->get_basefile() ),
1059 function ( $actions ) {
1060 if ( $this->get_license_status( true ) !== self::STATUS_ACTIVE_EXPIRED ) {
1061 return $actions;
1062 }
1063 $new_actions['deactivate'] = $actions['deactivate'];
1064 $new_actions['renew_link'] = '<a style="color:#d63638" href="' . esc_url( $this->renew_url() ) . '" target="_blank" rel="external noopener noreferrer">' . esc_html( Loader::$labels['licenser']['renew_cta'] ) . '</a>';
1065
1066 return $new_actions;
1067 }
1068 );
1069 }
1070 }
1071
1072 return $this;
1073 }
1074 if ( $this->product->is_theme() ) {
1075 if ( ! $product->is_wordpress_available() ) {
1076 add_filter( 'site_transient_update_themes', array( &$this, 'theme_update_transient' ) );
1077 add_action( 'delete_site_transient_update_themes', array( &$this, 'delete_theme_update_transient' ) );
1078 add_action( 'load-update-core.php', array( &$this, 'delete_theme_update_transient' ) );
1079 add_action( 'load-themes.php', array( &$this, 'delete_theme_update_transient' ) );
1080 add_action( 'load-themes.php', array( &$this, 'load_themes_screen' ) );
1081 add_filter( //phpcs:ignore WordPressVIPMinimum.Hooks.RestrictedHooks.http_request_args
1082 'http_request_args',
1083 array(
1084 $this,
1085 'disable_wporg_update',
1086 ),
1087 5,
1088 2
1089 );
1090 }
1091
1092 return $this;
1093
1094 }
1095
1096 return $this;
1097 }
1098
1099 /**
1100 * Register license fields for the products.
1101 */
1102 public function register_license_hooks() {
1103 add_action( 'admin_init', array( $this, 'register_settings' ) );
1104 add_action( 'admin_init', array( $this, 'process_license' ) );
1105 add_action( 'admin_init', array( $this, 'product_valid' ), 99999999 );
1106 add_action( 'admin_notices', array( $this, 'show_notice' ) );
1107 add_filter( $this->product->get_key() . '_license_status', array( $this, 'get_license_status' ) );
1108 add_action( 'wp_ajax_themeisle_sdk_dismiss_license_notice', array( $this, 'dismiss_license_notice' ) );
1109 }
1110
1111 /**
1112 * Handle AJAX request to dismiss the license notice.
1113 */
1114 public function dismiss_license_notice() {
1115 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( $_POST['nonce'] ), 'themeisle_sdk_dismiss_license_notice' ) ) {
1116 wp_send_json_error( 'Invalid nonce' );
1117 }
1118
1119 if ( ! current_user_can( 'manage_options' ) ) {
1120 wp_send_json_error( 'Insufficient permissions' );
1121 }
1122
1123 $notice_id = isset( $_POST['notice_id'] ) ? sanitize_text_field( $_POST['notice_id'] ) : '';
1124
1125 if ( empty( $notice_id ) ) {
1126 wp_send_json_error( 'Missing notice ID' );
1127 }
1128
1129 // Save the dismissal option.
1130 $dismiss_option_key = $notice_id . '_dismissed';
1131 update_option( $dismiss_option_key, true );
1132
1133 wp_send_json_success();
1134 }
1135
1136 /**
1137 * Check license on filesystem.
1138 *
1139 * @return mixed License key.
1140 */
1141 public function get_file_license() {
1142
1143 $license_file = dirname( $this->product->get_basefile() ) . '/license.json';
1144
1145 global $wp_filesystem;
1146 if ( ! is_file( $license_file ) ) {
1147 return false;
1148 }
1149
1150 require_once ABSPATH . '/wp-admin/includes/file.php';
1151 \WP_Filesystem();
1152 $content = json_decode( $wp_filesystem->get_contents( $license_file ) );
1153 if ( ! is_object( $content ) ) {
1154 return false;
1155 }
1156 if ( ! isset( $content->key ) ) {
1157 return false;
1158 }
1159
1160 return $content->key;
1161 }
1162
1163 /**
1164 * Run license activation on plugin activate.
1165 */
1166 public function auto_activate() {
1167 $status = $this->get_license_status();
1168 if ( 'not_active' !== $status ) {
1169 return false;
1170 }
1171
1172 if ( ! empty( $this->namespace ) ) {
1173 $license_key = apply_filters( 'product_' . $this->namespace . '_license_key_constant', '' );
1174 }
1175
1176 if ( empty( $license_key ) ) {
1177 $license_key = $this->get_file_license();
1178 }
1179 if ( empty( $license_key ) ) {
1180 return;
1181 }
1182
1183
1184 $this->license_local = $license_key;
1185 $lock_key = $this->product->get_key() . '_autoactivated';
1186
1187 if ( 'yes' === get_option( $lock_key, '' ) ) {
1188 return;
1189 }
1190 if ( 'yes' === get_transient( $lock_key ) ) {
1191 return;
1192 }
1193 $response = $this->do_license_process( $license_key, 'activate' );
1194
1195 set_transient( $lock_key, 'yes', 6 * HOUR_IN_SECONDS );
1196
1197 if ( apply_filters( $this->product->get_key() . '_hide_license_notices', false ) ) {
1198 return;
1199 }
1200
1201 if ( true === $response ) {
1202 add_action( 'admin_notices', [ $this, 'autoactivate_notice' ] );
1203 }
1204 }
1205
1206 /**
1207 * Show auto-activate notice.
1208 */
1209 public function autoactivate_notice() {
1210 ?>
1211 <div class="notice notice-success is-dismissible">
1212 <p><?php echo sprintf( esc_html( Loader::$labels['licenser']['autoactivate_notice'] ), '<strong>' . esc_attr( $this->product->get_name() ) . '</strong>', '<strong>' . esc_attr( str_repeat( '*', 20 ) . substr( $this->license_local, - 10 ) ) . '</strong>' ); ?></p>
1213 </div>
1214 <?php
1215 }
1216
1217 /**
1218 * Activate product license on this site.
1219 *
1220 * ## OPTIONS
1221 *
1222 * @param array $args Command args.
1223 *
1224 * [<license-key>]
1225 * : Product license key.
1226 */
1227 public function cli_activate( $args ) {
1228 $license_key = isset( $args[0] ) ? trim( $args[0] ) : '';
1229 $response = $this->do_license_process( $license_key, 'activate' );
1230 if ( true !== $response ) {
1231 \WP_CLI::error( $response->get_error_message() );
1232
1233 return;
1234 }
1235
1236 \WP_CLI::success( 'Product successfully activated.' );
1237 }
1238
1239 /**
1240 * Deactivate product license on this site.
1241 *
1242 * @param array $args Command args.
1243 *
1244 * ## OPTIONS
1245 *
1246 * [<license-key>]
1247 * : Product license key.
1248 */
1249 public function cli_deactivate( $args ) {
1250 $license_key = isset( $args[0] ) ? trim( $args[0] ) : '';
1251 $response = $this->do_license_process( $license_key, 'deactivate' );
1252 if ( true !== $response ) {
1253 \WP_CLI::error( $response->get_error_message() );
1254
1255 return;
1256 }
1257
1258 \WP_CLI::success( 'Product successfully deactivated.' );
1259 }
1260
1261 /**
1262 * Checks if product has license activated.
1263 *
1264 * @param array $args Command args.
1265 *
1266 * @subcommand is-active
1267 */
1268 public function cli_is_active( $args ) {
1269
1270 $status = $this->get_license_status();
1271 if ( 'valid' === $status ) {
1272 \WP_CLI::halt( 0 );
1273
1274 return;
1275 }
1276
1277 \WP_CLI::halt( 1 );
1278 }
1279 }
1280