This commit is contained in:
KhaiNguyen
2020-02-13 10:39:37 +07:00
commit 59401cb805
12867 changed files with 4646216 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
# Change Log
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.6.0] - 2017-08-22
### Added
- Added a way to overwrite the result of the license manager in the implementing plugin. This is done by applying `yoast-license-valid` and `yoast-show-license-notice` filters.
### Fixed
- Fixed activation of licenses on multisites and on wpml installations.
## [1.5.0] - 2017-02-28
### Added
- Add a `custom_message` option to the response which will be shown as a custom message after the default message.
- Add a `custom_message_[locale]` option to the response which will be shown as a custom message only if the user locale is set to the locale in the message key.
## [1.4.0] - 2016-11-11
### Added
- Add a `get_extension_url` method to `Yoast_Product` to retrieve the URL where people can extend/upgrade their license.
- Add a `set_extension_url` method to `Yoast_Product` to set the URL where people can extend/upgrade their license.
### Changed
- Removed development files from zip that GitHub generates by settings export-ignore for certain files in `.gitattributes`, props [Danny van Kooten](https://github.com/dannyvankooten).
### Fixed
- Add missing gettext functions to several strings, props [Pedro Mendonça](https://github.com/pedro-mendonca)
- Improve text string for the new version notification, props [Xavi Ivars](https://github.com/xavivars)
- Fix alignment of license fields by setting WordPress classes that have certain default styles that align form elements correctly.
## [1.3.0] - 2016-06-14
### Fixed
- Fix issue where the transient would be overwritten for different products with different slugs.

View File

@@ -0,0 +1,138 @@
<?php
if ( ! class_exists( "Yoast_API_Request", false ) ) {
/**
* Handles requests to the Yoast EDD API
*/
class Yoast_API_Request {
/**
* @var string Request URL
*/
private $url = '';
/**
* @var array Request parameters
*/
private $args = array(
'method' => 'GET',
'timeout' => 10,
'sslverify' => false,
'headers' => array(
'Accept-Encoding' => '*',
'X-Yoast-EDD' => '1'
)
);
/**
* @var boolean
*/
private $success = false;
/**
* @var mixed
*/
private $response;
/**
* @var string
*/
private $error_message = '';
/**
* Constructor
*
* @param string url
* @param array $args
*/
public function __construct( $url, array $args = array() ) {
// set api url
$this->url = $url;
// set request args (merge with defaults)
$this->args = wp_parse_args( $args, $this->args );
// fire the request
$this->success = $this->fire();
}
/**
* Fires the request, automatically called from constructor
*
* @return boolean
*/
private function fire() {
// fire request to shop
$response = wp_remote_request( $this->url, $this->args );
// validate raw response
if( $this->validate_raw_response( $response ) === false ) {
return false;
}
// decode the response
$this->response = json_decode( wp_remote_retrieve_body( $response ) );
// response should be an object
if( ! is_object( $this->response ) ) {
$this->error_message = 'No JSON object was returned.';
return false;
}
return true;
}
/**
* @param object $response
* @return boolean
*/
private function validate_raw_response( $response ) {
// make sure response came back okay
if( is_wp_error( $response ) ) {
$this->error_message = $response->get_error_message();
return false;
}
// check response code, should be 200
$response_code = wp_remote_retrieve_response_code( $response );
if( false === strstr( $response_code, '200' ) ) {
$response_message = wp_remote_retrieve_response_message( $response );
$this->error_message = "{$response_code} {$response_message}";
return false;
}
return true;
}
/**
* Was a valid response returned?
*
* @return boolean
*/
public function is_valid() {
return ( $this->success === true );
}
/**
* @return string
*/
public function get_error_message() {
return $this->error_message;
}
/**
* @return object
*/
public function get_response() {
return $this->response;
}
}
}

View File

@@ -0,0 +1,781 @@
<?php
if ( ! interface_exists( 'iYoast_License_Manager', false ) ) {
interface iYoast_License_Manager {
public function specific_hooks();
public function setup_auto_updater();
}
}
if ( ! class_exists( 'Yoast_License_Manager', false ) ) {
/**
* Class Yoast_License_Manager
*/
abstract class Yoast_License_Manager implements iYoast_License_Manager {
/**
* @const VERSION The version number of the License_Manager class
*/
const VERSION = 1;
/**
* @var Yoast_License The license
*/
protected $product;
/**
* @var string
*/
private $license_constant_name = '';
/**
* @var boolean True if license is defined with a constant
*/
private $license_constant_is_defined = false;
/**
* @var boolean True if remote license activation just failed
*/
private $remote_license_activation_failed = false;
/**
* @var array Array of license related options
*/
private $options = array();
/**
* @var string Used to prefix ID's, option names, etc..
*/
protected $prefix;
/**
* @var bool Boolean indicating whether this plugin is network activated
*/
protected $is_network_activated = false;
/**
* Constructor
*
* @param Yoast_Product $product
*/
public function __construct( Yoast_Product $product ) {
// Set the license
$this->product = $product;
// set prefix
$this->prefix = sanitize_title_with_dashes( $this->product->get_item_name() . '_', null, 'save' );
// maybe set license key from constant
$this->maybe_set_license_key_from_constant();
}
/**
* Setup hooks
*
*/
public function setup_hooks() {
// show admin notice if license is not active
add_action( 'admin_notices', array( $this, 'display_admin_notices' ) );
// catch POST requests from license form
add_action( 'admin_init', array( $this, 'catch_post_request' ) );
// Adds the plugin to the active extensions.
add_filter( 'yoast-active-extensions', array( $this, 'set_active_extension' ) );
// setup item type (plugin|theme) specific hooks
$this->specific_hooks();
// setup the auto updater
$this->setup_auto_updater();
}
/**
* Checks if the license is valid and put it into the list with extensions.
*
* @param array $extensions The extensions used in Yoast SEO.
*
* @return array
*/
public function set_active_extension( $extensions ) {
if ( ! $this->license_is_valid() ) {
$this->set_license_key( 'yoast-dummy-license' );
$this->activate_license();
}
if ( $this->license_is_valid() ) {
$extensions[] = $this->product->get_slug();
}
return $extensions;
}
/**
* Display license specific admin notices, namely:
*
* - License for the product isn't activated
* - External requests are blocked through WP_HTTP_BLOCK_EXTERNAL
*/
public function display_admin_notices() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
// show notice if license is invalid
if ( $this->show_license_notice() && ! $this->license_is_valid() ) {
if ( $this->get_license_key() == '' ) {
$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>.' );
} else {
$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>.' );
}
?>
<div class="notice notice-error yoast-notice-error">
<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>
</div>
<?php
}
// show notice if external requests are blocked through the WP_HTTP_BLOCK_EXTERNAL constant
if ( defined( "WP_HTTP_BLOCK_EXTERNAL" ) && WP_HTTP_BLOCK_EXTERNAL === true ) {
// check if our API endpoint is in the allowed hosts
$host = parse_url( $this->product->get_api_url(), PHP_URL_HOST );
if ( ! defined( "WP_ACCESSIBLE_HOSTS" ) || stristr( WP_ACCESSIBLE_HOSTS, $host ) === false ) {
?>
<div class="notice notice-error yoast-notice-error">
<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>
</div>
<?php
}
}
}
/**
* Set a notice to display in the admin area
*
* @param string $type error|updated
* @param string $message The message to display
*/
protected function set_notice( $message, $success = true ) {
$css_class = ( $success ) ? 'notice-success yoast-notice-success' : 'notice-error yoast-notice-error';
add_settings_error( $this->prefix . 'license', 'license-notice', $message, $css_class );
}
/**
* Remotely activate License
* @return boolean True if the license is now activated, false if not
*/
public function activate_license() {
$result = $this->call_license_api( 'activate' );
if ( $result ) {
// show success notice if license is valid
if ( $result->license === 'valid' ) {
$success = true;
$message = $this->get_successful_activation_message( $result );
} else {
$this->remote_license_activation_failed = true;
$success = false;
$message = $this->get_unsuccessful_activation_message( $result );
}
// Append custom HTML message to default message.
$message .= $this->get_custom_message( $result );
if ( $this->show_license_notice() ) {
$this->set_notice( $message, $success );
}
$this->set_license_status( $result->license );
}
return $this->license_is_valid();
}
/**
* Remotely deactivate License
* @return boolean True if the license is now deactivated, false if not
*/
public function deactivate_license() {
$result = $this->call_license_api( 'deactivate' );
if ( $result ) {
// show notice if license is deactivated
if ( $result->license === 'deactivated' ) {
$success = true;
$message = sprintf( __( "Your %s license has been deactivated.", $this->product->get_text_domain() ), $this->product->get_item_name() );
} else {
$success = false;
$message = sprintf( __( "Failed to deactivate your %s license.", $this->product->get_text_domain() ), $this->product->get_item_name() );
}
$message .= $this->get_custom_message( $result );
// Append custom HTML message to default message.
if ( $this->show_license_notice() ) {
$this->set_notice( $message, $success );
}
$this->set_license_status( $result->license );
}
return ( $this->get_license_status() === 'deactivated' );
}
/**
* Returns the home url with the following modifications:
*
* In case of a multisite setup we return the network_home_url.
* In case of no multisite setup we return the home_url while overriding the WPML filter.
*/
public function get_url() {
// Add a new filter to undo WPML's changing of home url.
add_filter( 'wpml_get_home_url', array( $this, 'wpml_get_home_url' ), 10, 2 );
// If the plugin is network activated, use the network home URL.
if ( $this->is_network_activated ) {
$url = network_home_url();
}
// Otherwise use the home URL for this specific site.
if ( ! $this->is_network_activated ) {
$url = home_url();
}
remove_filter( 'wpml_get_home_url', array( $this, 'wpml_get_home_url' ), 10 );
return $url;
}
/**
* Returns the original URL instead of the language-enriched URL.
* This method gets automatically triggered by the wpml_get_home_url filter.
*
* @param string $home_url The url altered by WPML. Unused.
* @param string $url The url that isn't altered by WPML.
*
* @return string The original url.
*/
public function wpml_get_home_url( $home_url, $url ) {
return $url;
}
/**
* @param string $action activate|deactivate
*
* @return mixed
*/
protected function call_license_api( $action ) {
// don't make a request if license key is empty
if ( $this->get_license_key() === '' ) {
return false;
}
// data to send in our API request
$api_params = array(
'edd_action' => $action . '_license',
'license' => $this->get_license_key(),
'item_name' => urlencode( trim( $this->product->get_item_name() ) ),
'url' => $this->get_url()
// grab the URL straight from the option to prevent filters from breaking it.
);
// create api request url
$url = add_query_arg( $api_params, $this->product->get_api_url() );
require_once dirname( __FILE__ ) . '/class-api-request.php';
$request = new Yoast_API_Request( $url );
if ( $request->is_valid() !== true ) {
$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 );
}
// get response
return $request->get_response();
}
/**
* Set the license status
*
* @param string $license_status
*/
public function set_license_status( $license_status ) {
$this->set_option( 'status', $license_status );
}
/**
* Get the license status
*
* @return string $license_status;
*/
public function get_license_status() {
$license_status = $this->get_option( 'status' );
return trim( $license_status );
}
/**
* Set the license key
*
* @param string $license_key
*/
public function set_license_key( $license_key ) {
$this->set_option( 'key', $license_key );
}
/**
* Gets the license key from constant or option
*
* @return string $license_key
*/
public function get_license_key() {
$license_key = $this->get_option( 'key' );
return trim( $license_key );
}
/**
* Gets the license expiry date
*
* @return string
*/
public function get_license_expiry_date() {
return $this->get_option( 'expiry_date' );
}
/**
* Stores the license expiry date
*/
public function set_license_expiry_date( $expiry_date ) {
$this->set_option( 'expiry_date', $expiry_date );
}
/**
* Checks whether the license status is active
*
* @return boolean True if license is active
*/
public function license_is_valid() {
return ( $this->get_license_status() === 'valid' );
}
/**
* Get all license related options
*
* @return array Array of license options
*/
protected function get_options() {
// create option name
$option_name = $this->prefix . 'license';
// get array of options from db
if ( $this->is_network_activated ) {
$options = get_site_option( $option_name, array() );
} else {
$options = get_option( $option_name, array() );
}
// setup array of defaults
$defaults = array(
'key' => '',
'status' => '',
'expiry_date' => ''
);
// merge options with defaults
$this->options = wp_parse_args( $options, $defaults );
return $this->options;
}
/**
* Set license related options
*
* @param array $options Array of new license options
*/
protected function set_options( array $options ) {
// create option name
$option_name = $this->prefix . 'license';
// update db
if ( $this->is_network_activated ) {
update_site_option( $option_name, $options );
} else {
update_option( $option_name, $options );
}
}
/**
* Gets a license related option
*
* @param string $name The option name
*
* @return mixed The option value
*/
protected function get_option( $name ) {
$options = $this->get_options();
return $options[ $name ];
}
/**
* Set a license related option
*
* @param string $name The option name
* @param mixed $value The option value
*/
protected function set_option( $name, $value ) {
// get options
$options = $this->get_options();
// update option
$options[ $name ] = $value;
// save options
$this->set_options( $options );
}
public function show_license_form_heading() {
?>
<h3>
<?php printf( __( "%s: License Settings", $this->product->get_text_domain() ), $this->product->get_item_name() ); ?>
&nbsp; &nbsp;
</h3>
<?php
}
/**
* Show a form where users can enter their license key
*
* @param boolean $embedded Boolean indicating whether this form is embedded in another form?
*/
public function show_license_form( $embedded = true ) {
$key_name = $this->prefix . 'license_key';
$nonce_name = $this->prefix . 'license_nonce';
$action_name = $this->prefix . 'license_action';
$api_host_available = $this->get_api_availability();
$visible_license_key = $this->get_license_key();
// obfuscate license key
$obfuscate = ( strlen( $this->get_license_key() ) > 5 && ( $this->license_is_valid() || ! $this->remote_license_activation_failed ) );
if ( $obfuscate ) {
$visible_license_key = str_repeat( '*', strlen( $this->get_license_key() ) - 4 ) . substr( $this->get_license_key(), - 4 );
}
// make license key readonly when license key is valid or license is defined with a constant
$readonly = ( $this->license_is_valid() || $this->license_constant_is_defined );
require dirname( __FILE__ ) . '/views/form.php';
// enqueue script in the footer
add_action( 'admin_footer', array( $this, 'output_script' ), 99 );
}
/**
* Check if the license form has been submitted
*/
public function catch_post_request() {
$name = $this->prefix . 'license_key';
// check if license key was posted and not empty
if ( ! isset( $_POST[ $name ] ) ) {
return;
}
// run a quick security check
$nonce_name = $this->prefix . 'license_nonce';
if ( ! check_admin_referer( $nonce_name, $nonce_name ) ) {
return;
}
// @TODO: check for user cap?
// get key from posted value
$license_key = $_POST[ $name ];
// check if license key doesn't accidentally contain asterisks
if ( strstr( $license_key, '*' ) === false ) {
// sanitize key
$license_key = trim( sanitize_key( $_POST[ $name ] ) );
// save license key
$this->set_license_key( $license_key );
}
// does user have an activated valid license
if ( ! $this->license_is_valid() ) {
// try to auto-activate license
return $this->activate_license();
}
$action_name = $this->prefix . 'license_action';
// was one of the action buttons clicked?
if ( isset( $_POST[ $action_name ] ) ) {
$action = trim( $_POST[ $action_name ] );
switch ( $action ) {
case 'activate':
return $this->activate_license();
case 'deactivate':
return $this->deactivate_license();
}
}
}
/**
* Output the script containing the YoastLicenseManager JS Object
*
* This takes care of disabling the 'activate' and 'deactivate' buttons
*/
public function output_script() {
require_once dirname( __FILE__ ) . '/views/script.php';
}
/**
* Set the constant used to define the license
*
* @param string $license_constant_name The license constant name
*/
public function set_license_constant_name( $license_constant_name ) {
$this->license_constant_name = trim( $license_constant_name );
$this->maybe_set_license_key_from_constant();
}
/**
* Get the API availability information
*
* @return array
*/
protected function get_api_availability() {
return array(
'url' => $this->product->get_api_url(),
'availability' => $this->check_api_host_availability(),
'curl_version' => $this->get_curl_version(),
);
}
/**
* Check if the API host address is available from this server
*
* @return bool
*/
private function check_api_host_availability() {
$wp_http = new WP_Http();
if ( $wp_http->block_request( $this->product->get_api_url() ) === false ) {
return true;
}
return false;
}
/**
* Get the current curl version, or false
*
* @return mixed
*/
protected function get_curl_version() {
if ( function_exists( 'curl_version' ) ) {
$curl_version = curl_version();
if ( isset( $curl_version['version'] ) ) {
return $curl_version['version'];
}
}
return false;
}
/**
* Maybe set license key from a defined constant
*/
private function maybe_set_license_key_from_constant() {
if ( empty( $this->license_constant_name ) ) {
// generate license constant name
$this->set_license_constant_name( strtoupper( str_replace( array(
' ',
'-'
), '', sanitize_key( $this->product->get_item_name() ) ) ) . '_LICENSE' );
}
// set license key from constant
if ( defined( $this->license_constant_name ) ) {
$license_constant_value = constant( $this->license_constant_name );
// update license key value with value of constant
if ( $this->get_license_key() !== $license_constant_value ) {
$this->set_license_key( $license_constant_value );
}
$this->license_constant_is_defined = true;
}
}
/**
* Determine what message should be shown for a successful license activation
*
* @param Object $result Result of a request.
*
* @return string
*/
protected function get_successful_activation_message( $result ) {
// Get expiry date.
if ( isset( $result->expires ) ) {
$this->set_license_expiry_date( $result->expires );
$expiry_date = strtotime( $result->expires );
} else {
$expiry_date = false;
}
// Always show that it was successful.
$message = sprintf( __( "Your %s license has been activated. ", $this->product->get_text_domain() ), $this->product->get_item_name() );
// Show a custom notice it is an unlimited license.
if ( $result->license_limit == 0 ) {
$message .= __( "You have an unlimited license. ", $this->product->get_text_domain() );
} else {
$message .= sprintf( _n( "You have used %d/%d activation. ", "You have used %d/%d activations. ", $result->license_limit, $this->product->get_text_domain() ), $result->site_count, $result->license_limit );
}
// add upgrade notice if user has less than 3 activations left
if ( $result->license_limit > 0 && ( $result->license_limit - $result->site_count ) <= 3 ) {
$message .= sprintf( __( '<a href="%s">Did you know you can upgrade your license?</a> ', $this->product->get_text_domain() ), $this->product->get_extension_url( 'license-nearing-limit-notice' ) );
}
if ( $expiry_date !== false && $expiry_date < strtotime( "+1 month" ) ) {
// Add extend notice if license is expiring in less than 1 month.
$days_left = round( ( $expiry_date - time() ) / 86400 );
$message .= sprintf( _n( '<a href="%s">Your license is expiring in %d day, would you like to extend it?</a> ', '<a href="%s">Your license is expiring in %d days, would you like to extend it?</a> ', $days_left, $this->product->get_text_domain() ), $this->product->get_extension_url( 'license-expiring-notice' ), $days_left );
}
return $message;
}
/**
* Determine what message should be shown for an unsuccessful activation
*
* @param Object $result Result of a request.
*
* @return string
*/
protected function get_unsuccessful_activation_message( $result ) {
// Default message if we cannot detect anything more specific.
$message = __( 'Failed to activate your license, your license key seems to be invalid.', $this->product->get_text_domain() );
if ( ! empty( $result->error ) ) {
switch ( $result->error ) {
// Show notice if user is at their activation limit.
case 'no_activations_left':
$message = 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_extension_url( 'license-at-limit-notice' ) );
break;
// Show notice if the license is expired.
case 'expired':
$message = 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_extension_url( 'license-expired-notice' ) );
break;
}
}
return $message;
}
/**
* Get the locale for the current user
*
* @return string
*/
protected function get_user_locale() {
if ( function_exists( 'get_user_locale' ) ) {
return get_user_locale();
}
return get_locale();
}
/**
* Parse custom HTML message from response
*
* @param Object $result Result of the request.
*
* @return string
*/
protected function get_custom_message( $result ) {
$message = '';
// Allow for translated messages to be used.
$localizedDescription = 'custom_message_' . $this->get_user_locale();
if ( ! empty( $result->{$localizedDescription} ) ) {
$message = $result->{$localizedDescription};
}
// Fall back to non-localized custom message if no locale has been provided.
if ( empty( $message ) && ! empty( $result->custom_message ) ) {
$message = $result->custom_message;
}
// Make sure we limit the type of HTML elements to be displayed.
if ( ! empty( $message ) ) {
$message = wp_kses( $message, array(
'a' => array(
'href' => array(),
'target' => array(),
'title' => array()
),
'br' => array(),
) );
// Make sure we are on a new line.
$message = '<br />' . $message;
}
return $message;
}
/**
* Returns true when a license notice should be shown.
*
* @return bool
*/
protected function show_license_notice() {
/**
* Filter: 'yoast-show-license-notice' - Show the license notice.
*
* @api bool $show True if notices should be shown.
*/
return ( bool ) apply_filters( 'yoast-show-license-notice', true );
}
}
}

