PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.6.6
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.6.6
1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 1.7.0.9 All 211 releases
charitable / includes / licensing / class-charitable-licenses.php

class-charitable-licenses.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.6.6, at includes/licensing/class-charitable-licenses.php

665 lines 17.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class to assist with the setup of extension licenses.
4 *
5 * @package Charitable/Classes/Charitable_Licenses
6 * @version 1.4.20
7 * @author Eric Daams
8 * @copyright Copyright (c) 2018, Studio 164a
9 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
10 */
11
12 // Exit if accessed directly.
13 if ( ! defined( 'ABSPATH' ) ) { exit; }
14
15 if ( ! class_exists( 'Charitable_Licenses' ) ) :
16
17 /**
18 * Charitable_Licenses
19 *
20 * @since 1.0.0
21 */
22 class Charitable_Licenses {
23
24 /* @var string */
25 const UPDATE_URL = 'https://www.wpcharitable.com';
26
27 /**
28 * The single instance of this class.
29 *
30 * @var Charitable_Licenses|null
31 */
32 private static $instance = null;
33
34 /**
35 * All the registered products requiring licensing.
36 *
37 * @var array
38 */
39 private $products;
40
41 /**
42 * All the stored licenses.
43 *
44 * @var array
45 */
46 private $licenses;
47
48 /**
49 * Cached update data.
50 *
51 * @var array
52 */
53 private $update_data;
54
55 /**
56 * Returns and/or create the single instance of this class.
57 *
58 * @since 1.2.0
59 *
60 * @return Charitable_Licenses
61 */
62 public static function get_instance() {
63 if ( is_null( self::$instance ) ) {
64 self::$instance = new self();
65 }
66
67 return self::$instance;
68 }
69
70 /**
71 * Create class object.
72 *
73 * @since 1.0.0
74 */
75 private function __construct() {
76 $this->products = array();
77 $this->update_data = array();
78
79 add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_for_updates' ) );
80 add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
81 add_action( 'charitable_deactivate_license', array( $this, 'deactivate_license' ) );
82 add_filter( 'upgrader_pre_download', array( $this, 'set_upgrader_error_message' ), 10, 3 );
83 add_filter( 'upgrader_package_options', array( $this, 'set_upgrader_package_options' ) );
84 }
85
86 /**
87 * Checks for any Charitable extensions with updates.
88 *
89 * @since 1.4.0
90 *
91 * @param array $_transient_data The plugin updates data.
92 * @return array
93 */
94 public function check_for_updates( $_transient_data ) {
95 global $pagenow;
96
97 if ( ! is_object( $_transient_data ) ) {
98 $_transient_data = new stdClass;
99 }
100
101 if ( 'plugins.php' == $pagenow && is_multisite() ) {
102 return $_transient_data;
103 }
104
105 /* Loop over our licensed products and check whether any are missing transient data. */
106 $missing_data = array();
107
108 foreach ( $this->get_products() as $product ) {
109 if ( $this->is_missing_version_info( $product, $_transient_data ) ) {
110 $missing_data[] = $product;
111 }
112 }
113
114 /* If we are missing data for any of our products, check whether any have an update. */
115 if ( ! empty( $missing_data ) ) {
116
117 $versions = $this->get_versions();
118
119 unset( $versions['request_speed'] );
120
121 if ( ! empty( $versions ) ) {
122
123 $versions_name_lookup = wp_list_pluck( $versions, 'name' );
124
125 foreach ( $missing_data as $product ) {
126
127 if ( ! in_array( $product['name'], $versions_name_lookup ) ) {
128 continue;
129 }
130
131 $plugin_file = plugin_basename( $product['file'] );
132 $product_key = array_search( $product['name'], wp_list_pluck( $this->get_products(), 'name' ) );
133 $version_info = $versions[ array_search( $product['name'], $versions_name_lookup ) ];
134 $version_info['license'] = $this->get_license_details( $product_key );
135
136 if ( version_compare( $product['version'], $version_info['new_version'], '<' ) ) {
137
138 if ( isset( $version_info['sections'] ) ) {
139 $version_info['sections'] = maybe_unserialize( $version_info['sections'] );
140 }
141
142 $_transient_data->response[ $plugin_file ] = (object) $version_info;
143
144 }
145
146 $_transient_data->last_checked = time();
147 $_transient_data->checked[ $plugin_file ] = $product['version'];
148
149 }//end foreach
150 }//end if
151 }//end if
152
153 return $_transient_data;
154 }
155
156 /**
157 * Updates information on the "View version x.x details" page with custom data.
158 *
159 * @uses api_request()
160 *
161 * @since 1.4.20
162 *
163 * @param mixed $_data Default set of data.
164 * @param string $_action The current action.
165 * @param object $_args Request args.
166 * @return object $_data
167 */
168 public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
169 if ( 'plugin_information' != $_action ) {
170 return $_data;
171 }
172
173 if ( ! isset( $_args->slug ) ) {
174 return $_data;
175 }
176
177 $plugin_key = str_replace( '-', '_', $_args->slug );
178
179 if ( ! array_key_exists( $plugin_key, $this->products ) ) {
180 return $_data;
181 }
182
183 $version_info = $this->get_version_info( plugin_basename( $this->products[ $plugin_key ]['file'] ) );
184
185 if ( $version_info ) {
186 $_data = $version_info;
187 }
188
189 return $_data;
190 }
191
192 /**
193 * Return whether a particular plugin is missing version info.
194 *
195 * @since 1.4.20
196 *
197 * @param array $product Product details array.
198 * @param false|object $update_cache Optional argument to pass update cache.
199 * @return boolean
200 */
201 public function is_missing_version_info( $product, $update_cache = false ) {
202 return ! $this->get_version_info( plugin_basename( $product['file'] ), $update_cache );
203 }
204
205 /**
206 * Return the version update info for a particular plugin.
207 *
208 * @since 1.4.20
209 *
210 * @param string $slug The plugin slug.
211 * @param false|object $update_cache Optional argument to pass update cache.
212 * @return array|false Array if an update is available. False otherwise.
213 */
214 public function get_version_info( $slug, $update_cache = false ) {
215 if ( ! $update_cache ) {
216 $update_cache = get_site_transient( 'update_plugins' );
217 }
218
219 if ( ! is_object( $update_cache ) || empty( $update_cache->response ) || ! array_key_exists( $slug, $update_cache->response ) ) {
220 return false;
221 }
222
223 return $update_cache->response[ $slug ];
224 }
225
226 /**
227 * Display an error message when users attempt to update a plugin
228 * without a license or with an expired license.
229 *
230 * @since 1.4.20
231 *
232 * @param false|WP_Error $reply The messaage to return. Set to false by default.
233 * @param string $package Package URL. For Charitable extensions without a
234 * license or that have expired, we set this to a
235 * key. Kind of a hack to pass a message to this hook.
236 * @param Plugin_Upgrader $upgrader The Plugin_Upgrader object.
237 * @return false|WP_Error
238 */
239 public function set_upgrader_error_message( $reply, $package, $upgrader ) {
240 $ajax_skin = 'WP_Ajax_Upgrader_Skin' == get_class( $upgrader->skin );
241
242 if ( 'missing_license' == $package ) {
243 if ( $ajax_skin ) {
244 $message = sprintf( __( 'You have not activated your license key. Activate your license to update: %s', 'charitable' ),
245 admin_url( 'admin.php?page=charitable-settings&tab=licenses' )
246 );
247 } else {
248 $message = sprintf( __( 'You have not activated your license key. <a href="%s" target="_top">Activate your license to update.</a>', 'charitable' ),
249 admin_url( 'admin.php?page=charitable-settings&tab=licenses' )
250 );
251 }
252
253 return new WP_Error( 'missing_license_key', $message );
254 }
255
256 if ( false !== strpos( $package, 'expired_license:' ) ) {
257
258 $renewal_link = str_replace( 'expired_license:', '', $package );
259
260 if ( $ajax_skin ) {
261 $message = sprintf( __( 'Your license has expired. Renew your license key: %s', 'charitable' ),
262 $renewal_link
263 );
264 } else {
265 $message = sprintf( __( 'Your license has expired. <a href="%s" target="_blank">Renew your license key.</a>', 'charitable' ),
266 esc_url( $renewal_link )
267 );
268 }
269
270 return new WP_Error( 'expired_license_key', $message );
271 }
272
273 return $reply;
274 }
275
276 /**
277 * Set upgrader package options.
278 *
279 * This is used for expired licenses and in certain cases for products that do
280 * not have a license activated.
281 *
282 * @since 1.4.20
283 *
284 * @param array $options Upgrader package options.
285 * @return array
286 */
287 public function set_upgrader_package_options( $options ) {
288 if ( 'expired_license' != $options['package'] && empty( $options['package'] ) ) {
289 return $options;
290 }
291
292 if ( ! array_key_exists( 'hook_extra', $options ) || ! array_key_exists( 'plugin', $options['hook_extra'] ) ) {
293 return $options;
294 }
295
296 list( $plugin_key, ) = explode( '/', str_replace( '-', '_', $options['hook_extra']['plugin'] ) );
297
298 if ( ! array_key_exists( $plugin_key, $this->products ) ) {
299 return $options;
300 }
301
302 /* Set up the renewal link for expired licenses. */
303 if ( 'expired_license' == $options['package'] ) {
304 $options['package'] = $this->get_expired_license_package( $options['hook_extra']['plugin'] );
305
306 return $options;
307 }
308
309 $license_details = $this->get_license_details( $plugin_key );
310
311 if ( ! is_array( $license_details ) || ! array_key_exists( 'license', $license_details ) || empty( $license_details['license'] ) ) {
312 $options['package'] = 'missing_license';
313 }
314
315 return $options;
316 }
317
318 /**
319 * Return the package string for an expired license.
320 *
321 * @since 1.4.20
322 *
323 * @param string $plugin Plugin basename.
324 * @return string
325 */
326 public function get_expired_license_package( $plugin ) {
327 $version_info = $this->get_version_info( $plugin );
328 $base_renewal_url = isset( $version_info->renewal_link ) ? $version_info->renewal_link : 'https://www.wpcharitable.com/account';
329
330 return sprintf( 'expired_license:%s', add_query_arg( array(
331 'utm_source' => 'plugin-upgrades',
332 'utm_medium' => 'wordpress-dashboard',
333 'utm_campaign' => 'expired-license',
334 ), $base_renewal_url ) );
335 }
336
337 /**
338 * Register a product that requires licensing.
339 *
340 * @since 1.0.0
341 *
342 * @param string $item_name The title of the product.
343 * @param string $author The author of the product.
344 * @param string $version The current product version we have installed.
345 * @param string $file The path to the plugin file.
346 * @param string $url The base URL where the plugin is licensed. Defaults to Charitable_Licenses::UPDATE_URL.
347 * @return void
348 */
349 public function register_licensed_product( $item_name, $author, $version, $file, $url = false ) {
350 if ( ! $url ) {
351 $url = Charitable_Licenses::UPDATE_URL;
352 }
353
354 $product_key = $this->get_item_key( $item_name );
355
356 $this->products[ $product_key ] = array(
357 'name' => $item_name,
358 'author' => $author,
359 'version' => $version,
360 'url' => $url,
361 'file' => $file,
362 );
363
364 $licenses = $this->get_licenses();
365 $license = isset( $licenses[ $product_key ]['license'] ) ? trim( $licenses[ $product_key ]['license'] ) : '';
366
367 new Charitable_Plugin_Updater( $url, $file, array(
368 'version' => $version,
369 'license' => $license,
370 'item_name' => $item_name,
371 'author' => $author,
372 ) );
373 }
374
375 /**
376 * Return the list of products requiring licensing.
377 *
378 * @since 1.0.0
379 *
380 * @return array[]
381 */
382 public function get_products() {
383 return $this->products;
384 }
385
386 /**
387 * Return a specific product's details.
388 *
389 * @since 1.0.0
390 *
391 * @param string $item The item for which we are getting product details.
392 * @return string[]
393 */
394 public function get_product_license_details( $item ) {
395 return isset( $this->products[ $item ] ) ? $this->products[ $item ] : false;
396 }
397
398 /**
399 * Returns whether the given product has a valid license.
400 *
401 * @since 1.0.0
402 *
403 * @param string $item The item to check.
404 * @return boolean
405 */
406 public function has_valid_license( $item ) {
407 $license = $this->get_license_details( $item );
408
409 if ( ! $license || ! isset( $license['valid'] ) ) {
410 return false;
411 }
412
413 return $license['valid'];
414 }
415
416 /**
417 * Returns the license details for the given product.
418 *
419 * @since 1.0.0
420 *
421 * @param string $item The item to get the license for.
422 * @return mixed[]
423 */
424 public function get_license( $item ) {
425 $license = $this->get_license_details( $item );
426
427 if ( ! $license || ! is_array( $license ) ) {
428 return false;
429 }
430
431 return $license['license'];
432 }
433
434 /**
435 * Returns the active license details for the given product.
436 *
437 * @since 1.0.0
438 *
439 * @param string $item The item to get active licensing details for.
440 * @return mixed[]
441 */
442 public function get_license_details( $item ) {
443 $licenses = $this->get_licenses();
444
445 if ( ! isset( $licenses[ $item ] ) ) {
446 return false;
447 }
448
449 return $licenses[ $item ];
450 }
451
452 /**
453 * Return the list of licenses.
454 *
455 * Note: The licenses are not necessarily valid. If a user enters an invalid
456 * license, the license will be stored but it will be flagged as invalid.
457 *
458 * @since 1.0.0
459 *
460 * @return array[]
461 */
462 public function get_licenses() {
463 if ( ! isset( $this->licenses ) ) {
464 $this->licenses = charitable_get_option( 'licenses', array() );
465 }
466
467 return $this->licenses;
468 }
469
470 /**
471 * Verify a license.
472 *
473 * @since 1.0.0
474 *
475 * @param string $item The item to verify.
476 * @param string $license The license key for the item.
477 * @param boolean $force Whether to force the verification check.
478 * @return mixed[]
479 */
480 public function verify_license( $item, $license, $force = false ) {
481 $license = trim( $license );
482
483 if ( $license === $this->get_license( $item ) && ! $force ) {
484 return $this->get_license_details( $item );
485 }
486
487 $product_details = $this->get_product_license_details( $item );
488
489 /* This product was not correctly registered. */
490 if ( ! $product_details ) {
491 return;
492 }
493
494 /* Data to send in our API request */
495 $api_params = array(
496 'edd_action' => 'activate_license',
497 'license' => $license,
498 'item_name' => urlencode( $product_details['name'] ),
499 'url' => home_url(),
500 );
501
502 /* Call the custom API */
503 $response = wp_remote_post( $product_details['url'], array(
504 'timeout' => 15,
505 'sslverify' => false,
506 'body' => $api_params,
507 ) );
508
509 /* Make sure the response came back okay */
510 if ( is_wp_error( $response ) ) {
511 return;
512 }
513
514 $this->flush_update_cache();
515
516 $license_data = json_decode( wp_remote_retrieve_body( $response ) );
517
518 return array(
519 'license' => $license,
520 'expiration_date' => $license_data->expires,
521 'valid' => ( 'valid' === $license_data->license ),
522 );
523 }
524
525 /**
526 * Return the URL to deactivate a specific license.
527 *
528 * @since 1.0.0
529 *
530 * @param string $item The item to deactivate.
531 * @return string
532 */
533 public function get_license_deactivation_url( $item ) {
534 return esc_url( add_query_arg( array(
535 'charitable_action' => 'deactivate_license',
536 'product_key' => $item,
537 '_nonce' => wp_create_nonce( 'license' ),
538 ), admin_url( 'admin.php?page=charitable-settings&tab=licenses' ) ) );
539 }
540
541 /**
542 * Deactivate a license.
543 *
544 * @since 1.0.0
545 *
546 * @return void
547 */
548 public function deactivate_license() {
549 if ( ! wp_verify_nonce( $_REQUEST['_nonce'], 'license' ) ) {
550 wp_die( esc_attr__( 'Cheatin\' eh?!', 'charitable' ) );
551 }
552
553 $product_key = isset( $_REQUEST['product_key'] ) ? $_REQUEST['product_key'] : false;
554
555 /* Product key must be set */
556 if ( false === $product_key ) {
557 wp_die( esc_attr__( 'Missing product key', 'charitable' ) );
558 }
559
560 $product = $this->get_product_license_details( $product_key );
561
562 /* Make sure we have a valid product with a valid license. */
563 if ( ! $product || ! $this->has_valid_license( $product_key ) ) {
564 wp_die( esc_attr__( 'This product is not valid or does not have a valid license key.', 'charitable' ) );
565 }
566
567 $license = $this->get_license( $product_key );
568
569 /* Data to send to wpcharitable.com to deactivate the license. */
570 $api_params = array(
571 'edd_action' => 'deactivate_license',
572 'license' => $license,
573 'item_name' => urlencode( $product['name'] ),
574 'url' => home_url(),
575 );
576
577 /* Call the custom API. */
578 $response = wp_remote_post( $product['url'], array( 'timeout' => 15, 'sslverify' => false, 'body' => $api_params ) );
579
580 /* Make sure the response came back okay */
581 if ( is_wp_error( $response ) ) {
582 return;
583 }
584
585 $this->flush_update_cache();
586
587 /* Decode the license data */
588 $license_data = json_decode( wp_remote_retrieve_body( $response ) );
589
590 $settings = get_option( 'charitable_settings' );
591
592 unset( $settings['licenses'][ $product_key ] );
593
594 update_option( 'charitable_settings', $settings );
595 }
596
597 /**
598 * Flush the version update cache.
599 *
600 * @since 1.4.20
601 *
602 * @return void
603 */
604 protected function flush_update_cache() {
605 wp_cache_delete( 'plugin_versions', 'charitable' );
606 set_site_transient( 'update_plugins', null );
607 }
608
609 /**
610 * Return a key for the item, based on the item name.
611 *
612 * @since 1.0.0
613 *
614 * @param string $item_name Name of the item.
615 * @return string
616 */
617 protected function get_item_key( $item_name ) {
618 return strtolower( str_replace( ' ', '_', $item_name ) );
619 }
620
621 /**
622 * Return the latest versions of Charitable plugins.
623 *
624 * @since 1.4.0
625 *
626 * @return array
627 */
628 protected function get_versions() {
629 $versions = wp_cache_get( 'plugin_versions', 'charitable' );
630
631 if ( false === $versions ) {
632
633 $licenses = array();
634
635 foreach ( $this->get_licenses() as $license ) {
636 if ( isset( $license['license'] ) ) {
637 $licenses[] = $license['license'];
638 }
639 }
640
641 $response = wp_remote_post(
642 Charitable_Licenses::UPDATE_URL . '/edd-api/versions-v2/',
643 array(
644 'sslverify' => false,
645 'timeout' => 15,
646 'body' => array(
647 'licenses' => $licenses,
648 'url' => home_url(),
649 ),
650 )
651 );
652
653 $versions = wp_remote_retrieve_body( $response );
654
655 $versions = json_decode( $versions, true );
656
657 wp_cache_set( 'plugin_versions', $versions, 'charitable' );
658 }//end if
659
660 return $versions;
661 }
662 }
663
664 endif;
665