<?php
/**
* A Terms of Service class for Jetpack.
*
* @package automattic/jetpack-licensing
*/
namespace Automattic\Jetpack;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Licensing\Endpoints;
use Jetpack_IXR_ClientMulticall;
use Jetpack_Options;
use WP_Error;
/**
* Class Licensing.
* Helper class that is responsible for attaching licenses to the current site.
*
* @since 1.1.1
*/
class Licensing {
/**
* Name of the WordPress option that holds all known Jetpack licenses.
*
* @const string
*/
const LICENSES_OPTION_NAME = 'jetpack_licenses';
/**
* Name of the WordPress transient that holds the last license attaching error, if any.
*
* @const string
*/
const ERROR_TRANSIENT_NAME = 'jetpack_licenses_error';
/**
* Holds the singleton instance of this class.
*
* @var self
*/
protected static $instance = false;
/**
* Singleton.
*
* @static
*/
public static function instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Initialize.
*
* @return void
*/
public function initialize() {
add_action( 'add_option_' . self::LICENSES_OPTION_NAME, array( $this, 'attach_stored_licenses' ) );
add_action( 'update_option_' . self::LICENSES_OPTION_NAME, array( $this, 'attach_stored_licenses' ) );
add_action( 'jetpack_authorize_ending_authorized', array( $this, 'attach_stored_licenses_on_connection' ) );
add_action( 'rest_api_init', array( $this, 'initialize_endpoints' ) );
}
/**
* Initialize endpoints required for Licensing package.
*
* @since 1.7.0
*
* @return void
*/
public function initialize_endpoints() {
$endpoints = new Endpoints();
$endpoints->register_endpoints();
}
/**
* Get Jetpack connection manager instance.
*
* @return Connection_Manager
*/
protected function connection() {
static $connection;
if ( null === $connection ) {
$connection = new Connection_Manager();
}
return $connection;
}
/**
* Get the last license attach request error that has occurred, if any.
*
* @return string Human-readable error message or an empty string.
*/
public function last_error() {
return Jetpack_Options::get_option( 'licensing_error', '' );
}
/**
* Log an error to be surfaced to the user at a later time.
*
* @param string $error Human-readable error message.
* @return void
*/
public function log_error( $error ) {
$substr = function_exists( 'mb_substr' ) ? 'mb_substr' : 'substr';
Jetpack_Options::update_option( 'licensing_error', $substr( $error, 0, 1024 ) );
}
/**
* Get all stored licenses.
*
* @return string[] License keys.
*/
public function stored_licenses() {
$licenses = (array) get_option( self::LICENSES_OPTION_NAME, array() );
$licenses = array_filter( $licenses, 'is_scalar' );
$licenses = array_map( 'strval', $licenses );
$licenses = array_filter( $licenses );
return $licenses;
}
/**
* Append a license
*
* @param string $license A jetpack license key.
* @return bool True if the option was updated with the new license, false otherwise.
*/
public function append_license( $license ) {
$licenses = $this->stored_licenses();
array_push( $licenses, $license );
return update_option( self::LICENSES_OPTION_NAME, $licenses );
}
/**
* Make an authenticated WP.com XMLRPC multicall request to attach the provided license keys.
*
* @param string[] $licenses License keys to attach.
* @return Jetpack_IXR_ClientMulticall
*/
protected function attach_licenses_request( array $licenses ) {
$xml = new Jetpack_IXR_ClientMulticall( array( 'timeout' => 30 ) );
foreach ( $licenses as $license ) {
$xml->addCall( 'jetpack.attachLicense', $license );
}
$xml->query();
return $xml;
}
/**
* Attach the given licenses.
*
* @param string[] $licenses Licenses to attach.
* @return array|WP_Error Results for each license (which may include WP_Error instances) or a WP_Error instance.
*/
public function attach_licenses( array $licenses ) {
if ( ! $this->connection()->has_connected_owner() ) {
return new WP_Error( 'not_connected', __( 'Jetpack doesn\'t have a connected owner.', 'jetpack-licensing' ) );
}
if ( empty( $licenses ) ) {
return array();
}
$xml = $this->attach_licenses_request( $licenses );
if ( $xml->isError() ) {
$error = new WP_Error( 'request_failed', __( 'License attach request failed.', 'jetpack-licensing' ) );
$error->add( $xml->getErrorCode(), $xml->getErrorMessage() );
return $error;
}
$results = array_map(
function ( $response ) {
if ( isset( $response['faultCode'] ) || isset( $response['faultString'] ) ) {
return new WP_Error( $response['faultCode'], $response['faultString'] );
}
return $response;
},
(array) $xml->getResponse()
);
return $results;
}
/**
* Attach all stored licenses.
*
* @return array|WP_Error Results for each license (which may include WP_Error instances) or a WP_Error instance.
*/
public function attach_stored_licenses() {
$licenses = $this->stored_licenses();
$results = $this->attach_licenses( $licenses );
if ( is_wp_error( $results ) ) {
if ( 'request_failed' === $results->get_error_code() ) {
$this->log_error(
__( 'Failed to attach your Jetpack license(s). Please try reconnecting Jetpack.', 'jetpack-licensing' )
);
}
return $results;
}
$failed = array();
foreach ( $results as $index => $result ) {
if ( isset( $licenses[ $index ] ) && is_wp_error( $result ) ) {
$failed[] = $licenses[ $index ];
}
}
if ( ! empty( $failed ) ) {
$this->log_error(
sprintf(
/* translators: %s is a comma-separated list of license keys. */
__( 'The following Jetpack licenses are invalid, already in use, or revoked: %s', 'jetpack-licensing' ),
implode( ', ', $failed )
)
);
}
return $results;
}
/**
* Attach all stored licenses during connection flow for the connection owner.
*
* @return void
*/
public function attach_stored_licenses_on_connection() {
if ( $this->connection()->is_connection_owner() ) {
$this->attach_stored_licenses();
}
}
/**
* Is the current user allowed to use the Licensing Input UI?
*
* @since 1.4.0
* @return bool
*/
public static function is_licensing_input_enabled() {
/**
* Filter that checks if the user is allowed to see the Licensing UI. `true` enables it.
*
* @since 1.4.0
*
* @param bool False by default.
*/
return apply_filters( 'jetpack_licensing_ui_enabled', false ) && current_user_can( 'jetpack_connect_user' );
}
/**
* Gets the user-licensing activation notice dismissal info.
*
* @since 10.4.0
* @return array
*/
public function get_license_activation_notice_dismiss() {
$default = array(
'last_detached_count' => null,
'last_dismissed_time' => null,
);
if ( $this->connection()->is_user_connected() && $this->connection()->is_connection_owner() ) {
return Jetpack_Options::get_option( 'licensing_activation_notice_dismiss', $default );
}
return $default;
}
}