View File

@@ -0,0 +1,95 @@
<?php
if ( class_exists( 'Yoast_License_Manager' ) && ! class_exists( "Yoast_Plugin_License_Manager", false ) ) {
class Yoast_Plugin_License_Manager extends Yoast_License_Manager {
/**
* Constructor
*
* @param Yoast_Product $product
*/
public function __construct( Yoast_Product $product ) {
parent::__construct( $product );
// Check if plugin is network activated. We should use site(wide) options in that case.
if( is_admin() && is_multisite() ) {
if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
}
$this->is_network_activated = is_plugin_active_for_network( $product->get_file() );
}
}
/**
* Setup auto updater for plugins
*/
public function setup_auto_updater() {
/**
* Filter: 'yoast-license-valid' - Perform action when license is valid or hook returns true.
*
* @api bool $is_valid True if the license is valid.
*/
if ( apply_filters( 'yoast-license-valid', $this->license_is_valid() ) ) {
// setup auto updater
require_once( dirname( __FILE__ ) . '/class-update-manager.php' );
require_once( dirname( __FILE__ ) . '/class-plugin-update-manager.php' );
new Yoast_Plugin_Update_Manager( $this->product, $this );
}
}
/**
* Setup hooks
*/
public function specific_hooks() {
// deactivate the license remotely on plugin deactivation
register_deactivation_hook( $this->product->get_file(), array( $this, 'deactivate_license' ) );
}
/**
* Show a form where users can enter their license key
* Takes Multisites into account
*
* @param bool $embedded
* @return null
*/
public function show_license_form( $embedded = true ) {
// For non-multisites, always show the license form
if( ! is_multisite() ) {
parent::show_license_form( $embedded );
return;
}
// Plugin is network activated
if( $this->is_network_activated ) {
// We're on the network admin
if( is_network_admin() ) {
parent::show_license_form( $embedded );
} else {
// We're not in the network admin area, show a notice
parent::show_license_form_heading();
if ( is_super_admin() ) {
echo "<p>" . sprintf( __( '%s is network activated, you can manage your license in the <a href="%s">network admin license page</a>.', $this->product->get_text_domain() ), $this->product->get_item_name(), $this->product->get_license_page_url() ) . "</p>";
} else {
echo "<p>" . sprintf( __( '%s is network activated, please contact your site administrator to manage the license.', $this->product->get_text_domain() ), $this->product->get_item_name() ) . "</p>";
}
}
} else {
if( false == is_network_admin() ) {
parent::show_license_form( $embedded );
}
}
}
}
}

