<?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; } }