This commit is contained in:
2026-07-02 15:54:39 -06:00
commit 9883323161
17470 changed files with 4470592 additions and 0 deletions
@@ -0,0 +1,470 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [3.1.9] - 2026-06-08
### Changed
- Internal updates.
## [3.1.8] - 2026-06-05
### Changed
- Internal updates.
## [3.1.7] - 2026-05-19
### Changed
- Internal updates.
## [3.1.6] - 2026-05-04
### Changed
- Internal: No longer require automattic/jetpack-changelogger as a per-project dev dependency. [#48225]
## [3.1.5] - 2026-02-23
### Changed
- Update dependencies.
## [3.1.4] - 2026-02-02
### Changed
- Update dependencies.
## [3.1.3] - 2025-12-15
### Changed
- Internal updates.
## [3.1.2] - 2025-12-08
### Fixed
- Ensure proper flags are used with `json_encode()`. [#46092]
## [3.1.1] - 2025-10-20
### Changed
- Internal updates.
## [3.1.0] - 2025-08-04
### Changed
- Prevent fatals when non-connection owner adds license. [#44613]
## [3.0.9] - 2025-04-28
### Changed
- Internal updates.
## [3.0.8] - 2025-03-21
### Changed
- Internal updates.
## [3.0.7] - 2025-03-12
### Changed
- Internal updates.
## [3.0.6] - 2025-03-05
### Changed
- Internal updates.
## [3.0.5] - 2025-02-24
### Changed
- Update dependencies.
## [3.0.4] - 2025-02-03
### Changed
- Internal updates.
## [3.0.3] - 2025-01-20
### Changed
- Code: Use function-style exit() and die() with a default status code of 0. [#41167]
## [3.0.2] - 2024-12-02
### Changed
- Exclude revoked licenses from results when filtering unattached licenses in `Licensing::get_user_licenses()`. Only unattached and valid licenses are now returned. [#40215]
## [3.0.1] - 2024-11-25
### Changed
- Updated dependencies. [#40286]
## [3.0.0] - 2024-11-14
### Removed
- General: Update minimum PHP version to 7.2. [#40147]
## [2.0.12] - 2024-11-04
### Added
- Enable test coverage. [#39961]
## [2.0.11] - 2024-10-29
### Changed
- Internal updates.
## [2.0.10] - 2024-09-23
### Changed
- Update dependencies.
## [2.0.9] - 2024-09-05
### Changed
- Update dependencies.
## [2.0.8] - 2024-09-05
### Changed
- Update dependencies.
## [2.0.7] - 2024-08-26
### Changed
- Updated package dependencies. [#39004]
## [2.0.6] - 2024-08-19
### Changed
- Internal updates.
## [2.0.5] - 2024-04-22
### Changed
- Internal updates.
## [2.0.4] - 2024-04-08
### Changed
- Internal updates.
## [2.0.3] - 2024-03-25
### Changed
- Internal updates.
## [2.0.2] - 2024-03-14
### Changed
- Internal updates.
## [2.0.1] - 2023-11-21
## [2.0.0] - 2023-11-20
### Changed
- Updated required PHP version to >= 7.0. [#34192]
## [1.8.4] - 2023-09-19
- Minor internal updates.
## [1.8.3] - 2023-08-23
### Changed
- Updated package dependencies. [#32605]
## [1.8.2] - 2023-04-10
### Added
- Add Jetpack Autoloader package suggestion. [#29988]
## [1.8.1] - 2023-02-20
### Changed
- Minor internal updates.
## [1.8.0] - 2023-02-08
### Added
- After connection flow, load unattached licenses. If any of them match the product that's being connected, redirect users to the license activation page. [#28509]
## [1.7.14] - 2023-01-11
### Changed
- Updated package dependencies.
## [1.7.13] - 2022-12-02
### Changed
- Updated package dependencies. [#27688]
## [1.7.12] - 2022-11-22
### Changed
- Updated package dependencies. [#27043]
## [1.7.11] - 2022-10-25
### Changed
- Updated package dependencies. [#26705]
## [1.7.10] - 2022-09-20
### Changed
- Updated package dependencies.
## [1.7.9] - 2022-09-08
### Changed
- Updated package dependencies.
## [1.7.8] - 2022-08-29
### Changed
- Updated package dependencies.
## [1.7.7] - 2022-08-23
### Changed
- Updated package dependencies. [#25628]
## [1.7.6] - 2022-08-03
### Changed
- Updated package dependencies. [#25300, #25315]
## [1.7.5] - 2022-07-26
### Changed
- Updated package dependencies. [#25158]
## [1.7.4] - 2022-06-21
### Changed
- Renaming master to trunk.
## [1.7.3] - 2022-06-14
### Changed
- Updated package dependencies. [#24529]
## [1.7.2] - 2022-05-04
### Changed
- Updated package dependencies. [#24095]
### Deprecated
- Moved the options class into Connection. [#24095]
## [1.7.1] - 2022-04-26
### Changed
- Updated package dependencies.
## [1.7.0] - 2022-04-19
### Changed
- Moved licensing endpoints from the Jetpack plugin to the Licensing package
## [1.6.4] - 2022-04-12
### Changed
- Updated package dependencies.
## [1.6.3] - 2022-03-02
### Changed
- Updated package dependencies.
## [1.6.2] - 2022-01-25
### Changed
- Updated package dependencies.
## [1.6.1] - 2022-01-18
### Changed
- Updated package dependencies.
## [1.6.0] - 2022-01-04
### Changed
- Switch to pcov for code coverage.
- Updated package dependencies
- Updated package textdomain from `jetpack` to `jetpack-licensing`.
## [1.5.4] - 2021-12-14
### Changed
- Updated package dependencies.
## [1.5.3] - 2021-12-03
### Changed
- Increases the timeout of the license activation request from 10 to 30 seconds.
## [1.5.2] - 2021-11-30
### Changed
- Updated package dependencies.
## [1.5.1] - 2021-11-23
### Changed
- Updated package dependencies.
## [1.5.0] - 2021-11-16
### Added
- Add a test for update to WPCOM return change.
- Added get_license_activation_notice_dismiss() function.
## [1.4.9] - 2021-11-02
### Changed
- Set `convertDeprecationsToExceptions` true in PHPUnit config.
- Update PHPUnit configs to include just what needs coverage rather than include everything then try to exclude stuff that doesn't.
## [1.4.8] - 2021-10-19
### Changed
- Updated package dependencies.
## [1.4.7] - 2021-10-12
### Changed
- Updated package dependencies
## [1.4.6] - 2021-09-28
### Changed
- Updated package dependencies.
## [1.4.5] - 2021-08-31
### Changed
- Run composer update on test-php command instead of phpunit.
- Tests: update PHPUnit polyfills dependency (yoast/phpunit-polyfills).
- Updated versions in annotations.
## [1.4.4] - 2021-07-27
### Changed
- Updated package dependencies.
## [1.4.3] - 2021-06-29
### Changed
- Updated package dependencies.
## [1.4.2] - 2021-05-25
### Changed
- Updated package dependencies.
## [1.4.1] - 2021-04-27
### Changed
- Updated package dependencies.
## [1.4.0] - 2021-03-30
### Added
- Composer alias for dev-master, to improve dependencies
- Dashboard: add new option to input license key.
### Changed
- Replace usage of deprecated is_active method
- Update package dependencies.
### Fixed
- Fix stored licenses not being attached on option creation
- Use `composer update` rather than `install` in scripts, as composer.lock isn't checked in.
## [1.3.4] - 2021-02-23
- CI: Make tests more generic
- codesniffer: Hack around mediawiki-codesniffer bug
## [1.3.3] - 2021-02-08
- Update dependencies to latest stable
## [1.3.2] - 2021-01-28
- Update dependencies to latest stable
## [1.3.1] - 2021-01-26
- Add mirror-repo information to all current composer packages
- Monorepo: Reorganize all projects
## [1.3.0] - 2021-01-05
- Pin dependencies
- Packages: Update for PHP 8 testing
## [1.2.4] - 2020-11-24
- Version packages for release
## [1.2.3] - 2020-11-24
- Updated PHPCS: Packages and Debugger
## [1.2.2] - 2020-11-05
- Update dependencies to latest stable
## [1.2.1] - 2020-10-29
- Update dependencies to latest stable
## [1.2.0] - 2020-10-27
- Licensing: use Oxford comma in error message
## [1.1.4] - 2020-10-14
- Update dependencies to latest stable
## [1.1.3] - 2020-10-09
- Update dependencies to latest stable
## [1.1.2] - 2020-10-06
- Update dependencies to latest stable
## [1.1.1] - 2020-10-01
- Update dependencies to latest stable
## [1.1.0] - 2020-09-29
- Update dependencies to latest stable
## 1.0.0 - 2020-09-24
- Licensing: Add support for Jetpack licenses
[3.1.9]: https://github.com/Automattic/jetpack-licensing/compare/v3.1.8...v3.1.9
[3.1.8]: https://github.com/Automattic/jetpack-licensing/compare/v3.1.7...v3.1.8
[3.1.7]: https://github.com/Automattic/jetpack-licensing/compare/v3.1.6...v3.1.7
[3.1.6]: https://github.com/Automattic/jetpack-licensing/compare/v3.1.5...v3.1.6
[3.1.5]: https://github.com/Automattic/jetpack-licensing/compare/v3.1.4...v3.1.5
[3.1.4]: https://github.com/Automattic/jetpack-licensing/compare/v3.1.3...v3.1.4
[3.1.3]: https://github.com/Automattic/jetpack-licensing/compare/v3.1.2...v3.1.3
[3.1.2]: https://github.com/Automattic/jetpack-licensing/compare/v3.1.1...v3.1.2
[3.1.1]: https://github.com/Automattic/jetpack-licensing/compare/v3.1.0...v3.1.1
[3.1.0]: https://github.com/Automattic/jetpack-licensing/compare/v3.0.9...v3.1.0
[3.0.9]: https://github.com/Automattic/jetpack-licensing/compare/v3.0.8...v3.0.9
[3.0.8]: https://github.com/Automattic/jetpack-licensing/compare/v3.0.7...v3.0.8
[3.0.7]: https://github.com/Automattic/jetpack-licensing/compare/v3.0.6...v3.0.7
[3.0.6]: https://github.com/Automattic/jetpack-licensing/compare/v3.0.5...v3.0.6
[3.0.5]: https://github.com/Automattic/jetpack-licensing/compare/v3.0.4...v3.0.5
[3.0.4]: https://github.com/Automattic/jetpack-licensing/compare/v3.0.3...v3.0.4
[3.0.3]: https://github.com/Automattic/jetpack-licensing/compare/v3.0.2...v3.0.3
[3.0.2]: https://github.com/Automattic/jetpack-licensing/compare/v3.0.1...v3.0.2
[3.0.1]: https://github.com/Automattic/jetpack-licensing/compare/v3.0.0...v3.0.1
[3.0.0]: https://github.com/Automattic/jetpack-licensing/compare/v2.0.12...v3.0.0
[2.0.12]: https://github.com/Automattic/jetpack-licensing/compare/v2.0.11...v2.0.12
[2.0.11]: https://github.com/Automattic/jetpack-licensing/compare/v2.0.10...v2.0.11
[2.0.10]: https://github.com/Automattic/jetpack-licensing/compare/v2.0.9...v2.0.10
[2.0.9]: https://github.com/Automattic/jetpack-licensing/compare/v2.0.8...v2.0.9
[2.0.8]: https://github.com/Automattic/jetpack-licensing/compare/v2.0.7...v2.0.8
[2.0.7]: https://github.com/Automattic/jetpack-licensing/compare/v2.0.6...v2.0.7
[2.0.6]: https://github.com/Automattic/jetpack-licensing/compare/v2.0.5...v2.0.6
[2.0.5]: https://github.com/Automattic/jetpack-licensing/compare/v2.0.4...v2.0.5
[2.0.4]: https://github.com/Automattic/jetpack-licensing/compare/v2.0.3...v2.0.4
[2.0.3]: https://github.com/Automattic/jetpack-licensing/compare/v2.0.2...v2.0.3
[2.0.2]: https://github.com/Automattic/jetpack-licensing/compare/v2.0.1...v2.0.2
[2.0.1]: https://github.com/Automattic/jetpack-licensing/compare/v2.0.0...v2.0.1
[2.0.0]: https://github.com/Automattic/jetpack-licensing/compare/v1.8.4...v2.0.0
[1.8.4]: https://github.com/Automattic/jetpack-licensing/compare/v1.8.3...v1.8.4
[1.8.3]: https://github.com/Automattic/jetpack-licensing/compare/v1.8.2...v1.8.3
[1.8.2]: https://github.com/Automattic/jetpack-licensing/compare/v1.8.1...v1.8.2
[1.8.1]: https://github.com/Automattic/jetpack-licensing/compare/v1.8.0...v1.8.1
[1.8.0]: https://github.com/Automattic/jetpack-licensing/compare/v1.7.14...v1.8.0
[1.7.14]: https://github.com/Automattic/jetpack-licensing/compare/v1.7.13...v1.7.14
[1.7.13]: https://github.com/Automattic/jetpack-licensing/compare/v1.7.12...v1.7.13
[1.7.12]: https://github.com/Automattic/jetpack-licensing/compare/v1.7.11...v1.7.12
[1.7.11]: https://github.com/Automattic/jetpack-licensing/compare/v1.7.10...v1.7.11
[1.7.10]: https://github.com/Automattic/jetpack-licensing/compare/v1.7.9...v1.7.10
[1.7.9]: https://github.com/Automattic/jetpack-licensing/compare/v1.7.8...v1.7.9
[1.7.8]: https://github.com/Automattic/jetpack-licensing/compare/v1.7.7...v1.7.8
[1.7.7]: https://github.com/Automattic/jetpack-licensing/compare/v1.7.6...v1.7.7
[1.7.6]: https://github.com/Automattic/jetpack-licensing/compare/v1.7.5...v1.7.6
[1.7.5]: https://github.com/Automattic/jetpack-licensing/compare/v1.7.4...v1.7.5
[1.7.4]: https://github.com/Automattic/jetpack-licensing/compare/v1.7.3...v1.7.4
[1.7.3]: https://github.com/Automattic/jetpack-licensing/compare/v1.7.2...v1.7.3
[1.7.2]: https://github.com/Automattic/jetpack-licensing/compare/v1.7.1...v1.7.2
[1.7.1]: https://github.com/Automattic/jetpack-licensing/compare/v1.7.0...v1.7.1
[1.7.0]: https://github.com/Automattic/jetpack-licensing/compare/v1.6.4...v1.7.0
[1.6.4]: https://github.com/Automattic/jetpack-licensing/compare/v1.6.3...v1.6.4
[1.6.3]: https://github.com/Automattic/jetpack-licensing/compare/v1.6.2...v1.6.3
[1.6.2]: https://github.com/Automattic/jetpack-licensing/compare/v1.6.1...v1.6.2
[1.6.1]: https://github.com/Automattic/jetpack-licensing/compare/v1.6.0...v1.6.1
[1.6.0]: https://github.com/Automattic/jetpack-licensing/compare/v1.5.4...v1.6.0
[1.5.4]: https://github.com/Automattic/jetpack-licensing/compare/v1.5.3...v1.5.4
[1.5.3]: https://github.com/Automattic/jetpack-licensing/compare/v1.5.2...v1.5.3
[1.5.2]: https://github.com/Automattic/jetpack-licensing/compare/v1.5.1...v1.5.2
[1.5.1]: https://github.com/Automattic/jetpack-licensing/compare/v1.5.0...v1.5.1
[1.5.0]: https://github.com/Automattic/jetpack-licensing/compare/v1.4.9...v1.5.0
[1.4.9]: https://github.com/Automattic/jetpack-licensing/compare/v1.4.8...v1.4.9
[1.4.8]: https://github.com/Automattic/jetpack-licensing/compare/v1.4.7...v1.4.8
[1.4.7]: https://github.com/Automattic/jetpack-licensing/compare/v1.4.6...v1.4.7
[1.4.6]: https://github.com/Automattic/jetpack-licensing/compare/v1.4.5...v1.4.6
[1.4.5]: https://github.com/Automattic/jetpack-licensing/compare/v1.4.4...v1.4.5
[1.4.4]: https://github.com/Automattic/jetpack-licensing/compare/v1.4.3...v1.4.4
[1.4.3]: https://github.com/Automattic/jetpack-licensing/compare/v1.4.2...v1.4.3
[1.4.2]: https://github.com/Automattic/jetpack-licensing/compare/v1.4.1...v1.4.2
[1.4.1]: https://github.com/Automattic/jetpack-licensing/compare/v1.4.0...v1.4.1
[1.4.0]: https://github.com/Automattic/jetpack-licensing/compare/v1.3.4...v1.4.0
[1.3.4]: https://github.com/Automattic/jetpack-licensing/compare/v1.3.3...v1.3.4
[1.3.3]: https://github.com/Automattic/jetpack-licensing/compare/v1.3.2...v1.3.3
[1.3.2]: https://github.com/Automattic/jetpack-licensing/compare/v1.3.1...v1.3.2
[1.3.1]: https://github.com/Automattic/jetpack-licensing/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/Automattic/jetpack-licensing/compare/v1.2.4...v1.3.0
[1.2.4]: https://github.com/Automattic/jetpack-licensing/compare/v1.2.3...v1.2.4
[1.2.3]: https://github.com/Automattic/jetpack-licensing/compare/v1.2.2...v1.2.3
[1.2.2]: https://github.com/Automattic/jetpack-licensing/compare/v1.2.1...v1.2.2
[1.2.1]: https://github.com/Automattic/jetpack-licensing/compare/v1.2.0...v1.2.1
[1.2.0]: https://github.com/Automattic/jetpack-licensing/compare/v1.1.4...v1.2.0
[1.1.4]: https://github.com/Automattic/jetpack-licensing/compare/v1.1.3...v1.1.4
[1.1.3]: https://github.com/Automattic/jetpack-licensing/compare/v1.1.2...v1.1.3
[1.1.2]: https://github.com/Automattic/jetpack-licensing/compare/v1.1.1...v1.1.2
[1.1.1]: https://github.com/Automattic/jetpack-licensing/compare/v1.1.0...v1.1.1
[1.1.0]: https://github.com/Automattic/jetpack-licensing/compare/v1.0.0...v1.1.0
@@ -0,0 +1,431 @@
<?php
/**
* A Licensing Endpoints class for Jetpack.
*
* @package automattic/jetpack-licensing
*/
namespace Automattic\Jetpack\Licensing;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Licensing;
use Automattic\Jetpack\Status\Visitor;
use Jetpack_Options;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
/**
* Class Endpoints.
* Helper class that is responsible for registering and responding to licensing endpoint requests.
*
* @since 1.7.0
*/
class Endpoints {
/**
* This property stores the localized "Insufficient Permissions" error message.
*
* @var string Generic error message when user is not allowed to perform an action.
*/
private static $user_permissions_error_msg;
/**
* Constructor.
*/
public function __construct() {
self::$user_permissions_error_msg = esc_html__(
'You do not have the correct user permissions to perform this action.
Please contact your site admin if you think this is a mistake.',
'jetpack-licensing'
);
}
/**
* Declare the endpoints for the licensing package.
*
* @since 1.7.0
*
* @since-jetpack 10.9.0
*/
public function register_endpoints() {
/*
* Get and update the last licensing error message.
*/
register_rest_route(
'jetpack/v4',
'/licensing/error',
array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => __CLASS__ . '::get_licensing_error',
'permission_callback' => __CLASS__ . '::can_manage_options_check',
),
array(
'methods' => \WP_REST_Server::EDITABLE,
'callback' => __CLASS__ . '::update_licensing_error',
'permission_callback' => __CLASS__ . '::can_manage_options_check',
'args' => array(
'error' => array(
'required' => true,
'type' => 'string',
'validate_callback' => __CLASS__ . '::validate_string',
'sanitize_callback' => 'sanitize_text_field',
),
),
),
)
);
/**
* Sets a license. This is still used as part of the first pass at licensing done for partners.
*
* See https://github.com/Automattic/jetpack/pull/23687 for more details.
*/
register_rest_route(
'jetpack/v4',
'/licensing/set-license',
array(
'methods' => \WP_REST_Server::EDITABLE,
'callback' => __CLASS__ . '::set_jetpack_license',
'permission_callback' => __CLASS__ . '::set_jetpack_license_key_permission_check',
'args' => array(
'license' => array(
'required' => true,
'type' => 'string',
'validate_callback' => __CLASS__ . '::validate_string',
'sanitize_callback' => 'sanitize_text_field',
),
),
)
);
/**
* Get Jetpack user licenses.
*/
register_rest_route(
'jetpack/v4',
'licensing/user/licenses',
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => __CLASS__ . '::get_user_licenses',
'permission_callback' => __CLASS__ . '::can_manage_options_check',
)
);
/**
* Get Jetpack user license counts.
*/
register_rest_route(
'jetpack/v4',
'licensing/user/counts',
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => __CLASS__ . '::get_user_license_counts',
'permission_callback' => __CLASS__ . '::can_manage_options_check',
)
);
/**
* Update user-licensing activation notice dismiss info.
*/
register_rest_route(
'jetpack/v4',
'licensing/user/activation-notice-dismiss',
array(
'methods' => \WP_REST_Server::EDITABLE,
'callback' => __CLASS__ . '::update_licensing_activation_notice_dismiss',
'permission_callback' => __CLASS__ . '::can_manage_options_check',
'args' => array(
'last_detached_count' => array(
'required' => true,
'type' => 'integer',
'validate_callback' => __CLASS__ . '::validate_non_neg_int',
),
),
)
);
/**
* Attach licenses to user account
*/
register_rest_route(
'jetpack/v4',
'/licensing/attach-licenses',
array(
'methods' => \WP_REST_Server::EDITABLE,
'callback' => __CLASS__ . '::attach_jetpack_licenses',
'permission_callback' => __CLASS__ . '::can_manage_options_check',
'args' => array(
'licenses' => array(
'required' => true,
'type' => 'array',
'items' => array(
'type' => 'string',
),
),
),
)
);
}
/**
* Verify that the user can set a Jetpack license key
*
* @since 1.7.0
*
* @since-jetpack 9.5.0
*
* @return bool|WP_Error True if user is able to set a Jetpack license key
*/
public static function set_jetpack_license_key_permission_check() {
if ( Licensing::instance()->is_licensing_input_enabled() ) {
return true;
}
return new WP_Error( 'invalid_user_permission_set_jetpack_license_key', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) );
}
/**
* Verify that user can manage_options
*
* @since 1.7.0
*
* @return bool Whether user has the capability 'manage_options'.
*/
public static function can_manage_options_check() {
if ( current_user_can( 'manage_options' ) ) {
return true;
}
return new WP_Error( 'invalid_user_permission_view_admin', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) );
}
/**
* Validates that the parameter is a string.
*
* @since 1.7.0
*
* @since-jetpack 4.3.0
*
* @param string $value Value to check.
* @param WP_REST_Request $request The request sent to the WP REST API.
* @param string $param Name of the parameter passed to endpoint holding $value.
*
* @return bool|WP_Error
*/
public static function validate_string( $value, $request, $param ) {
if ( ! is_string( $value ) ) {
/* translators: %s: The literal parameter name. Should not be translated. */
return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be a string.', 'jetpack-licensing' ), $param ) );
}
return true;
}
/**
* Validates that the parameter is a non-negative integer (includes 0).
*
* @since 1.7.0
*
* @since-jetpack 10.4.0
*
* @param int $value Value to check.
* @param WP_REST_Request $request The request sent to the WP REST API.
* @param string $param Name of the parameter passed to endpoint holding $value.
*
* @return bool|WP_Error
*/
public static function validate_non_neg_int( $value, $request, $param ) {
if ( ! is_numeric( $value ) || $value < 0 ) {
return new WP_Error(
'invalid_param',
/* translators: %s: The literal parameter name. Should not be translated. */
sprintf( esc_html__( '%s must be a non-negative integer.', 'jetpack-licensing' ), $param )
);
}
return true;
}
/**
* Update the last licensing error message.
*
* @since 1.7.0
*
* @since-jetpack 9.0.0
*
* @param WP_REST_Request $request The request.
*
* @return bool true.
*/
public static function update_licensing_error( $request ) {
Licensing::instance()->log_error( $request['error'] );
return true;
}
/**
* Get the last licensing error message, if any.
*
* @since 1.7.0
*
* @since-jetpack 9.0.0
*
* @return string Licensing error message or empty string.
*/
public static function get_licensing_error() {
return Licensing::instance()->last_error();
}
/**
* Set a Jetpack license
*
* @since 1.7.0
*
* @since-jetpack 9.6.0
*
* @param WP_REST_Request $request The request.
*
* @return WP_REST_Response|WP_Error A response object if the option was successfully updated, or a WP_Error if it failed.
*/
public static function set_jetpack_license( $request ) {
$license = trim( sanitize_text_field( $request['license'] ) );
if ( Licensing::instance()->append_license( $license ) ) {
return rest_ensure_response( array( 'code' => 'success' ) );
}
return new WP_Error(
'setting_license_key_failed',
esc_html__( 'Could not set this license key. Please try again.', 'jetpack-licensing' ),
array( 'status' => 500 )
);
}
/**
* Gets the users licenses.
*
* @since 1.7.0
*
* @since-jetpack 10.4.0
*
* @return string|WP_Error A JSON object of user licenses if the request was successful, or a WP_Error otherwise.
*/
public static function get_user_licenses() {
$wpcom_request = Client::wpcom_json_api_request_as_user(
'/jetpack-licensing/user/licenses',
'2',
array(
'method' => 'GET',
'headers' => array(
'Content-Type' => 'application/json',
'X-Forwarded-For' => ( new Visitor() )->get_ip( true ),
),
)
);
$response_code = wp_remote_retrieve_response_code( $wpcom_request );
if ( 200 === $response_code ) {
$licenses = json_decode( wp_remote_retrieve_body( $wpcom_request ) );
return $licenses;
} else {
return new WP_Error(
'failed_to_fetch_data',
esc_html__( 'Unable to fetch the requested data.', 'jetpack-licensing' ),
array( 'status' => $response_code )
);
}
}
/**
* Gets the users licenses counts.
*
* @since 1.7.0
*
* @since-jetpack 10.4.0
*
* @return string|WP_Error A JSON object of user license counts if the request was successful, or a WP_Error otherwise.
*/
public static function get_user_license_counts() {
$wpcom_request = Client::wpcom_json_api_request_as_user(
'/jetpack-licensing/user/licenses/counts',
'2',
array(
'method' => 'GET',
'headers' => array(
'Content-Type' => 'application/json',
'X-Forwarded-For' => ( new Visitor() )->get_ip( true ),
),
)
);
$response_code = wp_remote_retrieve_response_code( $wpcom_request );
if ( 200 === $response_code ) {
$license_counts = json_decode( wp_remote_retrieve_body( $wpcom_request ) );
return $license_counts;
} else {
return new WP_Error(
'failed_to_fetch_data',
esc_html__( 'Unable to fetch the requested data.', 'jetpack-licensing' ),
array( 'status' => $response_code )
);
}
}
/**
* Update the user-licenses activation notice dismissal data.
*
* @since 1.7.0
*
* @since-jetpack 10.4.0
*
* @param WP_REST_Request $request The request sent to the WP REST API.
*
* @return array|WP_Error
*/
public static function update_licensing_activation_notice_dismiss( $request ) {
if ( ! isset( $request['last_detached_count'] ) ) {
return new WP_Error( 'invalid_param', esc_html__( 'Missing parameter "last_detached_count".', 'jetpack-licensing' ), array( 'status' => 404 ) );
}
$default = array(
'last_detached_count' => null,
'last_dismissed_time' => null,
);
$last_detached_count = ( '' === $request['last_detached_count'] )
? $default['last_detached_count']
: $request['last_detached_count'];
$last_dismissed_time = ( '' === $request['last_detached_count'] )
? $default['last_dismissed_time']
// Use UTC timezone and convert to ISO8601 format(DateTime::W3C) for best compatibility with JavaScript Date in all browsers.
: ( new \DateTime( 'NOW', new \DateTimeZone( 'UTC' ) ) )->format( \DateTime::W3C );
$notice_data = array(
'last_detached_count' => $last_detached_count,
'last_dismissed_time' => $last_dismissed_time,
);
Jetpack_Options::update_option( 'licensing_activation_notice_dismiss', $notice_data, true );
return rest_ensure_response( $notice_data );
}
/**
* Attach Jetpack licenses
*
* @since 1.7.0
*
* @since-jetpack 10.4.0
*
* @param WP_REST_Request $request The request.
*
* @return WP_REST_Response|WP_Error A response object
*/
public static function attach_jetpack_licenses( $request ) {
$licenses = array_map(
function ( $license ) {
return trim( sanitize_text_field( $license ) );
},
$request['licenses']
);
return rest_ensure_response( Licensing::instance()->attach_licenses( $licenses ) );
}
}
@@ -0,0 +1,359 @@
<?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;
}
/**
* Load current user's licenses.
*
* @param bool $unattached_only Only return unattached and not revoked licenses.
*
* @return array
*/
public function get_user_licenses( $unattached_only = false ) {
$licenses = Endpoints::get_user_licenses();
if ( empty( $licenses->items ) ) {
return array();
}
$items = $licenses->items;
if ( $unattached_only ) {
$items = array_filter(
$items,
static function ( $item ) {
return $item->attached_at === null && $item->revoked_at === null;
}
);
}
return $items;
}
/**
* If the destination URL is checkout page,
* see if there are unattached licenses they could use instead of getting a new one.
* If found, redirect the user to license activation.
*
* @param string $dest_url User's destination URL.
*
* @return void
*/
public function handle_user_connected_redirect( $dest_url ) {
if ( ! preg_match( '#^https://[^/]+/checkout/#i', $dest_url ) ) {
return;
}
$licenses = $this->get_user_licenses( true );
$plugin_slug = null;
$query_string = wp_parse_url( $dest_url, PHP_URL_QUERY );
if ( $query_string ) {
parse_str( $query_string, $query_args );
if ( $query_args['redirect_to']
&& preg_match( '/^admin\.php\?page=(jetpack-\w+)/i', $query_args['redirect_to'], $matches )
) {
$plugin_slug = $matches[1];
}
}
/**
* Check for the user's unattached licenses.
*
* @since 3.8.2
*
* @param bool $has_license Whether a license was already found.
* @param array $licenses Unattached licenses belonging to the user.
* @param string $plugin_slug Slug of the plugin that initiated the flow.
*/
if ( $plugin_slug && count( $licenses )
&& apply_filters( 'jetpack_connection_user_has_license', false, $licenses, $plugin_slug )
) {
wp_safe_redirect( '/wp-admin/admin.php?page=my-jetpack#/add-license' );
exit( 0 );
}
}
}