View File

@@ -0,0 +1,93 @@
<?php
if( class_exists( 'Yoast_Update_Manager' ) && ! class_exists( "Yoast_Plugin_Update_Manager", false ) ) {
class Yoast_Plugin_Update_Manager extends Yoast_Update_Manager {
/**
* Constructor
*
* @param Yoast_Product $product The Product.
* @param string $license_key The License entered.
*/
public function __construct( Yoast_Product $product, $license_key ) {
parent::__construct( $product, $license_key );
// setup hooks
$this->setup_hooks();
}
/**
* Setup hooks
*/
private function setup_hooks() {
// check for updates
add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'set_updates_available_data' ) );
// get correct plugin information (when viewing details)
add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
}
/**
* Check for updates and if so, add to "updates available" data
*
* @param object $data
* @return object $data
*/
public function set_updates_available_data( $data ) {
if ( empty( $data ) ) {
return $data;
}
// send of API request to check for updates
$remote_data = $this->get_remote_data();
// did we get a response?
if( $remote_data === false ) {
return $data;
}
// compare local version with remote version
if ( version_compare( $this->product->get_version(), $remote_data->new_version, '<' ) ) {
// remote version is newer, add to data
$data->response[ $this->product->get_file() ] = $remote_data;
}
return $data;
}
/**
* Gets new plugin version details (view version x.x.x details)
*
* @uses api_request()
*
* @param object $data
* @param string $action
* @param object $args (optional)
*
* @return object $data
*/
public function plugins_api_filter( $data, $action = '', $args = null ) {
// only do something if we're checking for our plugin
if ( $action !== 'plugin_information' || ! isset( $args->slug ) || $args->slug !== $this->product->get_slug() ) {
return $data;
}
$api_response = $this->get_remote_data();
// did we get a response?
if ( $api_response === false ) {
return $data;
}
// return api response
return $api_response;
}
}
}

