PluginProbe ʕ •ᴥ•ʔ
Robin Image Optimizer – Unlimited Image Optimization, WebP & AVIF / 2.0.6
Robin Image Optimizer – Unlimited Image Optimization, WebP & AVIF v2.0.6
2.0.7 2.0.6 2.0.5 trunk 1.3.7 1.4.0 1.4.1 1.4.2 1.4.6 1.5.0 1.5.3 1.5.6 1.5.8 1.6.5 1.6.6 1.6.9 1.7.0 1.7.4 1.8.1 1.8.2 1.9.0 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4
robin-image-optimizer / vendor / codeinwp / themeisle-sdk / src / Modules / Licenser.php
robin-image-optimizer / vendor / codeinwp / themeisle-sdk / src / Modules Last commit date
About_us.php 1 month ago Abstract_Migration.php 4 months ago Announcements.php 4 months ago Compatibilities.php 10 months ago Crash_reporter.php 1 month ago Dashboard_widget.php 1 month ago Featured_plugins.php 1 month ago Float_widget.php 10 months ago Licenser.php 4 months ago Logger.php 1 month ago Migrator.php 1 month ago Notification.php 10 months ago Promotions.php 1 month ago Recommendation.php 10 months ago Review.php 10 months ago Rollback.php 10 months ago Script_loader.php 10 months ago Translate.php 10 months ago Translations.php 10 months ago Uninstall_feedback.php 1 month ago Welcome.php 10 months 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