3 if( ! interface_exists( 'iYoast_License_Manager', false ) ) {
5 interface iYoast_License_Manager {
7 public function specific_hooks();
8 public function setup_auto_updater();
15 if( ! class_exists( 'Yoast_License_Manager', false ) ) {
18 * Class Yoast_License_Manager
20 * @todo Maybe create a license class that contains key and option
21 * @todo Not sure if Yoast_License_Manager is a good name for this class, it's more managing the product (plugin or theme)
23 abstract class Yoast_License_Manager implements iYoast_License_Manager {
26 * @const VERSION The version number of the License_Manager class
31 * @var Yoast_License The license
38 private $license_constant_name = '';
41 * @var boolean True if license is defined with a constant
43 private $license_constant_is_defined = false;
46 * @var boolean True if remote license activation just failed
48 private $remote_license_activation_failed = false;
51 * @var array Array of license related options
53 private $options = array();
56 * @var string Used to prefix ID's, option names, etc..
61 * @var bool Boolean indicating whether this plugin is network activated
63 protected $is_network_activated = false;
68 * @param Yoast_Product $product
70 public function __construct( Yoast_Product $product ) {
73 $this->product = $product;
76 $this->prefix = sanitize_title_with_dashes( $this->product->get_item_name() . '_', null, 'save' );
78 // maybe set license key from constant
79 $this->maybe_set_license_key_from_constant();
86 public function setup_hooks() {
88 // show admin notice if license is not active
89 add_action( 'admin_notices', array( $this, 'display_admin_notices' ) );
91 // catch POST requests from license form
92 add_action( 'admin_init', array( $this, 'catch_post_request') );
94 // setup item type (plugin|theme) specific hooks
95 $this->specific_hooks();
97 // setup the auto updater
98 $this->setup_auto_updater();
103 * Display license specific admin notices, namely:
105 * - License for the product isn't activated
106 * - External requests are blocked through WP_HTTP_BLOCK_EXTERNAL
108 public function display_admin_notices() {
110 if ( ! current_user_can( 'manage_options' ) ) {
114 // show notice if license is invalid
115 if( ! $this->license_is_valid() ) {
116 if( $this->get_license_key() == '' ) {
117 $message = '<b>Warning!</b> You didn\'t set your %s license key yet, which means you\'re missing out on updates and support! <a href="%s">Enter your license key</a> or <a href="%s" target="_blank">get a license here</a>.';
119 $message = '<b>Warning!</b> Your %s license is inactive which means you\'re missing out on updates and support! <a href="%s">Activate your license</a> or <a href="%s" target="_blank">get a license here</a>.';
123 <p><?php printf( __( $message, $this->product->get_text_domain() ), $this->product->get_item_name(), $this->product->get_license_page_url(), $this->product->get_tracking_url( 'activate-license-notice' ) ); ?></p>
128 // show notice if external requests are blocked through the WP_HTTP_BLOCK_EXTERNAL constant
129 if( defined( "WP_HTTP_BLOCK_EXTERNAL" ) && WP_HTTP_BLOCK_EXTERNAL === true ) {
131 // check if our API endpoint is in the allowed hosts
132 $host = parse_url( $this->product->get_api_url(), PHP_URL_HOST );
134 if( ! defined( "WP_ACCESSIBLE_HOSTS" ) || stristr( WP_ACCESSIBLE_HOSTS, $host ) === false ) {
137 <p><?php printf( __( '<b>Warning!</b> You\'re blocking external requests which means you won\'t be able to get %s updates. Please add %s to %s.', $this->product->get_text_domain() ), $this->product->get_item_name(), '<strong>' . $host . '</strong>', '<code>WP_ACCESSIBLE_HOSTS</code>'); ?></p>
146 * Set a notice to display in the admin area
148 * @param string $type error|updated
149 * @param string $message The message to display
151 protected function set_notice( $message, $success = true ) {
152 $css_class = ( $success ) ? 'updated' : 'error';
153 add_settings_error( $this->prefix . 'license', 'license-notice', $message, $css_class );
157 * Remotely activate License
158 * @return boolean True if the license is now activated, false if not
160 public function activate_license() {
162 $result = $this->call_license_api( 'activate' );
168 if( isset( $result->expires ) ) {
169 $this->set_license_expiry_date( $result->expires );
170 $expiry_date = strtotime( $result->expires );
172 $expiry_date = false;
175 // show success notice if license is valid
176 if($result->license === 'valid') {
178 $message = sprintf( __( "Your %s license has been activated. ", $this->product->get_text_domain() ), $this->product->get_item_name() );
180 // show a custom notice if users have an unlimited license
181 if( $result->license_limit == 0 ) {
182 $message .= __( "You have an unlimited license. ", $this->product->get_text_domain() );
184 $message .= sprintf( __( "You have used %d/%d activations. ", $this->product->get_text_domain() ), $result->site_count, $result->license_limit );
187 // add upgrade notice if user has less than 3 activations left
188 if( $result->license_limit > 0 && ( $result->license_limit - $result->site_count ) <= 3 ) {
189 $message .= sprintf( __( '<a href="%s">Did you know you can upgrade your license?</a>', $this->product->get_text_domain() ), $this->product->get_tracking_url( 'license-nearing-limit-notice' ) );
190 // add extend notice if license is expiring in less than 1 month
191 } elseif( $expiry_date !== false && $expiry_date < strtotime( "+1 month" ) ) {
192 $days_left = round( ( $expiry_date - strtotime( "now" ) ) / 86400 );
193 $message .= sprintf( __( '<a href="%s">Your license is expiring in %d days, would you like to extend it?</a>', $this->product->get_text_domain() ), $this->product->get_tracking_url( 'license-expiring-notice' ), $days_left );
196 $this->set_notice( $message, true );
200 if( isset($result->error) && $result->error === 'no_activations_left' ) {
201 // show notice if user is at their activation limit
202 $this->set_notice( sprintf( __('You\'ve reached your activation limit. You must <a href="%s">upgrade your license</a> to use it on this site.', $this->product->get_text_domain() ), $this->product->get_tracking_url( 'license-at-limit-notice' ) ), false );
203 } elseif( isset($result->error) && $result->error == "expired" ) {
204 // show notice if the license is expired
205 $this->set_notice( sprintf( __('Your license has expired. You must <a href="%s">extend your license</a> in order to use it again.', $this->product->get_text_domain() ), $this->product->get_tracking_url( 'license-expired-notice' ) ), false );
207 // show a general notice if it's any other error
208 $this->set_notice( __( "Failed to activate your license, your license key seems to be invalid.", $this->product->get_text_domain() ), false );
211 $this->remote_license_activation_failed = true;
214 $this->set_license_status( $result->license );
217 return ( $this->license_is_valid() );
221 * Remotely deactivate License
222 * @return boolean True if the license is now deactivated, false if not
224 public function deactivate_license () {
226 $result = $this->call_license_api( 'deactivate' );
230 // show notice if license is deactivated
231 if( $result->license === 'deactivated' ) {
232 $this->set_notice( sprintf( __( "Your %s license has been deactivated.", $this->product->get_text_domain() ), $this->product->get_item_name() ) );
234 $this->set_notice( sprintf( __( "Failed to deactivate your %s license.", $this->product->get_text_domain() ), $this->product->get_item_name() ), false );
237 $this->set_license_status( $result->license );
240 return ( $this->get_license_status() === 'deactivated' );
244 * @param string $action activate|deactivate
247 protected function call_license_api( $action ) {
249 // don't make a request if license key is empty
250 if( $this->get_license_key() === '' ) {
254 // data to send in our API request
256 'edd_action' => $action . '_license',
257 'license' => $this->get_license_key(),
258 'item_name' => urlencode( trim( $this->product->get_item_name() ) ),
259 'url' => get_option( 'home' ) // grab the URL straight from the option to prevent filters from breaking it.
262 // create api request url
263 $url = add_query_arg( $api_params, $this->product->get_api_url() );
265 require_once dirname( __FILE__ ) . '/class-api-request.php';
266 $request = new Yoast_API_Request( $url );
268 if( $request->is_valid() !== true ) {
269 $this->set_notice( sprintf( __( "Request error: \"%s\" (%scommon license notices%s)", $this->product->get_text_domain() ), $request->get_error_message(), '<a href="http://kb.yoast.com/article/13-license-activation-notices">', '</a>' ), false );
273 $response = $request->get_response();
275 // update license status
276 $license_data = $response;
278 return $license_data;
284 * Set the license status
286 * @param string $license_status
288 public function set_license_status( $license_status ) {
289 $this->set_option( 'status', $license_status );
293 * Get the license status
295 * @return string $license_status;
297 public function get_license_status() {
298 $license_status = $this->get_option( 'status' );
299 return trim( $license_status );
303 * Set the license key
305 * @param string $license_key
307 public function set_license_key( $license_key ) {
308 $this->set_option( 'key', $license_key );
312 * Gets the license key from constant or option
314 * @return string $license_key
316 public function get_license_key() {
317 $license_key = $this->get_option( 'key' );
318 return trim( $license_key );
322 * Gets the license expiry date
326 public function get_license_expiry_date() {
327 return $this->get_option( 'expiry_date');
331 * Stores the license expiry date
333 public function set_license_expiry_date( $expiry_date ) {
334 $this->set_option( 'expiry_date', $expiry_date );
338 * Checks whether the license status is active
340 * @return boolean True if license is active
342 public function license_is_valid() {
343 return ( $this->get_license_status() === 'valid' );
347 * Get all license related options
349 * @return array Array of license options
351 protected function get_options() {
353 // create option name
354 $option_name = $this->prefix . 'license';
356 // get array of options from db
357 if( $this->is_network_activated ) {
358 $options = get_site_option( $option_name, array( ) );
360 $options = get_option( $option_name, array( ) );
363 // setup array of defaults
370 // merge options with defaults
371 $this->options = wp_parse_args( $options, $defaults );
373 return $this->options;
377 * Set license related options
379 * @param array $options Array of new license options
381 protected function set_options( array $options ) {
382 // create option name
383 $option_name = $this->prefix . 'license';
386 if( $this->is_network_activated ) {
387 update_site_option( $option_name, $options );
389 update_option( $option_name, $options );
395 * Gets a license related option
397 * @param string $name The option name
398 * @return mixed The option value
400 protected function get_option( $name ) {
401 $options = $this->get_options();
402 return $options[ $name ];
406 * Set a license related option
408 * @param string $name The option name
409 * @param mixed $value The option value
411 protected function set_option( $name, $value ) {
413 $options = $this->get_options();
416 $options[ $name ] = $value;
419 $this->set_options( $options );
422 public function show_license_form_heading() {
425 <?php printf( __( "%s: License Settings", $this->product->get_text_domain() ), $this->product->get_item_name() ); ?>
431 * Show a form where users can enter their license key
433 * @param boolean $embedded Boolean indicating whether this form is embedded in another form?
435 public function show_license_form( $embedded = true ) {
437 $key_name = $this->prefix . 'license_key';
438 $nonce_name = $this->prefix . 'license_nonce';
439 $action_name = $this->prefix . 'license_action';
442 $visible_license_key = $this->get_license_key();
444 // obfuscate license key
445 $obfuscate = ( strlen( $this->get_license_key() ) > 5 && ( $this->license_is_valid() || ! $this->remote_license_activation_failed ) );
448 $visible_license_key = str_repeat('*', strlen( $this->get_license_key() ) - 4) . substr( $this->get_license_key(), -4 );
451 // make license key readonly when license key is valid or license is defined with a constant
452 $readonly = ( $this->license_is_valid() || $this->license_constant_is_defined );
454 require dirname( __FILE__ ) . '/views/form.php';
456 // enqueue script in the footer
457 add_action( 'admin_footer', array( $this, 'output_script'), 99 );
461 * Check if the license form has been submitted
463 public function catch_post_request() {
465 $name = $this->prefix . 'license_key';
467 // check if license key was posted and not empty
468 if( ! isset( $_POST[$name] ) ) {
472 // run a quick security check
473 $nonce_name = $this->prefix . 'license_nonce';
475 if ( ! check_admin_referer( $nonce_name, $nonce_name ) ) {
479 // @TODO: check for user cap?
481 // get key from posted value
482 $license_key = $_POST[$name];
484 // check if license key doesn't accidentally contain asterisks
485 if( strstr($license_key, '*') === false ) {
488 $license_key = trim( sanitize_key( $_POST[$name] ) );
491 $this->set_license_key( $license_key );
494 // does user have an activated valid license
495 if( ! $this->license_is_valid() ) {
497 // try to auto-activate license
498 return $this->activate_license();
502 $action_name = $this->prefix . 'license_action';
504 // was one of the action buttons clicked?
505 if( isset( $_POST[ $action_name ] ) ) {
507 $action = trim( $_POST[ $action_name ] );
512 return $this->activate_license();
516 return $this->deactivate_license();
525 * Output the script containing the YoastLicenseManager JS Object
527 * This takes care of disabling the 'activate' and 'deactivate' buttons
529 public function output_script() {
530 require_once dirname( __FILE__ ) . '/views/script.php';
534 * Set the constant used to define the license
536 * @param string $license_constant_name The license constant name
538 public function set_license_constant_name( $license_constant_name ) {
539 $this->license_constant_name = trim( $license_constant_name );
540 $this->maybe_set_license_key_from_constant();
544 * Maybe set license key from a defined constant
546 private function maybe_set_license_key_from_constant( ) {
548 if( empty( $this->license_constant_name ) ) {
549 // generate license constant name
550 $this->set_license_constant_name( strtoupper( str_replace( array(' ', '-' ), '', sanitize_key( $this->product->get_item_name() ) ) ) . '_LICENSE');
553 // set license key from constant
554 if( defined( $this->license_constant_name ) ) {
556 $license_constant_value = constant( $this->license_constant_name );
558 // update license key value with value of constant
559 if( $this->get_license_key() !== $license_constant_value ) {
560 $this->set_license_key( $license_constant_value );
563 $this->license_constant_is_defined = true;