View File

@@ -0,0 +1,323 @@
<?php
if ( ! class_exists( "Yoast_Product", false ) ) {
/**
* Class Yoast_Product
*
* @todo create a license class and store an object of it in this class
*/
class Yoast_Product {
/**
* @var string The URL of the shop running the EDD API.
*/
protected $api_url;
/**
* @var string The item name in the EDD shop.
*/
protected $item_name;
/**
* @var string The theme slug or plugin file
*/
protected $slug;
/**
* @var string The version number of the item
*/
protected $version;
/**
* @var string The absolute url on which users can purchase a license
*/
protected $item_url;
/**
* @var string Absolute admin URL on which users can enter their license key.
*/
protected $license_page_url;
/**
* @var string The text domain used for translating strings
*/
protected $text_domain;
/**
* @var string The item author
*/
protected $author;
/**
* @var string Relative file path to the plugin.
*/
protected $file;
/** @var int Product ID in backend system for quick lookup */
protected $product_id;
/** @var string URL referring to the extension page */
protected $extension_url;
/**
* Yoast_Product constructor.
*
* @param string $api_url The URL of the shop running the EDD API.
* @param string $item_name The item name in the EDD shop.
* @param string $slug The slug of the plugin, for shiny updates this needs to be a valid HTML id.
* @param string $version The version number of the item.
* @param string $item_url The absolute url on which users can purchase a license.
* @param string $license_page_url Absolute admin URL on which users can enter their license key.
* @param string $text_domain The text domain used for translating strings.
* @param string $author The item author.
* @param string $file The relative file path to the plugin.
* @param int $product_id The ID of the product in the backend system.
*/
public function __construct( $api_url, $item_name, $slug, $version, $item_url = '', $license_page_url = '#', $text_domain = 'yoast', $author = 'Yoast', $file = '', $product_id = 0 ) {
$this->set_api_url( $api_url );
$this->set_item_name( $item_name );
$this->set_slug( $slug );
$this->set_version( $version );
$this->set_item_url( $item_url );
$this->set_text_domain( $text_domain );
$this->set_author( $author );
$this->set_file( $file );
$this->set_product_id( $product_id );
$this->set_license_page_url( $license_page_url );
}
/**
* @param string $api_url
*/
public function set_api_url( $api_url ) {
$this->api_url = $api_url;
}
/**
* @return string
*/
public function get_api_url() {
return $this->api_url;
}
/**
* @param string $author
*/
public function set_author( $author ) {
$this->author = $author;
}
/**
* @return string
*/
public function get_author() {
return $this->author;
}
/**
* @param string $item_name
*/
public function set_item_name( $item_name ) {
$this->item_name = $item_name;
}
/**
* @return string
*/
public function get_item_name() {
return $this->item_name;
}
/**
* @param string $item_url
*/
public function set_item_url( $item_url ) {
if ( empty( $item_url ) ) {
$item_url = $this->api_url;
}
$this->item_url = $item_url;
}
/**
* @return string
*/
public function get_item_url() {
return $this->item_url;
}
/**
* @param string $license_page_url
*/
public function set_license_page_url( $license_page_url ) {
if ( is_admin() && is_multisite() ) {
if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
}
if ( is_plugin_active_for_network( $this->get_file() ) ) {
$this->license_page_url = network_admin_url( $license_page_url );
return;
}
}
$this->license_page_url = admin_url( $license_page_url );
}
/**
* @return string
*/
public function get_license_page_url() {
return $this->license_page_url;
}
/**
* @param string $slug
*/
public function set_slug( $slug ) {
$this->slug = $slug;
}
/**
* @return string
*/
public function get_slug() {
return $this->slug;
}
/**
* Returns the dirname of the slug and limits it to 15 chars
*
* @return string
*/
public function get_transient_prefix() {
return substr( md5( $this->file ), 0, 15 );
}
/**
* @param string $text_domain
*/
public function set_text_domain( $text_domain ) {
$this->text_domain = $text_domain;
}
/**
* @return string
*/
public function get_text_domain() {
return $this->text_domain;
}
/**
* @param string $version
*/
public function set_version( $version ) {
$this->version = $version;
}
/**
* @return string
*/
public function get_version() {
return $this->version;
}
/**
* Returns the file path relative to the plugins folder
*
* @return string
*/
public function get_file() {
/*
* Fall back to the slug for BC reasons.
*
* We used to pass the file to the slug field, but this isn't supported with the shiny updates in WordPress.
* WordPress uses the slug in the HTML as an ID and a slash isn't a valid
*/
return empty( $this->file ) ? $this->slug : $this->file;
}
/**
* Sets the file path relative to the plugins folder
*
* @param string $file Relative file path to the plugin.
*/
public function set_file( $file ) {
$this->file = $file;
}
/**
* Return the Product ID
*
* @return int
*/
public function get_product_id() {
return $this->product_id;
}
/**
* Set the product ID
*
* @param int $product_id Product ID to set.
*/
public function set_product_id( $product_id ) {
$this->product_id = (int) $product_id;
}
/**
* Gets a Google Analytics Campaign url for this product
*
* @param string $link_identifier
*
* @return string The full URL
*/
public function get_tracking_url( $link_identifier = '' ) {
return $this->add_campaign_attributes( $this->get_item_url(), $link_identifier );
}
/**
* Returns the extension url if set, otherwise it will be the tracking url.
*
* @param string $link_identifier
*
* @return string
*/
public function get_extension_url( $link_identifier = '' ) {
if ( $this->extension_url ) {
return $this->add_campaign_attributes( $this->extension_url, $link_identifier );
}
return $this->get_tracking_url( $link_identifier );
}
/**
* Sets the extension url.
*
* @param string $extension_url
*/
public function set_extension_url( $extension_url ) {
$this->extension_url = $extension_url;
}
private function add_campaign_attributes( $url, $link_identifier ) {
$tracking_vars = array(
'utm_campaign' => $this->get_item_name() . ' licensing',
'utm_medium' => 'link',
'utm_source' => $this->get_item_name(),
'utm_content' => $link_identifier
);
// URL encode tracking vars.
$tracking_vars = urlencode_deep( $tracking_vars );
$query_string = build_query( $tracking_vars );
return $url . '#' . $query_string;
}
}
}

View File

@@ -0,0 +1,224 @@
<?php
if ( ! class_exists( "Yoast_Update_Manager", false ) ) {
class Yoast_Update_Manager {
/**
* @var Yoast_Product
*/
protected $product;
/**
* @var Yoast_License_Manager
*/
protected $license_manager;
/**
* @var string
*/
protected $error_message = '';
/**
* @var object
*/
protected $update_response = null;
/**
* @var string The transient name storing the API response
*/
private $response_transient_key = '';
/**
* @var string The transient name that stores failed request tries
*/
private $request_failed_transient_key = '';
/**
* Constructor
*
* @param Yoast_Product $product The product.
* @param Yoast_License_Manager $license_manager The License Manager.
*/
public function __construct( Yoast_Product $product, $license_manager ) {
$this->product = $product;
$this->license_manager = $license_manager;
// generate transient names
$this->response_transient_key = $this->product->get_transient_prefix() . '-update-response';
$this->request_failed_transient_key = $this->product->get_transient_prefix() . '-update-request-failed';
// maybe delete transient
$this->maybe_delete_transients();
}
/**
* Deletes the various transients
* If we're on the update-core.php?force-check=1 page
*/
private function maybe_delete_transients() {
global $pagenow;
if ( $pagenow === 'update-core.php' && isset( $_GET['force-check'] ) ) {
delete_transient( $this->response_transient_key );
delete_transient( $this->request_failed_transient_key );
}
}
/**
* If the update check returned a WP_Error, show it to the user
*/
public function show_update_error() {
if ( $this->error_message === '' ) {
return;
}
?>
<div class="notice notice-error yoast-notice-error">
<p><?php printf( __( '%s failed to check for updates because of the following error: <em>%s</em>', $this->product->get_text_domain() ), $this->product->get_item_name(), $this->error_message ); ?></p>
</div>
<?php
}
/**
* Calls the API and, if successfull, returns the object delivered by the API.
*
* @uses get_bloginfo()
* @uses wp_remote_post()
* @uses is_wp_error()
*
* @return false||object
*/
private function call_remote_api() {
// only check if the failed transient is not set (or if it's expired)
if ( get_transient( $this->request_failed_transient_key ) !== false ) {
return false;
}
// start request process
global $wp_version;
// set a transient to prevent failed update checks on every page load
// this transient will be removed if a request succeeds
set_transient( $this->request_failed_transient_key, 'failed', 10800 );
// setup api parameters
$api_params = array(
'edd_action' => 'get_version',
'license' => $this->license_manager->get_license_key(),
'item_name' => $this->product->get_item_name(),
'wp_version' => $wp_version,
'item_version' => $this->product->get_version(),
'url' => $this->license_manager->get_url(),
'slug' => $this->product->get_slug(),
);
// Add product ID from product if it is implemented.
if ( method_exists( $this->product, 'get_product_id' ) ) {
$product_id = $this->product->get_product_id();
if ( $product_id > 0 ) {
$api_params['product_id'] = $this->product->get_product_id();
}
}
// setup request parameters
$request_params = array(
'method' => 'POST',
'body' => $api_params
);
require_once dirname( __FILE__ ) . '/class-api-request.php';
$request = new Yoast_API_Request( $this->product->get_api_url(), $request_params );
if ( $request->is_valid() !== true ) {
// show error message
$this->error_message = $request->get_error_message();
add_action( 'admin_notices', array( $this, 'show_update_error' ) );
return false;
}
// request succeeded, delete transient indicating a request failed
delete_transient( $this->request_failed_transient_key );
// decode response
$response = $request->get_response();
// check if response returned that a given site was inactive
if ( isset( $response->license_check ) && ! empty( $response->license_check ) && $response->license_check != 'valid' ) {
// deactivate local license
$this->license_manager->set_license_status( 'invalid' );
// show notice to let the user know we deactivated his/her license
$this->error_message = __( "This site has not been activated properly on yoast.com and thus cannot check for future updates. Please activate your site with a valid license key.", $this->product->get_text_domain() );
/**
* Filter: 'yoast-show-license-notice' - Show the license notice.
*
* @api bool $show True if notices should be shown.
*/
if ( apply_filters( 'yoast-show-license-notice', true ) ) {
add_action( 'admin_notices', array( $this, 'show_update_error' ) );
}
}
$response->sections = maybe_unserialize( $response->sections );
// store response
set_transient( $this->response_transient_key, $response, 10800 );
return $response;
}
/**
* Gets the remote product data (from the EDD API)
*
* - If it was previously fetched in the current requests, this gets it from the instance property
* - Next, it tries the 3-hour transient
* - Next, it calls the remote API and stores the result
*
* @return object
*/
protected function get_remote_data() {
// always use property if it's set
if ( null !== $this->update_response ) {
return $this->update_response;
}
// get cached remote data
$data = $this->get_cached_remote_data();
// if cache is empty or expired, call remote api
if ( $data === false ) {
$data = $this->call_remote_api();
}
$this->update_response = $data;
return $data;
}
/**
* Gets the remote product data from a 3-hour transient
*
* @return bool|mixed
*/
private function get_cached_remote_data() {
$data = get_transient( $this->response_transient_key );
if ( $data ) {
return $data;
}
return false;
}
}
}

View File

@@ -0,0 +1,2 @@
<?php
//Nothing to see here

View File

@@ -0,0 +1,102 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
header( 'HTTP/1.0 403 Forbidden' );
die;
}
/**
* @var Yoast_Product $product
*/
$product = $this->product;
$this->show_license_form_heading();
if( $api_host_available['availability'] === false ){
echo '<div class="notice notice-error inline yoast-notice-error"><p>' . sprintf( __( 'We couldn\'t create a connection to our API to verify your license key(s). Please ask your hosting company to allow outgoing connections from your server to %s.', $product->get_text_domain() ), $api_host_available['url'] ) . '</p></div>';
}
if( $api_host_available['curl_version'] !== false && version_compare( $api_host_available['curl_version'], '7.20.0', '<')){
echo '<div class="notice notice-error inline yoast-notice-error"><p>' . sprintf( __( 'Your server has an outdated version of the PHP module cURL (Version: %s). Please ask your hosting company to update this to a recent version of cURL. You can read more about that in our %sKnowledge base%s.', $product->get_text_domain() ), $api_host_available['curl_version'], '<a href="http://kb.yoast.com/article/90-is-my-curl-up-to-date" target="_blank">', '</a>' ) . '</p></div>';
}
// Output form tags if we're not embedded in another form
if( ! $embedded ) {
echo '<form method="post" action="">';
}
wp_nonce_field( $nonce_name, $nonce_name ); ?>
<table class="form-table yoast-license-form">
<tbody>
<tr valign="top">
<th scope="row" valign="top"><?php _e( 'License status', $product->get_text_domain() ); ?></th>
<td>
<?php if ( $this->license_is_valid() ) { ?>
<span class="yoast-license-status-active"><?php _e( 'ACTIVE', $product->get_text_domain() ); ?></span> &nbsp; - &nbsp; <?php _e( 'you are receiving updates.', $product->get_text_domain() ); ?>
<?php } else { ?>
<span class="yoast-license-status-inactive"><?php _e( 'INACTIVE', $product->get_text_domain() ); ?></span> &nbsp; - &nbsp; <?php _e( 'you are <strong>not</strong> receiving updates.', $product->get_text_domain() ); ?>
<?php } ?>
</td>
</tr>
<tr valign="top">
<th scope="row" valign="top"><?php _e('Toggle license status', $product->get_text_domain() ); ?></th>
<td class="yoast-license-toggler">
<?php if( $this->license_is_valid() ) { ?>
<button name="<?php echo esc_attr( $action_name ); ?>" type="submit" class="button button-secondary yoast-license-deactivate" value="deactivate"><?php echo esc_html_e( 'Deactivate License', $product->get_text_domain() ); ?></button> &nbsp;
<small><?php _e( '(deactivate your license so you can activate it on another WordPress site)', $product->get_text_domain() ); ?></small>
<?php } else {
if( $this->get_license_key() !== '') { ?>
<button name="<?php echo esc_attr( $action_name ); ?>" type="submit" class="button button-secondary yoast-license-activate" value="activate" /><?php echo esc_html_e('Activate License', $product->get_text_domain() ); ?></button> &nbsp;
<?php } else {
_e( 'Please enter a license key in the field below first.', $product->get_text_domain() );
}
} ?>
</td>
</tr>
<tr valign="top">
<th scope="row" valign="top"><?php _e( 'License Key', $product->get_text_domain() ); ?></th>
<td>
<input name="<?php echo esc_attr( $key_name ); ?>" type="text" class="regular-text textinput yoast-license-key-field <?php if( $obfuscate ) { ?>yoast-license-obfuscate<?php } ?>" value="<?php echo esc_attr( $visible_license_key ); ?>" placeholder="<?php echo esc_attr( sprintf( __( 'Paste your %s license key here...', $product->get_text_domain() ), $product->get_item_name() ) ); ?>" <?php if( $readonly ) { echo 'readonly="readonly"'; } ?> />
<?php if( $this->license_constant_is_defined ) { ?>
<p class="help"><?php printf( __( "You defined your license key using the %s PHP constant.", $product->get_text_domain() ), '<code>' . $this->license_constant_name . '</code>' ); ?></p>
<?php } ?>
</td>
</tr>
</tbody>
</table>
<?php
if( $this->license_is_valid() ) {
$expiry_date = strtotime( $this->get_license_expiry_date() );
if( $expiry_date !== false ) {
echo '<p>';
printf( __( 'Your %s license will expire on %s.', $product->get_text_domain() ), $product->get_item_name(), date('F jS Y', $expiry_date ) );
if( strtotime( '+3 months' ) > $expiry_date ) {
printf( ' ' . __('%sRenew your license now%s.', $product->get_text_domain() ), '<a href="'. $this->product->get_tracking_url( 'renewal' ) .'">', '</a>' );
}
echo '</p>';
}
}
// Only show a "Save Changes" button and end form if we're not embedded in another form.
if( ! $embedded ) {
// only show "Save Changes" button if license is not activated and not defined with a constant
if( $readonly === false && $api_host_available['availability'] === true ) {
submit_button();
}
echo '</form>';
}
$product = null;

View File

@@ -0,0 +1,2 @@
<?php
//Nothing to see here

View File

@@ -0,0 +1,67 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
header( 'HTTP/1.0 403 Forbidden' );
die;
}
?><script type="text/javascript">
(function($) {
if( typeof YoastLicenseManager !== "undefined" ) {
return;
}
window.YoastLicenseManager = (function () {
function init() {
var $keyInputs = $(".yoast-license-key-field.yoast-license-obfuscate");
var $actionButtons = $('.yoast-license-toggler button');
var $submitButtons = $('input[type="submit"], button[type="submit"]');
$submitButtons.click( addDisableEvent );
$actionButtons.click( actOnLicense );
$keyInputs.click( setEmptyValue );
}
function setEmptyValue() {
if( ! $(this).is('[readonly]') ) {
$(this).val('');
}
}
function actOnLicense() {
var $formScope = $(this).closest('form');
var $actionButton = $formScope.find('.yoast-license-toggler button');
// fake input field with exact same name => value
$("<input />")
.attr('type', 'hidden')
.attr( 'name', $(this).attr('name') )
.val( $(this).val() )
.appendTo( $formScope );
// change button text to show we're working..
var text = ( $actionButton.hasClass('yoast-license-activate') ) ? "Activating..." : "Deactivating...";
$actionButton.text( text );
}
function addDisableEvent() {
var $formScope = $(this).closest('form');
$formScope.submit(disableButtons);
}
function disableButtons() {
var $formScope = $(this).closest('form');
var $submitButton = $formScope.find('input[type="submit"], button[type="submit"]');
$submitButton.prop( 'disabled', true );
}
return {
init: init
}
})();
YoastLicenseManager.init();
})(jQuery);
</script>