updated plugin `Jetpack Protect` version 1.1.1

This commit is contained in:
KawaiiPunk 2022-11-24 13:40:35 +00:00 committed by Gitium
parent 9bf96ad952
commit 5284e32c67
126 changed files with 6115 additions and 1644 deletions

View File

@ -5,6 +5,20 @@ 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).
## 1.1.1 - 2022-11-18
### Fixed
- Fix issue with plugin activation.
## 1.1.0 - 2022-11-17
### Added
- Add features for paid Jetpack Scan users, including file and database threats, on-demand scanning, and threat auto-fixers.
### Changed
- Compatibility: WordPress 6.1 compatibility
### Fixed
- Adjust alignment of spinner icon and loading text on the in-progress scan screen.
## 1.0.4 - 2022-08-29
### Fixed
- Fixed NaN error that prevented the pricing interstitial from rendering.

View File

@ -1 +1 @@
<?php return array('dependencies' => array('react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-url'), 'version' => 'c28c9ff5fed1d1b9867f');
<?php return array('dependencies' => array('react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-url'), 'version' => '79aba5c67c65c2093d2d');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,20 +5,22 @@
"license": "GPL-2.0-or-later",
"require": {
"ext-json": "*",
"automattic/jetpack-assets": "^1.17",
"automattic/jetpack-admin-ui": "^0.2.11",
"automattic/jetpack-autoloader": "^2.11",
"automattic/jetpack-composer-plugin": "^1.1",
"automattic/jetpack-config": "^1.9",
"automattic/jetpack-identity-crisis": "^0.8.22",
"automattic/jetpack-my-jetpack": "^2.0",
"automattic/jetpack-assets": "^1.17.25",
"automattic/jetpack-admin-ui": "^0.2.12",
"automattic/jetpack-autoloader": "^2.11.11",
"automattic/jetpack-composer-plugin": "^1.1.5",
"automattic/jetpack-config": "^1.11.0",
"automattic/jetpack-identity-crisis": "^0.8.31",
"automattic/jetpack-my-jetpack": "^2.4.0",
"automattic/jetpack-plugins-installer": "^0.2.0",
"automattic/jetpack-sync": "^1.38"
"automattic/jetpack-sync": "^1.43.0",
"automattic/jetpack-transport-helper": "^0.1.0",
"automattic/jetpack-plans": "^0.2.6"
},
"require-dev": {
"yoast/phpunit-polyfills": "1.0.3",
"automattic/jetpack-changelogger": "^3.2",
"automattic/wordbless": "0.3.1"
"automattic/jetpack-changelogger": "^3.2.1",
"automattic/wordbless": "0.4.0"
},
"autoload": {
"classmap": [
@ -45,15 +47,21 @@
"Composer\\Config::disableProcessTimeout",
"pnpm run watch"
],
"post-update-cmd": "php -r \"copy('vendor/automattic/wordbless/src/dbless-wpdb.php', 'wordpress/wp-content/db.php');\""
"post-install-cmd": "WorDBless\\Composer\\InstallDropin::copy",
"post-update-cmd": "WorDBless\\Composer\\InstallDropin::copy"
},
"repositories": [],
"minimum-stability": "dev",
"prefer-stable": true,
"extra": {
"mirror-repo": "Automattic/jetpack-protect-plugin",
"autorelease": true,
"autotagger": {
"v": false
},
"release-branch-prefix": "protect",
"wp-plugin-slug": "jetpack-protect",
"wp-svn-autopublish": true,
"version-constants": {
"JETPACK_PROTECT_VERSION": "jetpack-protect.php"
}
@ -64,6 +72,6 @@
"automattic/jetpack-autoloader": true,
"automattic/jetpack-composer-plugin": true
},
"autoloader-suffix": "c4802e05bbcf59fd3b6350e8d3e5482c_protectⓥ1_0_4"
"autoloader-suffix": "c4802e05bbcf59fd3b6350e8d3e5482c_protectⓥ1_1_1"
}
}

View File

@ -4,7 +4,7 @@
* Plugin Name: Jetpack Protect
* Plugin URI: https://wordpress.org/plugins/jetpack-protect
* Description: Security tools that keep your site safe and sound, from posts to plugins.
* Version: 1.0.4
* Version: 1.1.1
* Author: Automattic
* Author URI: https://jetpack.com/
* License: GPLv2 or later
@ -33,7 +33,7 @@ if ( ! defined( 'ABSPATH' ) ) {
exit;
}
define( 'JETPACK_PROTECT_VERSION', '1.0.4' );
define( 'JETPACK_PROTECT_VERSION', '1.1.1' );
define( 'JETPACK_PROTECT_DIR', plugin_dir_path( __FILE__ ) );
define( 'JETPACK_PROTECT_ROOT_FILE', __FILE__ );
define( 'JETPACK_PROTECT_ROOT_FILE_RELATIVE_PATH', plugin_basename( __FILE__ ) );

View File

@ -5,6 +5,10 @@ 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).
## [0.2.12] - 2022-09-20
### Changed
- Updated package dependencies.
## [0.2.11] - 2022-07-26
### Changed
- Updated package dependencies. [#25158]
@ -72,6 +76,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Fixing menu visibility issues.
[0.2.12]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.11...0.2.12
[0.2.11]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.10...0.2.11
[0.2.10]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.9...0.2.10
[0.2.9]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.8...0.2.9

View File

@ -24,7 +24,8 @@
"test-php": [
"@composer phpunit"
],
"post-update-cmd": "php -r \"copy('vendor/automattic/wordbless/src/dbless-wpdb.php', 'wordpress/wp-content/db.php');\""
"post-install-cmd": "WorDBless\\Composer\\InstallDropin::copy",
"post-update-cmd": "WorDBless\\Composer\\InstallDropin::copy"
},
"minimum-stability": "dev",
"prefer-stable": true,

View File

@ -13,7 +13,7 @@ namespace Automattic\Jetpack\Admin_UI;
*/
class Admin_Menu {
const PACKAGE_VERSION = '0.2.11';
const PACKAGE_VERSION = '0.2.12';
/**
* Whether this class has been initialized

View File

@ -5,6 +5,22 @@ 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).
## [1.17.25] - 2022-11-08
### Changed
- Updated package dependencies. [#27289]
## [1.17.24] - 2022-11-01
### Changed
- Updated package dependencies.
## [1.17.23] - 2022-10-13
### Changed
- Updated package dependencies. [#26791]
## [1.17.22] - 2022-10-05
### Changed
- Updated package dependencies. [#26568]
## [1.17.21] - 2022-08-25
### Changed
- Updated package dependencies. [#25814]
@ -260,6 +276,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Statically access asset tools
[1.17.25]: https://github.com/Automattic/jetpack-assets/compare/v1.17.24...v1.17.25
[1.17.24]: https://github.com/Automattic/jetpack-assets/compare/v1.17.23...v1.17.24
[1.17.23]: https://github.com/Automattic/jetpack-assets/compare/v1.17.22...v1.17.23
[1.17.22]: https://github.com/Automattic/jetpack-assets/compare/v1.17.21...v1.17.22
[1.17.21]: https://github.com/Automattic/jetpack-assets/compare/v1.17.20...v1.17.21
[1.17.20]: https://github.com/Automattic/jetpack-assets/compare/v1.17.19...v1.17.20
[1.17.19]: https://github.com/Automattic/jetpack-assets/compare/v1.17.18...v1.17.19

View File

@ -1 +1 @@
<?php return array('dependencies' => array('wp-i18n'), 'version' => 'ee939953aa2115e2ca59');
<?php return array('dependencies' => array('wp-i18n'), 'version' => 'e69db286c9c94bd98790');

View File

@ -5,6 +5,14 @@ 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).
## [1.11.0] - 2022-10-11
### Changed
- Integrate Stats package in Jetpack plugin [#26640]
## [1.10.0] - 2022-09-27
### Added
- Social: Added the option to configure if the plan information should be refreshed as the package is enabled. [#26294]
## [1.9.6] - 2022-08-26
### Changed
- Call ensure_options_$feature methods before the initialization
@ -144,6 +152,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Trying to add deterministic initialization.
[1.11.0]: https://github.com/Automattic/jetpack-config/compare/v1.10.0...v1.11.0
[1.10.0]: https://github.com/Automattic/jetpack-config/compare/v1.9.6...v1.10.0
[1.9.6]: https://github.com/Automattic/jetpack-config/compare/v1.9.5...v1.9.6
[1.9.5]: https://github.com/Automattic/jetpack-config/compare/v1.9.4...v1.9.5
[1.9.4]: https://github.com/Automattic/jetpack-config/compare/v1.9.3...v1.9.4

View File

@ -22,7 +22,7 @@
"link-template": "https://github.com/Automattic/jetpack-config/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "1.9.x-dev"
"dev-trunk": "1.11.x-dev"
}
}
}

View File

@ -19,6 +19,7 @@ use Automattic\Jetpack\JITMS\JITM as JITMS_JITM;
use Automattic\Jetpack\Post_List\Post_List as Post_List;
use Automattic\Jetpack\Publicize\Publicize_Setup as Publicize_Setup;
use Automattic\Jetpack\Search\Initializer as Jetpack_Search_Main;
use Automattic\Jetpack\Stats\Main as Stats_Main;
use Automattic\Jetpack\Sync\Main as Sync_Main;
use Automattic\Jetpack\VideoPress\Initializer as VideoPress_Pkg_Initializer;
use Automattic\Jetpack\Waf\Waf_Initializer as Jetpack_Waf_Main;
@ -49,6 +50,7 @@ class Config {
'wordads' => false,
'waf' => false,
'videopress' => false,
'stats' => false,
);
/**
@ -146,6 +148,9 @@ class Config {
if ( $this->config['videopress'] ) {
$this->ensure_class( 'Automattic\Jetpack\VideoPress\Initializer' ) && $this->ensure_feature( 'videopress' );
}
if ( $this->config['stats'] ) {
$this->ensure_class( 'Automattic\Jetpack\Stats\Main' ) && $this->ensure_feature( 'stats' );
}
}
/**
@ -269,6 +274,17 @@ class Config {
return true;
}
/**
* Handles Publicize options.
*/
protected function ensure_options_publicize() {
$options = $this->get_feature_options( 'publicize' );
if ( ! empty( $options['force_refresh'] ) ) {
Publicize_Setup::$refresh_plan_info = true;
}
}
/**
* Enables WordAds.
*/
@ -293,6 +309,14 @@ class Config {
return true;
}
/**
* Enables Stats.
*/
protected function enable_stats() {
Stats_Main::init();
return true;
}
/**
* Handles VideoPress options
*/

View File

@ -5,6 +5,49 @@ 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).
## [1.46.3] - 2022-11-08
### Changed
- Updated package dependencies. [#27289]
## [1.46.2] - 2022-11-07
### Changed
- Updated package dependencies. [#27278]
## [1.46.1] - 2022-11-01
### Changed
- Updated package dependencies.
## [1.46.0] - 2022-10-25
### Changed
- Use blog token to unlink users from WPCOM. [#26705]
## [1.45.5] - 2022-10-25
### Added
- Connection: expose BlogId in the global state [#26978]
## [1.45.4] - 2022-10-13
### Changed
- Updated package dependencies. [#26791]
## [1.45.3] - 2022-10-05
### Changed
- Updated package dependencies. [#26568]
### Fixed
- Clean connection errors after successful blog token restore. [#26489]
## [1.45.2] - 2022-09-21
### Fixed
- Check request body error type before creating wp error. [#26304]
## [1.45.1] - 2022-09-20
### Fixed
- Rename the initial state key to ensure compatibility with the JS package. [#26259]
## [1.45.0] - 2022-09-08
### Changed
- Add option to JP options [#25979]
## [1.44.0] - 2022-08-29
### Added
- Connection: verify REST API errors.
@ -678,6 +721,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Separate the connection library into its own package.
[1.46.3]: https://github.com/Automattic/jetpack-connection/compare/v1.46.2...v1.46.3
[1.46.2]: https://github.com/Automattic/jetpack-connection/compare/v1.46.1...v1.46.2
[1.46.1]: https://github.com/Automattic/jetpack-connection/compare/v1.46.0...v1.46.1
[1.46.0]: https://github.com/Automattic/jetpack-connection/compare/v1.45.5...v1.46.0
[1.45.5]: https://github.com/Automattic/jetpack-connection/compare/v1.45.4...v1.45.5
[1.45.4]: https://github.com/Automattic/jetpack-connection/compare/v1.45.3...v1.45.4
[1.45.3]: https://github.com/Automattic/jetpack-connection/compare/v1.45.2...v1.45.3
[1.45.2]: https://github.com/Automattic/jetpack-connection/compare/v1.45.1...v1.45.2
[1.45.1]: https://github.com/Automattic/jetpack-connection/compare/v1.45.0...v1.45.1
[1.45.0]: https://github.com/Automattic/jetpack-connection/compare/v1.44.0...v1.45.0
[1.44.0]: https://github.com/Automattic/jetpack-connection/compare/v1.43.1...v1.44.0
[1.43.1]: https://github.com/Automattic/jetpack-connection/compare/v1.43.0...v1.43.1
[1.43.0]: https://github.com/Automattic/jetpack-connection/compare/v1.42.0...v1.43.0

View File

@ -8,7 +8,7 @@
"automattic/jetpack-admin-ui": "^0.2",
"automattic/jetpack-constants": "^1.6",
"automattic/jetpack-roles": "^1.4",
"automattic/jetpack-status": "^1.14",
"automattic/jetpack-status": "^1.15",
"automattic/jetpack-redirect": "^1.7"
},
"require-dev": {
@ -34,7 +34,8 @@
"phpunit": [
"./vendor/phpunit/phpunit/phpunit --colors=always"
],
"post-update-cmd": "php -r \"copy('vendor/automattic/wordbless/src/dbless-wpdb.php', 'wordpress/wp-content/db.php');\"",
"post-install-cmd": "WorDBless\\Composer\\InstallDropin::copy",
"post-update-cmd": "WorDBless\\Composer\\InstallDropin::copy",
"test-coverage": [
"php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/clover.xml\""
],
@ -55,7 +56,7 @@
"link-template": "https://github.com/Automattic/jetpack-connection/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "1.44.x-dev"
"dev-trunk": "1.46.x-dev"
}
},
"config": {

View File

@ -1 +1 @@
<?php return array('dependencies' => array(), 'version' => 'd9dbf909a3d10fb26f39');
<?php return array('dependencies' => array(), 'version' => 'a96178e4d62fb695caa0');

View File

@ -1 +1 @@
(()=>{var e={775:e=>{let n;window._tkq=window._tkq||[];const t=console.error;const o={initialize:function(e,n){o.setUser(e,n),o.identifyUser()},mc:{bumpStat:function(e,n){const t=function(e,n){let t="";if("object"==typeof e)for(const n in e)t+="&x_"+encodeURIComponent(n)+"="+encodeURIComponent(e[n]);else t="&x_"+encodeURIComponent(e)+"="+encodeURIComponent(n);return t}(e,n);(new Image).src=document.location.protocol+"//pixel.wp.com/g.gif?v=wpcom-no-pv"+t+"&t="+Math.random()}},tracks:{recordEvent:function(e,n){n=n||{},0===e.indexOf("jetpack_")?window._tkq.push(["recordEvent",e,n]):t('- Event name must be prefixed by "jetpack_"')},recordPageView:function(e){o.tracks.recordEvent("jetpack_page_view",{path:e})}},setUser:function(e,t){n={ID:e,username:t}},identifyUser:function(){n&&window._tkq.push(["identifyUser",n.ID,n.username])},clearedIdentity:function(){window._tkq.push(["clearIdentity"])}};e.exports=o}},n={};var t=function t(o){var r=n[o];if(void 0!==r)return r.exports;var i=n[o]={exports:{}};return e[o](i,i.exports,t),i.exports}(775);window.analytics=t})();
!function(){var e={775:function(e){let n;window._tkq=window._tkq||[];const t=console.error;const o={initialize:function(e,n){o.setUser(e,n),o.identifyUser()},mc:{bumpStat:function(e,n){const t=function(e,n){let t="";if("object"==typeof e)for(const n in e)t+="&x_"+encodeURIComponent(n)+"="+encodeURIComponent(e[n]);else t="&x_"+encodeURIComponent(e)+"="+encodeURIComponent(n);return t}(e,n);(new Image).src=document.location.protocol+"//pixel.wp.com/g.gif?v=wpcom-no-pv"+t+"&t="+Math.random()}},tracks:{recordEvent:function(e,n){n=n||{},0===e.indexOf("jetpack_")?window._tkq.push(["recordEvent",e,n]):t('- Event name must be prefixed by "jetpack_"')},recordPageView:function(e){o.tracks.recordEvent("jetpack_page_view",{path:e})}},setUser:function(e,t){n={ID:e,username:t}},identifyUser:function(){n&&window._tkq.push(["identifyUser",n.ID,n.username])},clearedIdentity:function(){window._tkq.push(["clearIdentity"])}};e.exports=o}},n={};var t=function t(o){var r=n[o];if(void 0!==r)return r.exports;var i=n[o]={exports:{}};return e[o](i,i.exports,t),i.exports}(775);window.analytics=t}();

View File

@ -127,8 +127,7 @@ class Jetpack_Options {
'partner_coupon', // (string) A Jetpack partner issued coupon to promote a sale together with Jetpack.
'partner_coupon_added', // (string) A date for when `partner_coupon` was added, so we can auto-purge after a certain time interval.
'dismissed_backup_review_restore', // (bool) Determines if the component review request is dismissed for successful restore requests.
'dismissed_backup_review_time_based', // (bool) Determines if the component review request is dismissed for subscription time based requests.
'dismissed_backup_review_backups', // (bool) Determines if the component review request is dismissed for successful backup requests.
);
}

View File

@ -710,7 +710,7 @@ class Error_Handler {
}
$body = json_decode( $body_raw, true );
if ( empty( $body['error'] ) ) {
if ( empty( $body['error'] ) || ( ! is_string( $body['error'] ) && ! is_int( $body['error'] ) ) ) {
return;
}

View File

@ -30,15 +30,15 @@ class Initial_State {
global $wp_version;
return array(
'apiRoot' => esc_url_raw( rest_url() ),
'apiNonce' => wp_create_nonce( 'wp_rest' ),
'registrationNonce' => wp_create_nonce( 'jetpack-registration-nonce' ),
'connectionStatus' => REST_Connector::connection_status( false ),
'userConnectionData' => REST_Connector::get_user_connection_data( false ),
'connectedPlugins' => REST_Connector::get_connection_plugins( false ),
'wpVersion' => $wp_version,
'siteSuffix' => ( new Status() )->get_site_suffix(),
'connectionVerifiedErrors' => Error_Handler::get_instance()->get_verified_errors(),
'apiRoot' => esc_url_raw( rest_url() ),
'apiNonce' => wp_create_nonce( 'wp_rest' ),
'registrationNonce' => wp_create_nonce( 'jetpack-registration-nonce' ),
'connectionStatus' => REST_Connector::connection_status( false ),
'userConnectionData' => REST_Connector::get_user_connection_data( false ),
'connectedPlugins' => REST_Connector::get_connection_plugins( false ),
'wpVersion' => $wp_version,
'siteSuffix' => ( new Status() )->get_site_suffix(),
'connectionErrors' => Error_Handler::get_instance()->get_verified_errors(),
);
}

View File

@ -15,6 +15,7 @@ use Automattic\Jetpack\Status;
use Automattic\Jetpack\Terms_Of_Service;
use Automattic\Jetpack\Tracking;
use Jetpack_IXR_Client;
use Jetpack_Options;
use WP_Error;
use WP_User;
@ -831,7 +832,12 @@ class Manager {
* @return Boolean Whether the disconnection of the user was successful.
*/
public function disconnect_user( $user_id = null, $can_overwrite_primary_user = false, $force_disconnect_locally = false ) {
$user_id = empty( $user_id ) ? get_current_user_id() : (int) $user_id;
$user_id = empty( $user_id ) ? get_current_user_id() : (int) $user_id;
$is_primary_user = Jetpack_Options::get_option( 'master_user' ) === $user_id;
if ( $is_primary_user && ! $can_overwrite_primary_user ) {
return false;
}
// Attempt to disconnect the user from WordPress.com.
$is_disconnected_from_wpcom = $this->unlink_user_from_wpcom( $user_id );
@ -839,7 +845,7 @@ class Manager {
$is_disconnected_locally = false;
if ( $is_disconnected_from_wpcom || $force_disconnect_locally ) {
// Disconnect the user locally.
$is_disconnected_locally = $this->get_tokens()->disconnect_user( $user_id, $can_overwrite_primary_user );
$is_disconnected_locally = $this->get_tokens()->disconnect_user( $user_id );
if ( $is_disconnected_locally ) {
// Delete cached connected user data.
@ -855,6 +861,10 @@ class Manager {
* @param int $user_id The current user's ID.
*/
do_action( 'jetpack_unlinked_user', $user_id );
if ( $is_primary_user ) {
Jetpack_Options::delete_option( 'master_user' );
}
}
}
@ -864,15 +874,13 @@ class Manager {
/**
* Request to wpcom for a user to be unlinked from their WordPress.com account
*
* @access public
* @param int $user_id The user identifier.
*
* @param Integer $user_id the user identifier.
*
* @return Boolean Whether the disconnection of the user was successful.
* @return bool Whether the disconnection of the user was successful.
*/
public function unlink_user_from_wpcom( $user_id ) {
// Attempt to disconnect the user from WordPress.com.
$xml = new Jetpack_IXR_Client( compact( 'user_id' ) );
$xml = new Jetpack_IXR_Client();
$xml->query( 'jetpack.unlink_user', $user_id );
if ( $xml->isError() ) {
@ -2416,6 +2424,8 @@ class Manager {
return new WP_Error( 'jetpack_secret', '', $code );
}
Error_Handler::get_instance()->delete_all_errors();
return $this->get_tokens()->update_blog_token( (string) $json->jetpack_secret );
}

View File

@ -12,7 +12,7 @@ namespace Automattic\Jetpack\Connection;
*/
class Package_Version {
const PACKAGE_VERSION = '1.44.0';
const PACKAGE_VERSION = '1.46.3';
const PACKAGE_SLUG = 'connection';

View File

@ -432,6 +432,8 @@ class REST_Connector {
* @return \WP_REST_Response|array
*/
public static function get_user_connection_data( $rest_response = true ) {
$blog_id = \Jetpack_Options::get_option( 'id' );
$connection = new Manager();
$current_user = wp_get_current_user();
@ -464,6 +466,7 @@ class REST_Connector {
'isMaster' => $is_master_user,
'username' => $current_user->user_login,
'id' => $current_user->ID,
'blogId' => $blog_id,
'wpcomUser' => $wpcom_user_data,
'gravatar' => get_avatar_url( $current_user->ID, 64, 'mm', '', array( 'force_display' => true ) ),
'permissions' => array(

View File

@ -487,18 +487,19 @@ class Tokens {
*
* @todo Refactor to properly load the XMLRPC client independently.
*
* @param Integer $user_id the user identifier.
* @param bool $can_overwrite_primary_user Allow for the primary user to be disconnected.
* @return Boolean Whether the disconnection of the user was successful.
* @param int $user_id The user identifier.
* @param bool|null $deprecated Deprecated.
*
* @return bool Whether the disconnection of the user was successful.
*/
public function disconnect_user( $user_id, $can_overwrite_primary_user = false ) {
public function disconnect_user( $user_id, $deprecated = null ) {
$tokens = $this->get_user_tokens();
if ( ! $tokens ) {
return false;
}
if ( Jetpack_Options::get_option( 'master_user' ) === $user_id && ! $can_overwrite_primary_user ) {
return false;
if ( null !== $deprecated ) {
_deprecated_argument( __METHOD__, '1.46.0', 'Parameter $can_overwrite_primary_user is deprecated' );
}
if ( ! isset( $tokens[ $user_id ] ) ) {

View File

@ -5,6 +5,14 @@ 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).
## [1.4.20] - 2022-11-07
### Fixed
- Ensure that User_Agent is loaded in environments without autoload enabled. (e.g.: WordPress.com and Super Cache) [#27223]
## [1.4.19] - 2022-10-25
### Changed
- Update `wp_unslash` wrapper to possibly run on wpcom before WordPress is loaded. [#26971]
## [1.4.18] - 2022-07-26
### Changed
- Updated package dependencies. [#25158]
@ -124,6 +132,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Moving jetpack_is_mobile into a package
[1.4.20]: https://github.com/Automattic/jetpack-device-detection/compare/v1.4.19...v1.4.20
[1.4.19]: https://github.com/Automattic/jetpack-device-detection/compare/v1.4.18...v1.4.19
[1.4.18]: https://github.com/Automattic/jetpack-device-detection/compare/v1.4.17...v1.4.18
[1.4.17]: https://github.com/Automattic/jetpack-device-detection/compare/v1.4.16...v1.4.17
[1.4.16]: https://github.com/Automattic/jetpack-device-detection/compare/v1.4.15...v1.4.16

View File

@ -8,6 +8,7 @@
namespace Automattic\Jetpack;
require_once __DIR__ . '/functions.php';
require_once __DIR__ . '/class-user-agent-info.php';
use Automattic\Jetpack\Device_Detection\User_Agent_Info;
use function Automattic\Jetpack\Device_Detection\wp_unslash;

View File

@ -13,14 +13,18 @@ namespace Automattic\Jetpack\Device_Detection;
* Even though PHP itself dropped the option to add slashes to superglobals a decade ago,
* WordPress still does it through some misguided extreme backwards compatibility. 🙄
*
* If WordPress's function exists, assume it needs to be called. If not, assume it doesn't.
* If WordPress's function exists, assume it needs to be called.
* Else if on WordPress.com, do a simplified version because we're running really early.
* Else, assume it's not needed.
*
* @param string|array $value String or array of data to unslash.
* @return string|array Possibly unslashed $value.
* @param string $value String of data to unslash.
* @return string Possibly unslashed $value.
*/
function wp_unslash( $value ) {
if ( function_exists( '\\wp_unslash' ) ) {
return \wp_unslash( $value );
} elseif ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
return stripslashes( $value );
} else {
return $value;
}

View File

@ -5,6 +5,42 @@ 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).
## [0.8.31] - 2022-11-10
### Changed
- Updated package dependencies. [#27319]
## [0.8.30] - 2022-11-08
### Changed
- Updated package dependencies. [#27289]
## [0.8.29] - 2022-11-07
### Changed
- Updated package dependencies. [#27278]
## [0.8.28] - 2022-11-01
### Changed
- Updated package dependencies. [#27196]
## [0.8.27] - 2022-10-25
### Changed
- Updated package dependencies. [#26705]
## [0.8.26] - 2022-10-13
### Changed
- Updated package dependencies. [#26791]
## [0.8.25] - 2022-10-05
### Changed
- Updated package dependencies. [#26568]
## [0.8.24] - 2022-09-20
### Changed
- Updated package dependencies.
## [0.8.23] - 2022-09-08
### Changed
- Updated package dependencies.
## [0.8.22] - 2022-08-29
### Changed
- Updated package dependencies.
@ -264,6 +300,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Updated package dependencies.
- Use Connection/Urls for home_url and site_url functions migrated from Sync.
[0.8.31]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.30...v0.8.31
[0.8.30]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.29...v0.8.30
[0.8.29]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.28...v0.8.29
[0.8.28]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.27...v0.8.28
[0.8.27]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.26...v0.8.27
[0.8.26]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.25...v0.8.26
[0.8.25]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.24...v0.8.25
[0.8.24]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.23...v0.8.24
[0.8.23]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.22...v0.8.23
[0.8.22]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.21...v0.8.22
[0.8.21]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.20...v0.8.21
[0.8.20]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.19...v0.8.20

View File

@ -1 +1 @@
<?php return array('dependencies' => array('react', 'react-dom', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-url'), 'version' => 'ec6be881467d41653072');
<?php return array('dependencies' => array('react', 'react-dom', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-url'), 'version' => '1621ca28578f7d06fec7');

View File

@ -4,9 +4,9 @@
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"automattic/jetpack-connection": "^1.44",
"automattic/jetpack-connection": "^1.46",
"automattic/jetpack-constants": "^1.6",
"automattic/jetpack-status": "^1.14",
"automattic/jetpack-status": "^1.15",
"automattic/jetpack-logo": "^1.5",
"automattic/jetpack-assets": "^1.17"
},
@ -36,7 +36,8 @@
"test-php": [
"@composer phpunit"
],
"post-update-cmd": "php -r \"copy('vendor/automattic/wordbless/src/dbless-wpdb.php', 'wordpress/wp-content/db.php');\"",
"post-install-cmd": "WorDBless\\Composer\\InstallDropin::copy",
"post-update-cmd": "WorDBless\\Composer\\InstallDropin::copy",
"watch": [
"Composer\\Config::disableProcessTimeout",
"pnpm run watch"

View File

@ -28,7 +28,7 @@ class Identity_Crisis {
/**
* Package Version
*/
const PACKAGE_VERSION = '0.8.22';
const PACKAGE_VERSION = '0.8.31';
/**
* Instance of the object.

View File

@ -5,6 +5,34 @@ 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).
## [2.2.33] - 2022-11-08
### Changed
- Updated package dependencies. [#27289]
## [2.2.32] - 2022-11-07
### Changed
- Updated package dependencies. [#27278]
## [2.2.31] - 2022-11-01
### Changed
- Updated package dependencies. [#27196]
## [2.2.30] - 2022-10-25
### Changed
- Updated package dependencies. [#26705]
## [2.2.29] - 2022-10-13
### Changed
- Updated package dependencies. [#26791]
## [2.2.28] - 2022-10-05
### Changed
- Updated package dependencies. [#26568]
## [2.2.27] - 2022-09-08
### Changed
- Updated package dependencies.
## [2.2.26] - 2022-08-29
### Changed
- Updated package dependencies.
@ -478,6 +506,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Update Jetpack to use new JITM package
[2.2.33]: https://github.com/Automattic/jetpack-jitm/compare/v2.2.32...v2.2.33
[2.2.32]: https://github.com/Automattic/jetpack-jitm/compare/v2.2.31...v2.2.32
[2.2.31]: https://github.com/Automattic/jetpack-jitm/compare/v2.2.30...v2.2.31
[2.2.30]: https://github.com/Automattic/jetpack-jitm/compare/v2.2.29...v2.2.30
[2.2.29]: https://github.com/Automattic/jetpack-jitm/compare/v2.2.28...v2.2.29
[2.2.28]: https://github.com/Automattic/jetpack-jitm/compare/v2.2.27...v2.2.28
[2.2.27]: https://github.com/Automattic/jetpack-jitm/compare/v2.2.26...v2.2.27
[2.2.26]: https://github.com/Automattic/jetpack-jitm/compare/v2.2.25...v2.2.26
[2.2.25]: https://github.com/Automattic/jetpack-jitm/compare/v2.2.24...v2.2.25
[2.2.24]: https://github.com/Automattic/jetpack-jitm/compare/v2.2.23...v2.2.24

View File

@ -1 +1 @@
<?php return array('dependencies' => array('wp-polyfill'), 'version' => '2844220d1ce117cbcaed');
<?php return array('dependencies' => array('wp-polyfill'), 'version' => '00af8e5432ff8e29efed');

View File

@ -1 +1 @@
(()=>{"use strict";jQuery(document).ready((function(t){var a={default:function(a){var e='<div class="jitm-card jitm-banner '+(a.CTA.message?"has-call-to-action":"")+" is-upgrade-premium "+a.content.classes+'" data-stats_url="'+a.jitm_stats_url+'">';if(e+='<div class="jitm-banner__content">',e+='<div class="jitm-banner__icon-plan">'+a.content.icon+"</div>",e+='<div class="jitm-banner__info">',e+='<div class="jitm-banner__title">'+a.content.message+"</div>",a.content.description&&""!==a.content.description){if(e+='<div class="jitm-banner__description">'+a.content.description,a.content.list.length>0){e+='<ul class="banner__list">';for(var i=0;i<a.content.list.length;i++){var n=a.content.list[i].item;a.content.list[i].url&&(n='<a href="'+a.content.list[i].url+'" target="_blank" rel="noopener noreferrer" data-module="'+a.feature_class+'" data-jptracks-name="nudge_item_click" data-jptracks-prop="jitm-'+a.id+'">'+n+"</a>"),e+='<li><svg class="gridicon gridicons-checkmark" height="16" width="16" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M9 19.414l-6.707-6.707 1.414-1.414L9 16.586 20.293 5.293l1.414 1.414" /></g></svg>'+n+"</li>"}}e+="</div>"}if(e+="</div>",e+="</div>",e+='<div class="jitm-banner__buttons_container">',a.activate_module&&(e+='<div class="jitm-banner__action" id="jitm-banner__activate">',e+='<a href="#" data-module="'+a.activate_module+'" type="button" class="jitm-button is-compact is-primary jptracks" data-jptracks-name="nudge_click" data-jptracks-prop="jitm-'+a.id+'-activate_module">'+window.jitm_config.activate_module_text+"</a>",e+="</div>"),a.CTA.message){var c="jitm-button is-compact jptracks";a.CTA.primary&&null===a.activate_module&&(c+=" is-primary");var o=a.CTA.ajax_action;e+='<div class="jitm-banner__action">',e+='<a href="'+(a.CTA.hasOwnProperty("link")&&a.CTA.link.length?a.CTA.link:a.url)+'" target="'+(!1===a.CTA.newWindow||o?"_self":"_blank")+'" rel="noopener noreferrer" title="'+a.CTA.message+'" data-module="'+a.feature_class+'" type="button" class="'+c+'" data-jptracks-name="nudge_click" data-jptracks-prop="jitm-'+a.id+'" '+(o?'data-ajax-action="'+o+'"':"")+">"+a.CTA.message+"</a>",e+="</div>"}return e+="</div>",a.is_dismissible&&(e+='<a href="#" data-module="'+a.feature_class+'" class="jitm-banner__dismiss"></a>'),t(e+="</div>")}},e=function(){t(".jetpack-jitm-message").each((function(){var e=t(this),i=e.data("message-path"),n=e.data("query"),c=e.data("redirect"),o=location.hash;"_dashboard"!==(o=o.replace(/#\//,"_"))&&(i=i.replace("toplevel_page_jetpack","toplevel_page_jetpack"+o));var r=!!t(".jetpack-logo__masthead").length;t.get(window.jitm_config.api_root+"jetpack/v4/jitm",{message_path:i,query:n,full_jp_logo_exists:r,_wpnonce:e.data("nonce")}).then((function(i){"object"==typeof i&&i[1]&&(i=[i[1]]),0!==i.length&&i[0].content&&function(e,i,n){var c;(c=i.template)&&a[c]||(c="default"),i.url=i.url+"&redirect="+n;var o,r=a[c](i);r.find(".jitm-banner__dismiss").on("click",(o=r,function(a){a.preventDefault(),o.hide(),t.ajax({url:window.jitm_config.api_root+"jetpack/v4/jitm",method:"POST",beforeSend:function(t){t.setRequestHeader("X-WP-Nonce",window.jitm_config.nonce)},data:{id:i.id,feature_class:i.feature_class}})})),t("#jp-admin-notices").length>0?(e.innerHTML=r,t("#jp-admin-notices").find(".jitm-card")&&t(".jitm-card").replaceWith(r),r.prependTo(t("#jp-admin-notices"))):e.replaceWith(r),r.find("#jitm-banner__activate a").on("click",(function(){var a=t(this);if(a.attr("disabled"))return!1;t.ajax({url:window.jitm_config.api_root+"jetpack/v4/module/"+a.data("module")+"/active",method:"POST",beforeSend:function(a){a.setRequestHeader("X-WP-Nonce",e.data("nonce")),t("#jitm-banner__activate a").text(window.jitm_config.activating_module_text),t("#jitm-banner__activate a").attr("disabled",!0)}}).done((function(){t("#jitm-banner__activate a").text(window.jitm_config.activated_module_text),t("#jitm-banner__activate a").attr("disabled",!0),setTimeout((function(){r.fadeOut("slow")}),2e3)}))})),r.find(".jitm-button[data-ajax-action]").on("click",(function(a){a.preventDefault();var i=t(this);return i.attr("disabled",!0),t.post(window.ajaxurl,{action:i.data("ajax-action"),_nonce:e.data("ajax-nonce")}).done((function(){r.fadeOut("slow")})).fail((function(){i.attr("disabled",!1)})),!1}))}(e,i[0],c)}))}))};e(),t(window).on("hashchange",(function(t){if(t.originalEvent.newURL.indexOf("jetpack#/")>=0){var a=document.querySelector(".jitm-card");a&&a.remove(),e()}}))}))})();
!function(){"use strict";jQuery(document).ready((function(t){var a={default:function(a){var e='<div class="jitm-card jitm-banner '+(a.CTA.message?"has-call-to-action":"")+" is-upgrade-premium "+a.content.classes+'" data-stats_url="'+a.jitm_stats_url+'">';if(e+='<div class="jitm-banner__content">',e+='<div class="jitm-banner__icon-plan">'+a.content.icon+"</div>",e+='<div class="jitm-banner__info">',e+='<div class="jitm-banner__title">'+a.content.message+"</div>",a.content.description&&""!==a.content.description){if(e+='<div class="jitm-banner__description">'+a.content.description,a.content.list.length>0){e+='<ul class="banner__list">';for(var i=0;i<a.content.list.length;i++){var n=a.content.list[i].item;a.content.list[i].url&&(n='<a href="'+a.content.list[i].url+'" target="_blank" rel="noopener noreferrer" data-module="'+a.feature_class+'" data-jptracks-name="nudge_item_click" data-jptracks-prop="jitm-'+a.id+'">'+n+"</a>"),e+='<li><svg class="gridicon gridicons-checkmark" height="16" width="16" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M9 19.414l-6.707-6.707 1.414-1.414L9 16.586 20.293 5.293l1.414 1.414" /></g></svg>'+n+"</li>"}}e+="</div>"}if(e+="</div>",e+="</div>",e+='<div class="jitm-banner__buttons_container">',a.activate_module&&(e+='<div class="jitm-banner__action" id="jitm-banner__activate">',e+='<a href="#" data-module="'+a.activate_module+'" type="button" class="jitm-button is-compact is-primary jptracks" data-jptracks-name="nudge_click" data-jptracks-prop="jitm-'+a.id+'-activate_module">'+window.jitm_config.activate_module_text+"</a>",e+="</div>"),a.CTA.message){var c="jitm-button is-compact jptracks";a.CTA.primary&&null===a.activate_module&&(c+=" is-primary");var o=a.CTA.ajax_action;e+='<div class="jitm-banner__action">',e+='<a href="'+(a.CTA.hasOwnProperty("link")&&a.CTA.link.length?a.CTA.link:a.url)+'" target="'+(!1===a.CTA.newWindow||o?"_self":"_blank")+'" rel="noopener noreferrer" title="'+a.CTA.message+'" data-module="'+a.feature_class+'" type="button" class="'+c+'" data-jptracks-name="nudge_click" data-jptracks-prop="jitm-'+a.id+'" '+(o?'data-ajax-action="'+o+'"':"")+">"+a.CTA.message+"</a>",e+="</div>"}return e+="</div>",a.is_dismissible&&(e+='<a href="#" data-module="'+a.feature_class+'" class="jitm-banner__dismiss"></a>'),t(e+="</div>")}},e=function(){t(".jetpack-jitm-message").each((function(){var e=t(this),i=e.data("message-path"),n=e.data("query"),c=e.data("redirect"),o=location.hash;"_dashboard"!==(o=o.replace(/#\//,"_"))&&(i=i.replace("toplevel_page_jetpack","toplevel_page_jetpack"+o));var r=!!t(".jetpack-logo__masthead").length;t.get(window.jitm_config.api_root+"jetpack/v4/jitm",{message_path:i,query:n,full_jp_logo_exists:r,_wpnonce:e.data("nonce")}).then((function(i){"object"==typeof i&&i[1]&&(i=[i[1]]),0!==i.length&&i[0].content&&function(e,i,n){var c;(c=i.template)&&a[c]||(c="default"),i.url=i.url+"&redirect="+n;var o,r=a[c](i);r.find(".jitm-banner__dismiss").on("click",(o=r,function(a){a.preventDefault(),o.hide(),t.ajax({url:window.jitm_config.api_root+"jetpack/v4/jitm",method:"POST",beforeSend:function(t){t.setRequestHeader("X-WP-Nonce",window.jitm_config.nonce)},data:{id:i.id,feature_class:i.feature_class}})})),t("#jp-admin-notices").length>0?(e.innerHTML=r,t("#jp-admin-notices").find(".jitm-card")&&t(".jitm-card").replaceWith(r),r.prependTo(t("#jp-admin-notices"))):e.replaceWith(r),r.find("#jitm-banner__activate a").on("click",(function(){var a=t(this);if(a.attr("disabled"))return!1;t.ajax({url:window.jitm_config.api_root+"jetpack/v4/module/"+a.data("module")+"/active",method:"POST",beforeSend:function(a){a.setRequestHeader("X-WP-Nonce",e.data("nonce")),t("#jitm-banner__activate a").text(window.jitm_config.activating_module_text),t("#jitm-banner__activate a").attr("disabled",!0)}}).done((function(){t("#jitm-banner__activate a").text(window.jitm_config.activated_module_text),t("#jitm-banner__activate a").attr("disabled",!0),setTimeout((function(){r.fadeOut("slow")}),2e3)}))})),r.find(".jitm-button[data-ajax-action]").on("click",(function(a){a.preventDefault();var i=t(this);return i.attr("disabled",!0),t.post(window.ajaxurl,{action:i.data("ajax-action"),_nonce:e.data("ajax-nonce")}).done((function(){r.fadeOut("slow")})).fail((function(){i.attr("disabled",!1)})),!1}))}(e,i[0],c)}))}))};e(),t(window).on("hashchange",(function(t){if(t.originalEvent.newURL.indexOf("jetpack#/")>=0){var a=document.querySelector(".jitm-card");a&&a.remove(),e()}}))}))}();

View File

@ -6,12 +6,12 @@
"require": {
"automattic/jetpack-a8c-mc-stats": "^1.4",
"automattic/jetpack-assets": "^1.17",
"automattic/jetpack-connection": "^1.44",
"automattic/jetpack-connection": "^1.46",
"automattic/jetpack-device-detection": "^1.4",
"automattic/jetpack-logo": "^1.5",
"automattic/jetpack-partner": "^1.7",
"automattic/jetpack-redirect": "^1.7",
"automattic/jetpack-status": "^1.14"
"automattic/jetpack-status": "^1.15"
},
"require-dev": {
"brain/monkey": "2.6.1",

View File

@ -20,7 +20,7 @@ use Automattic\Jetpack\Status;
*/
class JITM {
const PACKAGE_VERSION = '2.2.26';
const PACKAGE_VERSION = '2.2.33';
/**
* The configuration method that is called from the jetpack-config package.

View File

@ -5,6 +5,18 @@ 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).
## [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.
@ -206,6 +218,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Licensing: Add support for Jetpack licenses
[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

View File

@ -4,7 +4,7 @@
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"automattic/jetpack-connection": "^1.44"
"automattic/jetpack-connection": "^1.46"
},
"require-dev": {
"automattic/wordbless": "@dev",
@ -20,7 +20,8 @@
"phpunit": [
"./vendor/phpunit/phpunit/phpunit --colors=always"
],
"post-update-cmd": "php -r \"copy('vendor/automattic/wordbless/src/dbless-wpdb.php', 'wordpress/wp-content/db.php');\"",
"post-install-cmd": "WorDBless\\Composer\\InstallDropin::copy",
"post-update-cmd": "WorDBless\\Composer\\InstallDropin::copy",
"test-coverage": [
"php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/clover.xml\""
],

View File

@ -5,6 +5,10 @@ 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).
## [1.5.18] - 2022-11-07
### Changed
- Updated package dependencies.
## [1.5.17] - 2022-07-26
### Changed
- Updated package dependencies. [#25158]
@ -126,6 +130,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Packages: Add a basic Jetpack Logo package
[1.5.18]: https://github.com/Automattic/jetpack-logo/compare/v1.5.17...v1.5.18
[1.5.17]: https://github.com/Automattic/jetpack-logo/compare/v1.5.16...v1.5.17
[1.5.16]: https://github.com/Automattic/jetpack-logo/compare/v1.5.15...v1.5.16
[1.5.15]: https://github.com/Automattic/jetpack-logo/compare/v1.5.14...v1.5.15

View File

@ -5,6 +5,95 @@ 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).
## [2.4.0] - 2022-11-17
### Added
- Added Jetpack Protect to My Jetpack. [#26069]
### Changed
- Updated package dependencies. [#26736]
## [2.3.5] - 2022-11-10
### Changed
- Updated package dependencies. [#27319]
## [2.3.4] - 2022-11-08
### Changed
- Updated package dependencies. [#27289]
## [2.3.3] - 2022-11-01
### Changed
- Updated package dependencies. [#27196]
## [2.3.2] - 2022-10-25
### Changed
- Updated package dependencies. [#26705]
## [2.3.1] - 2022-10-25
### Added
- Add a flag to indicate if the pricing is introductory with product price [#26982]
- My Jetpack: Support trial [#27033]
### Changed
- Search: now support 38 languages [#27025]
### Fixed
- Don't show old price when it's the same as new one [#27015]
- Search: check if free plan and new pricing is active using wpcom API [#27016]
## [2.3.0] - 2022-10-19
### Added
- Added support for free product and added free product for Search [#26808]
### Changed
- Updated package dependencies. [#26883]
## [2.2.3] - 2022-10-17
### Changed
- Updated package dependencies. [#26851]
## [2.2.2] - 2022-10-13
### Changed
- Updated package dependencies. [#26790]
## [2.2.1] - 2022-10-11
### Changed
- Updated package dependencies. [#25973]
## [2.2.0] - 2022-10-05
### Added
- Integrate the new connection error message React component into My Jetpack. [#26485]
- Search: add post type breakdown endpoint [#26463]
- Trigger restore connection flow. [#26489]
### Changed
- Updated package dependencies. [#26457]
## [2.1.1] - 2022-09-27
### Changed
- Updated package dependencies.
## [2.1.0] - 2022-09-20
### Added
- Added the ConnectionErrorNotice React component. [#26259]
### Changed
- Updated package dependencies.
### Fixed
- Fixed the tests for the Module_Product class by creating and using a sample, test-only module product class as the test subject instead of relying on Videopress or other concrete products. [#26227]
## [2.0.5] - 2022-09-08
### Changed
- Change VideoPress into a Hybrid product in My Jetpack [#25954]
- Updated package dependencies.
## [2.0.4] - 2022-08-31
### Added
- Allow plugins to override a product class. [#25891]
### Changed
- Updated package dependencies. [#25856]
## [2.0.3] - 2022-08-29
### Changed
- Updated package dependencies.
@ -579,6 +668,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Created package
[2.4.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.3.5...2.4.0
[2.3.5]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.3.4...2.3.5
[2.3.4]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.3.3...2.3.4
[2.3.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.3.2...2.3.3
[2.3.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.3.1...2.3.2
[2.3.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.3.0...2.3.1
[2.3.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.2.3...2.3.0
[2.2.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.2.2...2.2.3
[2.2.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.2.1...2.2.2
[2.2.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.2.0...2.2.1
[2.2.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.1.1...2.2.0
[2.1.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.1.0...2.1.1
[2.1.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.0.5...2.1.0
[2.0.5]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.0.4...2.0.5
[2.0.4]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.0.3...2.0.4
[2.0.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.0.2...2.0.3
[2.0.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.0.1...2.0.2
[2.0.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.0.0...2.0.1

View File

@ -1 +1 @@
<?php return array('dependencies' => array('react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-url'), 'version' => 'ef234302efce2d8f505a');
<?php return array('dependencies' => array('react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-url'), 'version' => 'c8b0206144b22212a975');

View File

@ -6,7 +6,7 @@
"require": {
"automattic/jetpack-admin-ui": "^0.2",
"automattic/jetpack-assets": "^1.17",
"automattic/jetpack-connection": "^1.44",
"automattic/jetpack-connection": "^1.46",
"automattic/jetpack-jitm": "^2.2",
"automattic/jetpack-licensing": "^1.7",
"automattic/jetpack-plugins-installer": "^0.2",
@ -52,7 +52,8 @@
"Composer\\Config::disableProcessTimeout",
"pnpm run watch"
],
"post-update-cmd": "php -r \"copy('vendor/automattic/wordbless/src/dbless-wpdb.php', 'wordpress/wp-content/db.php');\""
"post-install-cmd": "WorDBless\\Composer\\InstallDropin::copy",
"post-update-cmd": "WorDBless\\Composer\\InstallDropin::copy"
},
"minimum-stability": "dev",
"prefer-stable": true,
@ -64,7 +65,7 @@
"link-template": "https://github.com/Automattic/jetpack-my-jetpack/compare/${old}...${new}"
},
"branch-alias": {
"dev-trunk": "2.0.x-dev"
"dev-trunk": "2.4.x-dev"
},
"version-constants": {
"::PACKAGE_VERSION": "src/class-initializer.php"

View File

@ -15,6 +15,7 @@ use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication;
use Automattic\Jetpack\JITMS\JITM as JITM;
use Automattic\Jetpack\Licensing;
use Automattic\Jetpack\Plugins_Installer;
use Automattic\Jetpack\Status as Status;
use Automattic\Jetpack\Terms_Of_Service;
use Automattic\Jetpack\Tracking;
@ -29,7 +30,7 @@ class Initializer {
*
* @var string
*/
const PACKAGE_VERSION = '2.0.3';
const PACKAGE_VERSION = '2.4.0';
/**
* Initialize My Jetapack
@ -159,6 +160,7 @@ class Initializer {
'purchases' => array(
'items' => array(),
),
'plugins' => Plugins_Installer::get_plugins(),
'myJetpackUrl' => admin_url( 'admin.php?page=my-jetpack' ),
'topJetpackMenuItemUrl' => Admin_Menu::get_top_level_menu_item_url(),
'siteSuffix' => ( new Status() )->get_site_suffix(),

View File

@ -17,22 +17,49 @@ class Products {
*
* Here's where all the existing Products are registered
*
* @throws \Exception If the result of a filter has invalid classes.
* @return array List of class names
*/
public static function get_products_classes() {
return array(
Products\Anti_Spam::class,
Products\Backup::class,
Products\Boost::class,
Products\Crm::class,
Products\Extras::class,
Products\Scan::class,
Products\Search::class,
Products\Social::class,
Products\Security::class,
Products\Protect::class,
Products\Videopress::class,
$classes = array(
'anti-spam' => Products\Anti_Spam::class,
'backup' => Products\Backup::class,
'boost' => Products\Boost::class,
'crm' => Products\Crm::class,
'extras' => Products\Extras::class,
'scan' => Products\Scan::class,
'search' => Products\Search::class,
'social' => Products\Social::class,
'security' => Products\Security::class,
'protect' => Products\Protect::class,
'videopress' => Products\Videopress::class,
);
/**
* This filter allows plugin to override the Product class of a given product. The new class must be a child class of the default one declared in My Jetpack
*
* For example, a stand-alone plugin could overwrite its product class to control specific behavior of the product in the My Jetpack page after it is active without having to commit changes to the My Jetpack package:
*
* add_filter( 'my_jetpack_products_classes', function( $classes ) {
* $classes['my_plugin'] = 'My_Plugin'; // a class that extends the original one declared in the My Jetpack package.
* return $classes
* } );
*
* @param array $classes An array where the keys are the product slugs and the values are the class names.
*/
$final_classes = apply_filters( 'my_jetpack_products_classes', $classes );
// Check that the classes are still child of the same original classes.
foreach ( (array) $final_classes as $slug => $final_class ) {
if ( $final_class === $classes[ $slug ] ) {
continue;
}
if ( ! class_exists( $final_class ) || ! is_subclass_of( $final_class, $classes[ $slug ] ) ) {
throw new \Exception( 'You can only overwrite a Product class with a child of the original class.' );
}
}
return $final_classes;
}
/**
@ -57,13 +84,24 @@ class Products {
* @return ?array
*/
public static function get_product( $product_slug ) {
foreach ( self::get_products_classes() as $class ) {
$p_slug = $class::$slug;
if ( $p_slug === $product_slug ) {
return $class::get_info();
}
$classes = self::get_products_classes();
if ( isset( $classes[ $product_slug ] ) ) {
return $classes[ $product_slug ]::get_info();
}
}
/**
* Get one product Class name
*
* @param string $product_slug The product slug.
*
* @return ?string
*/
public static function get_product_class( $product_slug ) {
$classes = self::get_products_classes();
if ( isset( $classes[ $product_slug ] ) ) {
return $classes[ $product_slug ];
}
return null;
}
/**
@ -72,11 +110,7 @@ class Products {
* @return array Product slugs array.
*/
public static function get_products_slugs() {
$slugs = array();
foreach ( self::get_products_classes() as $class ) {
$slugs[] = $class::$slug;
}
return $slugs;
return array_keys( self::get_products_classes() );
}
/**
@ -118,7 +152,7 @@ class Products {
'status' => array(
'title' => 'The product status',
'type' => 'string',
'enum' => array( 'active', 'inactive', 'plugin_absent', 'needs_purchase', 'error' ),
'enum' => array( 'active', 'inactive', 'plugin_absent', 'needs_purchase', 'needs_purchase_or_free', 'error' ),
),
'class' => array(
'title' => 'The product class handler',
@ -133,12 +167,16 @@ class Products {
* tied to the Products.
*/
public static function extend_plugins_action_links() {
Products\Backup::extend_plugin_action_links();
Products\Boost::extend_plugin_action_links();
Products\Crm::extend_plugin_action_links();
// Extend Jetpack plugin using Videopress instance.
Products\Videopress::extend_plugin_action_links();
$products = array(
'backup',
'boost',
'crm',
'videopress', // we use videopress here to add the plugin action to the Jetpack plugin itself
);
foreach ( $products as $product ) {
$class_name = self::get_product_class( $product );
$class_name::extend_plugin_action_links();
}
}
}

View File

@ -157,18 +157,21 @@ class Wpcom_Products {
return array();
}
$cost = $product->cost;
$discount_price = $cost;
$cost = $product->cost;
$discount_price = $cost;
$is_introductory_offer = false;
// Get/compute the discounted price.
if ( isset( $product->introductory_offer->cost_per_interval ) ) {
$discount_price = $product->introductory_offer->cost_per_interval;
$discount_price = $product->introductory_offer->cost_per_interval;
$is_introductory_offer = true;
}
$pricing = array(
'currency_code' => $product->currency_code,
'full_price' => $cost,
'discount_price' => $discount_price,
'currency_code' => $product->currency_code,
'full_price' => $cost,
'discount_price' => $discount_price,
'is_introductory_offer' => $is_introductory_offer,
);
return self::populate_with_discount( $product, $pricing, $discount_price );

View File

@ -76,7 +76,6 @@ abstract class Hybrid_Product extends Product {
/*
* Otherwise, activate Jetpack plugin.
* Silent mode True to avoid redirects.
*/
if ( static::is_jetpack_plugin_installed() ) {
return activate_plugin( static::get_installed_plugin_filename( 'jetpack' ) );
@ -95,7 +94,7 @@ abstract class Hybrid_Product extends Product {
if ( is_wp_error( $product_activation ) ) {
// If we failed to install the stand-alone plugin because the package was not found, let's try and install Jetpack plugin instead.
// This might happens, for example, while the stand-alone plugin was not released to the WP.org repository yet.
// This might happen, for example, while the stand-alone plugin was not released to the WP.org repository yet.
if ( 'no_package' === $product_activation->get_error_code() ) {
$product_activation = Plugins_Installer::install_plugin( self::JETPACK_PLUGIN_SLUG );
if ( ! is_wp_error( $product_activation ) ) {

View File

@ -222,6 +222,19 @@ abstract class Product {
return true;
}
/**
* Checks whether the product supports trial or not
*
* Returns true if it supports. Return false otherwise.
*
* Free products will always return false.
*
* @return boolean
*/
public static function has_trial_support() {
return false;
}
/**
* Checks whether product is a bundle.
*
@ -266,11 +279,19 @@ abstract class Product {
// We only consider missing user connection an error when the Product is active.
if ( static::$requires_user_connection && ! ( new Connection_Manager() )->has_connected_owner() ) {
$status = 'error';
} elseif ( ! static::has_required_plan() ) {
$status = 'needs_purchase'; // We need needs_purchase here as well because some products we consider active without the required plan.
} elseif ( ! static::has_required_plan() ) { // We need needs_purchase here as well because some products we consider active without the required plan.
if ( static::has_trial_support() ) {
$status = 'needs_purchase_or_free';
} else {
$status = 'needs_purchase';
}
}
} elseif ( ! static::has_required_plan() ) {
$status = 'needs_purchase';
if ( static::has_trial_support() ) {
$status = 'needs_purchase_or_free';
} else {
$status = 'needs_purchase';
}
} else {
$status = 'inactive';
}

View File

@ -14,9 +14,28 @@ use Jetpack_Options;
* Search stats (e.g. post count, post type breakdown)
*/
class Search_Stats {
const CACHE_EXPIRY = 5 * MINUTE_IN_SECONDS;
const CACHE_GROUP = 'jetpack_search';
const COUNT_ESTIMATE_CACHE_KEY = 'count_estimate';
const EXCLUDED_POST_TYPES = array(
'elementor_library', // Used by Elementor.
'jp_sitemap', // Used by Jetpack.
'revision',
'vip-legacy-redirect',
'scheduled-action',
'nbcs_video_lookup',
'reply', // bbpress, these get included in the topic
'product_variation', // woocommerce, not really public
'nav_menu_item',
'shop_order', // woocommerce, not really public
'redirect_rule', // Used by the Safe Redirect plugin.
);
const DO_NOT_EXCLUDE_POST_TYPES = array(
'topic', // bbpress
'forum', // bbpress
);
const CACHE_EXPIRY = 1 * MINUTE_IN_SECONDS;
const CACHE_GROUP = 'jetpack_search';
const POST_TYPE_BREAKDOWN_CACHE_KEY = 'post_type_break_down';
/**
* Get stats from the WordPress.com API for the current blog ID.
@ -43,47 +62,89 @@ class Search_Stats {
* Estimate record counts via a local database query.
*/
public static function estimate_count() {
$cached_value = wp_cache_get( self::COUNT_ESTIMATE_CACHE_KEY, self::CACHE_GROUP );
if ( false !== $cached_value ) {
return $cached_value;
}
return array_sum( static::get_post_type_breakdown() );
}
global $wpdb;
$indexable_statuses = get_post_stati( array( 'public' => true ) );
$unindexable_post_types = array_merge(
// Explicitly exclude various post types registered by plugins.
/**
* Calculate breakdown of post types for the site.
*/
public static function get_post_type_breakdown() {
$indexable_post_types = get_post_types(
array(
'elementor_library', // Used by Elementor.
'jp_sitemap', // Used by Jetpack.
'product_variation', // Used by Woocommerce.
'redirect_rule', // Used by the Safe Redirect plugin.
'reply', // Used by bbpress.
'scheduled-action', // Used by Woocommerce.
),
get_post_types(
array(
'exclude_from_search' => true,
'public' => false,
),
'names',
'or'
'public' => true,
'exclude_from_search' => false,
)
);
$prep_for_query = function ( $string ) use ( $wpdb ) {
// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.QuotedSimplePlaceholder -- This is used to sanitize post type names.
return $wpdb->prepare( "'%s'", $string );
};
$statuses_list = implode( ',', array_map( $prep_for_query, $indexable_statuses ) );
$post_types_list = implode( ',', array_map( $prep_for_query, $unindexable_post_types ) );
$count = (int) $wpdb->get_var(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- This is properly prepared, but the query is constructed using variables.
"SELECT COUNT(ID) FROM {$wpdb->posts} WHERE post_status IN ($statuses_list) AND post_type NOT IN ($post_types_list)"
$indexable_status_array = get_post_stati(
array(
'public' => true,
'exclude_from_search' => false,
)
);
$raw_posts_counts = static::get_raw_post_type_breakdown();
if ( ! $raw_posts_counts || is_wp_error( $raw_posts_counts ) ) {
return array();
}
$posts_counts = static::get_post_type_breakdown_with( $raw_posts_counts, $indexable_post_types, $indexable_status_array );
wp_cache_set( self::COUNT_ESTIMATE_CACHE_KEY, $count, self::CACHE_GROUP, self::CACHE_EXPIRY );
return $count;
return $posts_counts;
}
/**
* Calculate breakdown of post types with passed in indexable post types and statuses.
* The function is going to be used from WPCOM as well for consistency.
*
* @param array $raw_posts_counts Array of post types with counts as value.
* @param array $indexable_post_types Array of indexable post types.
* @param array $indexable_status_array Array of indexable post statuses.
*/
public static function get_post_type_breakdown_with( $raw_posts_counts, $indexable_post_types, $indexable_status_array ) {
$posts_counts = array();
foreach ( $raw_posts_counts as $row ) {
// ignore if status is not public.
if ( ! in_array( $row['post_status'], $indexable_status_array, true ) ) {
continue;
}
// ignore if post type is in excluded post types.
if ( in_array( $row['post_type'], self::EXCLUDED_POST_TYPES, true ) ) {
continue;
}
// ignore if post type is not public and is not explicitly included.
if ( ! in_array( $row['post_type'], $indexable_post_types, true ) &&
! in_array( $row['post_type'], self::DO_NOT_EXCLUDE_POST_TYPES, true )
) {
continue;
}
// add up post type counts of potentially multiple post_status.
if ( ! isset( $posts_counts[ $row['post_type'] ] ) ) {
$posts_counts[ $row['post_type'] ] = 0;
}
$posts_counts[ $row['post_type'] ] += intval( $row['num_posts'] );
}
arsort( $posts_counts, SORT_NUMERIC );
return $posts_counts;
}
/**
* Get raw post type breakdown from the database.
*/
protected static function get_raw_post_type_breakdown() {
global $wpdb;
$results = wp_cache_get( self::POST_TYPE_BREAKDOWN_CACHE_KEY, self::CACHE_GROUP );
if ( false !== $results ) {
return $results;
}
$query = "SELECT post_type, post_status, COUNT( * ) AS num_posts
FROM {$wpdb->posts}
WHERE post_password = ''
GROUP BY post_type, post_status";
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery
$results = $wpdb->get_results( $query, ARRAY_A );
wp_cache_set( self::POST_TYPE_BREAKDOWN_CACHE_KEY, $results, self::CACHE_GROUP, self::CACHE_EXPIRY );
return $results;
}
}

View File

@ -103,7 +103,7 @@ class Search extends Hybrid_Product {
return array(
__( 'Instant search and indexing', 'jetpack-my-jetpack' ),
__( 'Powerful filtering', 'jetpack-my-jetpack' ),
__( 'Supports 29 languages', 'jetpack-my-jetpack' ),
__( 'Supports 38 languages', 'jetpack-my-jetpack' ),
__( 'Spelling correction', 'jetpack-my-jetpack' ),
);
}
@ -117,8 +117,10 @@ class Search extends Hybrid_Product {
// Basic pricing info.
$pricing = array_merge(
array(
'available' => true,
'wpcom_product_slug' => static::get_wpcom_product_slug(),
'available' => true,
'trial_available' => static::has_trial_support(),
'wpcom_product_slug' => static::get_wpcom_product_slug(),
'wpcom_free_product_slug' => static::get_wpcom_free_product_slug(),
),
Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() )
);
@ -130,6 +132,8 @@ class Search extends Hybrid_Product {
return $pricing;
}
$pricing['estimated_record_count'] = $record_count;
return array_merge( $pricing, $search_pricing );
}
@ -142,6 +146,41 @@ class Search extends Hybrid_Product {
return 'jetpack_search';
}
/**
* Get the WPCOM free product slug
*
* @return ?string
*/
public static function get_wpcom_free_product_slug() {
return 'jetpack_search_free';
}
/**
* Returns true if the new_pricing_202208 is set to not empty in URL for testing purpose, or it's active.
*/
public static function is_new_pricing_202208() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
if ( isset( $_GET['new_pricing_202208'] ) && $_GET['new_pricing_202208'] ) {
return true;
}
$record_count = intval( Search_Stats::estimate_count() );
$search_pricing = static::get_pricing_from_wpcom( $record_count );
if ( is_wp_error( $search_pricing ) ) {
return false;
}
return '202208' === $search_pricing['pricing_version'];
}
/**
* Override status to `needs_purchase_or_free` when status is `needs_purchase`.
*/
public static function get_status() {
$status = parent::get_status();
return $status;
}
/**
* Use centralized Search pricing API.
*
@ -158,10 +197,22 @@ class Search extends Hybrid_Product {
return $pricings[ $record_count ];
}
$response = wp_remote_get(
sprintf( Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ) . '/wpcom/v2/jetpack-search/pricing?record_count=%1$d&locale=%2$s', $record_count, get_user_locale() ),
array( 'timeout' => 5 )
);
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
// For simple sites fetch the response directly.
$response = Client::wpcom_json_api_request_as_blog(
sprintf( '/jetpack-search/pricing?record_count=%1$d&locale=%2$s', $record_count, get_user_locale() ),
'2',
array( 'timeout' => 5 ),
null,
'wpcom'
);
} else {
// For non-simple sites we have to use the wp_remote_get, as connection might not be available.
$response = wp_remote_get(
sprintf( Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ) . '/wpcom/v2/jetpack-search/pricing?record_count=%1$d&locale=%2$s', $record_count, get_user_locale() ),
array( 'timeout' => 5 )
);
}
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
return new WP_Error( 'search_pricing_fetch_failed' );
@ -205,6 +256,19 @@ class Search extends Hybrid_Product {
return $status;
}
/**
* Checks whether the product supports trial or not
*
* Returns true if it supports. Return false otherwise.
*
* Free products will always return false.
*
* @return boolean
*/
public static function has_trial_support() {
return static::is_new_pricing_202208();
}
/**
* Checks whether the current plan of the site already supports the product
*

View File

@ -7,13 +7,13 @@
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Module_Product;
use Automattic\Jetpack\My_Jetpack\Hybrid_Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
/**
* Class responsible for handling the VideoPress product
*/
class Videopress extends Module_Product {
class Videopress extends Hybrid_Product {
/**
* The product slug
@ -29,6 +29,31 @@ class Videopress extends Module_Product {
*/
public static $module_name = 'videopress';
/**
* The slug of the plugin associated with this product.
*
* @var string
*/
public static $plugin_slug = 'jetpack-videopress';
/**
* The filename (id) of the plugin associated with this product.
*
* @var string
*/
public static $plugin_filename = array(
'jetpack-videopress/jetpack-videopress.php',
'videopress/jetpack-videopress.php',
'jetpack-videopress-dev/jetpack-videopress.php',
);
/**
* Search only requires site connection
*
* @var boolean
*/
public static $requires_user_connection = true;
/**
* Get the internationalized product name
*
@ -118,8 +143,11 @@ class Videopress extends Module_Product {
* @return ?string
*/
public static function get_manage_url() {
if ( static::is_active() ) {
if ( method_exists( 'Automattic\Jetpack\VideoPress\Initializer', 'should_initialize_admin_ui' ) && \Automattic\Jetpack\VideoPress\Initializer::should_initialize_admin_ui() ) {
return \Automattic\Jetpack\VideoPress\Admin_UI::get_admin_page_url();
} else {
return admin_url( 'admin.php?page=jetpack#/settings?term=videopress' );
}
}
}

View File

@ -5,6 +5,22 @@ 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).
## [1.7.19] - 2022-11-07
### Changed
- Updated package dependencies. [#27278]
## [1.7.18] - 2022-10-25
### Changed
- Updated package dependencies. [#26705]
## [1.7.17] - 2022-09-20
### Changed
- Updated package dependencies.
## [1.7.16] - 2022-09-08
### Changed
- Updated package dependencies.
## [1.7.15] - 2022-08-29
### Changed
- Updated package dependencies.
@ -184,6 +200,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add partner subsidiary id to upgrade URLs.
[1.7.19]: https://github.com/Automattic/jetpack-partner/compare/v1.7.18...v1.7.19
[1.7.18]: https://github.com/Automattic/jetpack-partner/compare/v1.7.17...v1.7.18
[1.7.17]: https://github.com/Automattic/jetpack-partner/compare/v1.7.16...v1.7.17
[1.7.16]: https://github.com/Automattic/jetpack-partner/compare/v1.7.15...v1.7.16
[1.7.15]: https://github.com/Automattic/jetpack-partner/compare/v1.7.14...v1.7.15
[1.7.14]: https://github.com/Automattic/jetpack-partner/compare/v1.7.13...v1.7.14
[1.7.13]: https://github.com/Automattic/jetpack-partner/compare/v1.7.12...v1.7.13

View File

@ -4,8 +4,8 @@
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"automattic/jetpack-connection": "^1.44",
"automattic/jetpack-status": "^1.14"
"automattic/jetpack-connection": "^1.46",
"automattic/jetpack-status": "^1.15"
},
"require-dev": {
"brain/monkey": "2.6.1",
@ -22,7 +22,8 @@
"phpunit": [
"./vendor/phpunit/phpunit/phpunit --colors=always"
],
"post-update-cmd": "php -r \"copy('vendor/automattic/wordbless/src/dbless-wpdb.php', 'wordpress/wp-content/db.php');\"",
"post-install-cmd": "WorDBless\\Composer\\InstallDropin::copy",
"post-update-cmd": "WorDBless\\Composer\\InstallDropin::copy",
"test-coverage": [
"php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/clover.xml\""
],

View File

@ -5,6 +5,10 @@ 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).
## [0.2.7] - 2022-09-20
### Changed
- Updated package dependencies.
## [0.2.6] - 2022-07-26
### Changed
- Updated package dependencies. [#25158]
@ -76,6 +80,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Use `composer update` rather than `install` in scripts, as composer.lock isn't checked in.
[0.2.7]: https://github.com/Automattic/jetpack-password-checker/compare/v0.2.6...v0.2.7
[0.2.6]: https://github.com/Automattic/jetpack-password-checker/compare/v0.2.5...v0.2.6
[0.2.5]: https://github.com/Automattic/jetpack-password-checker/compare/v0.2.4...v0.2.5
[0.2.4]: https://github.com/Automattic/jetpack-password-checker/compare/v0.2.3...v0.2.4

View File

@ -24,7 +24,8 @@
"test-php": [
"@composer phpunit"
],
"post-update-cmd": "php -r \"copy('vendor/automattic/wordbless/src/dbless-wpdb.php', 'wordpress/wp-content/db.php');\""
"post-install-cmd": "WorDBless\\Composer\\InstallDropin::copy",
"post-update-cmd": "WorDBless\\Composer\\InstallDropin::copy"
},
"minimum-stability": "dev",
"prefer-stable": true,

View File

@ -5,6 +5,10 @@ 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).
## [1.7.19] - 2022-11-07
### Changed
- Updated package dependencies. [#27278]
## [1.7.18] - 2022-07-26
### Changed
- Updated package dependencies. [#25158]
@ -152,6 +156,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Create Jetpack Redirect package
[1.7.19]: https://github.com/Automattic/jetpack-redirect/compare/v1.7.18...v1.7.19
[1.7.18]: https://github.com/Automattic/jetpack-redirect/compare/v1.7.17...v1.7.18
[1.7.17]: https://github.com/Automattic/jetpack-redirect/compare/v1.7.16...v1.7.17
[1.7.16]: https://github.com/Automattic/jetpack-redirect/compare/v1.7.15...v1.7.16

View File

@ -4,7 +4,7 @@
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"automattic/jetpack-status": "^1.14"
"automattic/jetpack-status": "^1.15"
},
"require-dev": {
"brain/monkey": "2.6.1",

View File

@ -5,6 +5,10 @@ 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).
## [1.15.0] - 2022-11-07
### Added
- WordPress.com: add checks for Simple or either Simple/WoA. [#27278]
## [1.14.3] - 2022-07-26
### Changed
- Updated package dependencies. [#25158]
@ -208,6 +212,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Packages: Introduce a status package
[1.15.0]: https://github.com/Automattic/jetpack-status/compare/v1.14.3...v1.15.0
[1.14.3]: https://github.com/Automattic/jetpack-status/compare/v1.14.2...v1.14.3
[1.14.2]: https://github.com/Automattic/jetpack-status/compare/v1.14.1...v1.14.2
[1.14.1]: https://github.com/Automattic/jetpack-status/compare/v1.14.0...v1.14.1

View File

@ -36,7 +36,7 @@
"link-template": "https://github.com/Automattic/jetpack-status/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "1.14.x-dev"
"dev-trunk": "1.15.x-dev"
}
}
}

View File

@ -58,6 +58,26 @@ class Host {
return Constants::is_defined( 'WPCOM_IS_VIP_ENV' ) && true === Constants::get_constant( 'WPCOM_IS_VIP_ENV' );
}
/**
* Determine if this is a Simple platform site.
*
* @return bool
*/
public function is_wpcom_simple() {
return Constants::is_defined( 'IS_WPCOM' ) && true === Constants::get_constant( 'IS_WPCOM' );
}
/**
* Determine if this is a WordPress.com site.
*
* Includes both Simple and WoA platforms.
*
* @return bool
*/
public function is_wpcom_platform() {
return $this->is_wpcom_simple() || $this->is_woa_site();
}
/**
* Add all wordpress.com environments to the safe redirect allowed list.
*

View File

@ -5,6 +5,56 @@ 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).
## [1.43.0] - 2022-11-17
### Added
- Added new sync option for launch-status [#27434]
- Jetpack Sync: Add new endpoint for resetting Sync locks. [#27446]
## [1.42.0] - 2022-11-14
### Changed
- Save attached media to post meta [#26930]
## [1.41.0] - 2022-11-07
### Changed
- Jetpack Sync: add Updates module in required Sync modules. [#27182]
- Updated package dependencies. [#27278]
## [1.40.3] - 2022-11-01
### Added
- Adds wpcom_gifting_subscription option for syncing [#27116]
## [1.40.2] - 2022-10-27
### Fixed
- Check $action_links is still array after plugin_action_links filter has been applied. [#27076]
## [1.40.1] - 2022-10-25
### Added
- Added featured_image_email_enabled option for syncing [#27009]
### Changed
- Search Sync Settings :: Add Marketplace product extra fields meta to allow list [#26927]
- Updated package dependencies. [#26705]
## [1.40.0] - 2022-10-17
### Added
- Adding the `sl-insta-media` post type to the Sync blacklist as the post type data isn't relevant to Sync and takes up non-trivial resources. [#26826]
## [1.39.0] - 2022-10-11
### Added
- Added some taxonomies to be indexed by Search. [#26683]
## [1.38.4] - 2022-09-20
### Added
- Gutenberg Support: add additional theme_supports items to our synced allowlist. [#26239]
### Changed
- Default Theme Support: Add support for new feature added in WP 6.1 [#26236]
- Theme Supports: updated allowed values list to align with a Core change r54159-core [#26220]
## [1.38.3] - 2022-09-08
### Changed
- Updated package dependencies.
## [1.38.2] - 2022-08-29
### Changed
- Updated package dependencies.
@ -711,6 +761,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Packages: Move sync to a classmapped package
[1.43.0]: https://github.com/Automattic/jetpack-sync/compare/v1.42.0...v1.43.0
[1.42.0]: https://github.com/Automattic/jetpack-sync/compare/v1.41.0...v1.42.0
[1.41.0]: https://github.com/Automattic/jetpack-sync/compare/v1.40.3...v1.41.0
[1.40.3]: https://github.com/Automattic/jetpack-sync/compare/v1.40.2...v1.40.3
[1.40.2]: https://github.com/Automattic/jetpack-sync/compare/v1.40.1...v1.40.2
[1.40.1]: https://github.com/Automattic/jetpack-sync/compare/v1.40.0...v1.40.1
[1.40.0]: https://github.com/Automattic/jetpack-sync/compare/v1.39.0...v1.40.0
[1.39.0]: https://github.com/Automattic/jetpack-sync/compare/v1.38.4...v1.39.0
[1.38.4]: https://github.com/Automattic/jetpack-sync/compare/v1.38.3...v1.38.4
[1.38.3]: https://github.com/Automattic/jetpack-sync/compare/v1.38.2...v1.38.3
[1.38.2]: https://github.com/Automattic/jetpack-sync/compare/v1.38.1...v1.38.2
[1.38.1]: https://github.com/Automattic/jetpack-sync/compare/v1.38.0...v1.38.1
[1.38.0]: https://github.com/Automattic/jetpack-sync/compare/v1.37.1...v1.38.0

View File

@ -4,12 +4,12 @@
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"automattic/jetpack-connection": "^1.44",
"automattic/jetpack-connection": "^1.46",
"automattic/jetpack-constants": "^1.6",
"automattic/jetpack-identity-crisis": "^0.8",
"automattic/jetpack-password-checker": "^0.2",
"automattic/jetpack-roles": "^1.4",
"automattic/jetpack-status": "^1.14"
"automattic/jetpack-status": "^1.15"
},
"require-dev": {
"automattic/jetpack-changelogger": "^3.2",
@ -31,7 +31,8 @@
"test-php": [
"@composer phpunit"
],
"post-update-cmd": "php -r \"copy('vendor/automattic/wordbless/src/dbless-wpdb.php', 'wordpress/wp-content/db.php');\""
"post-install-cmd": "WorDBless\\Composer\\InstallDropin::copy",
"post-update-cmd": "WorDBless\\Composer\\InstallDropin::copy"
},
"minimum-stability": "dev",
"prefer-stable": true,
@ -46,7 +47,7 @@
"link-template": "https://github.com/Automattic/jetpack-sync/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "1.38.x-dev"
"dev-trunk": "1.43.x-dev"
}
},
"config": {

View File

@ -1042,4 +1042,28 @@ class Actions {
}
return $result;
}
/**
* Reset Sync locks.
*
* @access public
* @static
* @since 1.43.0
*/
public static function reset_sync_locks() {
// Next sync locks.
delete_option( Sender::NEXT_SYNC_TIME_OPTION_NAME . '_sync' );
delete_option( Sender::NEXT_SYNC_TIME_OPTION_NAME . '_full_sync' );
delete_option( Sender::NEXT_SYNC_TIME_OPTION_NAME . '_full-sync-enqueue' );
// Retry after locks.
delete_option( self::RETRY_AFTER_PREFIX . '_sync' );
delete_option( self::RETRY_AFTER_PREFIX . '_full_sync' );
// Dedicated sync lock.
\Jetpack_Options::delete_raw_option( Dedicated_Sender::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME );
// Queue locks.
$sync_queue = new Queue( 'sync' );
$sync_queue->unlock();
$full_sync_queue = new Queue( 'full_sync' );
$full_sync_queue->unlock();
}
}

View File

@ -21,6 +21,7 @@ class Data_Settings {
'Automattic\\Jetpack\\Sync\\Modules\\Constants',
'Automattic\\Jetpack\\Sync\\Modules\\Full_Sync_Immediately', // enable Initial Sync on Site Connection.
'Automattic\\Jetpack\\Sync\\Modules\\Options',
'Automattic\\Jetpack\\Sync\\Modules\\Updates',
),
'jetpack_sync_callable_whitelist' => array(
'site_url' => array( 'Automattic\\Jetpack\\Connection\\Urls', 'site_url' ),

View File

@ -57,6 +57,7 @@ class Defaults {
'disabled_reblogs',
'disallowed_keys',
'enable_header_ad',
'featured_image_email_enabled',
'gmt_offset',
'gravatar_disable_hovercards',
'highlander_comment_form_prompt',
@ -101,6 +102,7 @@ class Defaults {
'jetpack_wga',
'large_size_h',
'large_size_w',
'launch-status',
'mailserver_login', // Not syncing contents, only the option name.
'mailserver_pass', // Not syncing contents, only the option name.
'mailserver_port',
@ -173,6 +175,7 @@ class Defaults {
'wpcom_publish_comments_with_markdown',
'wpcom_publish_posts_with_markdown',
'videopress_private_enabled_for_site',
'wpcom_gifting_subscription',
);
/**
@ -407,6 +410,7 @@ class Defaults {
'scheduled-action', // Action Scheduler - Job Queue for WordPress https://github.com/woocommerce/woocommerce/tree/e7762627c37ec1f7590e6cac4218ba0c6a20024d/includes/libraries/action-scheduler .
'secupress_log_action',
'sg_optimizer_jobs',
'sl-insta-media',
'snitch',
'vip-legacy-redirect',
'wp-rest-api-log', // https://wordpress.org/plugins/wp-rest-api-log/.
@ -721,6 +725,7 @@ class Defaults {
'_wpas_feature_enabled',
'_wpas_is_tweetstorm',
'_wpas_mess',
'_wpas_options',
'advanced_seo_description', // Jetpack_SEO_Posts::DESCRIPTION_META_KEY.
'content_width',
'custom_css_add',
@ -796,16 +801,21 @@ class Defaults {
*/
public static $default_theme_support_whitelist = array(
'align-wide',
'appearance-tools', // In Gutenberg.
'automatic-feed-links',
'block-templates',
'block-template-parts', // WP 6.1. Added via https://core.trac.wordpress.org/changeset/54176
'custom-background',
'custom-header',
'custom-logo',
'customize-selective-refresh-widgets',
'dark-editor-style',
'default-color-palette', // In Gutenberg.
'default-gradient-presets', // In Gutenberg.
'disable-custom-colors',
'disable-custom-font-sizes',
'disable-custom-gradients',
'disable-layout-styles', // WP 6.1. Added via https://core.trac.wordpress.org/changeset/54159
'editor-color-palette',
'editor-font-sizes',
'editor-gradient-presets',

View File

@ -12,7 +12,7 @@ namespace Automattic\Jetpack\Sync;
*/
class Package_Version {
const PACKAGE_VERSION = '1.38.2';
const PACKAGE_VERSION = '1.43.0';
const PACKAGE_SLUG = 'sync';

View File

@ -317,6 +317,17 @@ class REST_Endpoints {
)
);
// Reset Sync locks.
register_rest_route(
'jetpack/v4',
'/sync/locks',
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => __CLASS__ . '::reset_locks',
'permission_callback' => __CLASS__ . '::verify_default_permissions',
)
);
}
/**
@ -768,6 +779,23 @@ class REST_Endpoints {
);
}
/**
* Reset Sync locks.
*
* @since 1.43.0
*
* @return \WP_REST_Response
*/
public static function reset_locks() {
Actions::reset_sync_locks();
return rest_ensure_response(
array(
'success' => true,
)
);
}
/**
* Verify that request has default permissions to perform sync actions.
*

View File

@ -376,6 +376,10 @@ class Callables extends Module {
);
/** This filter is documented in src/wp-admin/includes/class-wp-plugins-list-table.php */
$action_links = apply_filters( 'plugin_action_links', $action_links, $plugin_file, null, 'all' );
// Verify $action_links is still an array.
if ( ! is_array( $action_links ) ) {
$action_links = array();
}
/** This filter is documented in src/wp-admin/includes/class-wp-plugins-list-table.php */
$action_links = apply_filters( "plugin_action_links_{$plugin_file}", $action_links, $plugin_file, null, 'all' );
// Verify $action_links is still an array to resolve warnings from filters not returning an array.

View File

@ -69,367 +69,368 @@ class Search extends Module {
*/
private static $postmeta_to_sync = array(
// jetpack.
'jetpack-search-meta0' => array( 'searchable_in_all_content' => true ),
'jetpack-search-meta1' => array( 'searchable_in_all_content' => true ),
'jetpack-search-meta2' => array( 'searchable_in_all_content' => true ),
'jetpack-search-meta3' => array( 'searchable_in_all_content' => true ),
'jetpack-search-meta4' => array( 'searchable_in_all_content' => true ),
'jetpack-search-meta5' => array( 'searchable_in_all_content' => true ),
'jetpack-search-meta6' => array( 'searchable_in_all_content' => true ),
'jetpack-search-meta7' => array( 'searchable_in_all_content' => true ),
'jetpack-search-meta8' => array( 'searchable_in_all_content' => true ),
'jetpack-search-meta9' => array( 'searchable_in_all_content' => true ),
'jetpack-search-meta0' => array( 'searchable_in_all_content' => true ),
'jetpack-search-meta1' => array( 'searchable_in_all_content' => true ),
'jetpack-search-meta2' => array( 'searchable_in_all_content' => true ),
'jetpack-search-meta3' => array( 'searchable_in_all_content' => true ),
'jetpack-search-meta4' => array( 'searchable_in_all_content' => true ),
'jetpack-search-meta5' => array( 'searchable_in_all_content' => true ),
'jetpack-search-meta6' => array( 'searchable_in_all_content' => true ),
'jetpack-search-meta7' => array( 'searchable_in_all_content' => true ),
'jetpack-search-meta8' => array( 'searchable_in_all_content' => true ),
'jetpack-search-meta9' => array( 'searchable_in_all_content' => true ),
// woocommerce.
'entity_types' => array(),
'exclude_product_categories' => array(),
'exclude_product_ids' => array(),
'free_shipping' => array(),
'id_field' => array(),
'individual_use' => array(),
'limit_usage_to_x_items' => array(),
'maximum_amount' => array(),
'minimum_amount' => array(),
'post_id' => array(),
'product_categories' => array( 'searchable_in_all_content' => true ),
'product_ids' => array(),
'total_sales' => array(
'entity_types' => array(),
'exclude_product_categories' => array(),
'exclude_product_ids' => array(),
'free_shipping' => array(),
'id_field' => array(),
'individual_use' => array(),
'limit_usage_to_x_items' => array(),
'maximum_amount' => array(),
'minimum_amount' => array(),
'post_id' => array(),
'product_categories' => array( 'searchable_in_all_content' => true ),
'product_ids' => array(),
'total_sales' => array(
'available' => false,
'alternatives' => array(
'wc.percent_of_sales',
),
),
'usage_limit' => array(),
'usage_limit_per_user' => array(),
'_crosssell_ids' => array(),
'_downloadable' => array(),
'_featured' => array(),
'_height' => array(),
'_length' => array(),
'_price' => array(
'usage_limit' => array(),
'usage_limit_per_user' => array(),
'_crosssell_ids' => array(),
'_downloadable' => array(),
'_featured' => array(),
'_height' => array(),
'_length' => array(),
'_price' => array(
'alternatives' => array(
'wc.price',
'wc.min_price',
'wc.max_price',
),
),
'_prices_include_tax' => array(),
'_product_attributes' => array(),
'_product_version' => array(),
'_regular_price' => array(
'_prices_include_tax' => array(),
'_product_attributes' => array(),
'_product_version' => array(),
'_regular_price' => array(
'alternatives' => array(
'wc.regular_price',
),
),
'_sale_price' => array(
'_sale_price' => array(
'alternatives' => array(
'wc.sale_price',
),
),
'_sale_price_dates_from' => array(),
'_sale_price_dates_to' => array(),
'_sku' => array( 'searchable_in_all_content' => true ),
'_stock_status' => array(),
'_wc_average_rating' => array(
'_sale_price_dates_from' => array(),
'_sale_price_dates_to' => array(),
'_sku' => array( 'searchable_in_all_content' => true ),
'_stock_status' => array(),
'_wc_average_rating' => array(
'alternatives' => array(
'wc.ave_rating_score',
),
),
'_wc_rating_count' => array(
'_wc_rating_count' => array(
'alternatives' => array(
'wc.rating', // wc.rating.count_1, wc.rating.count_2, ...
),
),
'_wc_review_count' => array(),
'_weight' => array(),
'_width' => array(),
'_wc_review_count' => array(),
'_weight' => array(),
'_width' => array(),
// co-authors plus.
'cap-description' => array( 'searchable_in_all_content' => true ),
'cap-user_login' => array( 'searchable_in_all_content' => true ),
'cap-user_email' => array(),
'cap-last_name' => array( 'searchable_in_all_content' => true ),
'cap-first_name' => array( 'searchable_in_all_content' => true ),
'cap-display_name' => array( 'searchable_in_all_content' => true ),
'cap-website' => array(),
'cap-jabber' => array(),
'cap-aim' => array(),
'cap-twitter' => array(),
'cap-facebook' => array(),
'cap-google_plus' => array(),
'cap-job_title' => array( 'searchable_in_all_content' => true ),
'cap-description' => array( 'searchable_in_all_content' => true ),
'cap-user_login' => array( 'searchable_in_all_content' => true ),
'cap-user_email' => array(),
'cap-last_name' => array( 'searchable_in_all_content' => true ),
'cap-first_name' => array( 'searchable_in_all_content' => true ),
'cap-display_name' => array( 'searchable_in_all_content' => true ),
'cap-website' => array(),
'cap-jabber' => array(),
'cap-aim' => array(),
'cap-twitter' => array(),
'cap-facebook' => array(),
'cap-google_plus' => array(),
'cap-job_title' => array( 'searchable_in_all_content' => true ),
// bbpress.
'bbpl_like' => array(),
'bbpress_discussion_comments_copied' => array(),
'bbpress_discussion_tags_copied' => array(),
'bbpress_discussion_topic_id' => array(),
'bbpress_discussion_use_defaults' => array(),
'bbpress_page_header_bg' => array(),
'bbpress_title_bg' => array(),
'use_bbpress_discussion_topic' => array(),
'bbpl_like' => array(),
'bbpress_discussion_comments_copied' => array(),
'bbpress_discussion_tags_copied' => array(),
'bbpress_discussion_topic_id' => array(),
'bbpress_discussion_use_defaults' => array(),
'bbpress_page_header_bg' => array(),
'bbpress_title_bg' => array(),
'use_bbpress_discussion_topic' => array(),
// wpml.
'tm_meta_wpml' => array(),
'wpml_language' => array(),
'wpml_media_lang' => array(),
'wpml_media_processed' => array(),
'tm_meta_wpml' => array(),
'wpml_language' => array(),
'wpml_media_lang' => array(),
'wpml_media_processed' => array(),
// blogger import.
'blogger_author' => array( 'searchable_in_all_content' => true ),
'blogger_blog' => array( 'searchable_in_all_content' => true ),
'blogger_permalink' => array( 'searchable_in_all_content' => true ),
'blogger_author' => array( 'searchable_in_all_content' => true ),
'blogger_blog' => array( 'searchable_in_all_content' => true ),
'blogger_permalink' => array( 'searchable_in_all_content' => true ),
// geo.
'geo_address' => array( 'searchable_in_all_content' => true ),
'geo_latitude' => array(),
'geo_longitude' => array(),
'geo_public' => array(),
'geolocated' => array(),
'geolocation_city' => array( 'searchable_in_all_content' => true ),
'geolocation_country_long' => array( 'searchable_in_all_content' => true ),
'geolocation_country_short' => array( 'searchable_in_all_content' => true ),
'geolocation_formatted_address' => array( 'searchable_in_all_content' => true ),
'geolocation_lat' => array(),
'geolocation_long' => array(),
'geolocation_postcode' => array( 'searchable_in_all_content' => true ),
'geolocation_state_long' => array( 'searchable_in_all_content' => true ),
'geolocation_state_short' => array( 'searchable_in_all_content' => true ),
'geo_address' => array( 'searchable_in_all_content' => true ),
'geo_latitude' => array(),
'geo_longitude' => array(),
'geo_public' => array(),
'geolocated' => array(),
'geolocation_city' => array( 'searchable_in_all_content' => true ),
'geolocation_country_long' => array( 'searchable_in_all_content' => true ),
'geolocation_country_short' => array( 'searchable_in_all_content' => true ),
'geolocation_formatted_address' => array( 'searchable_in_all_content' => true ),
'geolocation_lat' => array(),
'geolocation_long' => array(),
'geolocation_postcode' => array( 'searchable_in_all_content' => true ),
'geolocation_state_long' => array( 'searchable_in_all_content' => true ),
'geolocation_state_short' => array( 'searchable_in_all_content' => true ),
// wp-ultimate-recipe.
'recipe_alternate_image' => array(),
'recipe_cook_time' => array(),
'recipe_cook_time_text' => array(),
'recipe_description' => array( 'searchable_in_all_content' => true ),
'recipe_ingredients' => array( 'searchable_in_all_content' => true ),
'recipe_instructions' => array( 'searchable_in_all_content' => true ),
'recipe_notes' => array( 'searchable_in_all_content' => true ),
'recipe_nutritional' => array( 'searchable_in_all_content' => true ),
'recipe_passive_time' => array(),
'recipe_passive_time_text' => array(),
'recipe_prep_time' => array(),
'recipe_prep_time_text' => array(),
'recipe_rating' => array(),
'recipe_servings' => array(),
'recipe_servings_normalized' => array(),
'recipe_servings_type' => array(),
'recipe_terms' => array( 'searchable_in_all_content' => true ),
'recipe_terms_with_parents' => array(),
'recipe_title' => array( 'searchable_in_all_content' => true ),
'recipe_user_ratings' => array(),
'recipe_user_ratings_rating' => array(),
'recipe_alternate_image' => array(),
'recipe_cook_time' => array(),
'recipe_cook_time_text' => array(),
'recipe_description' => array( 'searchable_in_all_content' => true ),
'recipe_ingredients' => array( 'searchable_in_all_content' => true ),
'recipe_instructions' => array( 'searchable_in_all_content' => true ),
'recipe_notes' => array( 'searchable_in_all_content' => true ),
'recipe_nutritional' => array( 'searchable_in_all_content' => true ),
'recipe_passive_time' => array(),
'recipe_passive_time_text' => array(),
'recipe_prep_time' => array(),
'recipe_prep_time_text' => array(),
'recipe_rating' => array(),
'recipe_servings' => array(),
'recipe_servings_normalized' => array(),
'recipe_servings_type' => array(),
'recipe_terms' => array( 'searchable_in_all_content' => true ),
'recipe_terms_with_parents' => array(),
'recipe_title' => array( 'searchable_in_all_content' => true ),
'recipe_user_ratings' => array(),
'recipe_user_ratings_rating' => array(),
// generic fields.
// from advanced-custom-fields and metabox.io .
'Link' => array(),
'Location' => array(),
'Title' => array( 'searchable_in_all_content' => true ),
'ad_code' => array(),
'address' => array(),
'admin_mail' => array(),
'admin_only' => array(),
'advertisers' => array( 'searchable_in_all_content' => true ),
'age' => array(),
'aliases' => array(),
'alternate_title' => array(),
'amazon' => array(),
'answer' => array( 'searchable_in_all_content' => true ),
'area' => array(),
'attention' => array(),
'attr' => array(),
'author' => array( 'searchable_in_all_content' => true ),
'author_name' => array( 'searchable_in_all_content' => true ),
'blog' => array(),
'blog_id' => array(),
'call_to_action' => array(),
'campaign_preview' => array(),
'canonical_url' => array(),
'catch_text' => array(),
'category' => array( 'searchable_in_all_content' => true ),
'classificacao' => array(),
'classification' => array(),
'code' => array(),
'codigo' => array(),
'company' => array( 'searchable_in_all_content' => true ),
'company_website' => array(),
'config' => array(),
'construction' => array(),
'container_ids' => array(),
'content' => array( 'searchable_in_all_content' => true ),
'content_body-full_content' => array( 'searchable_in_all_content' => true ),
'copyright' => array(),
'custom_page_title' => array( 'searchable_in_all_content' => true ),
'custom_permalink' => array(),
'customize' => array(),
'data' => array(),
'date' => array(),
'day' => array(),
'descripcion' => array( 'searchable_in_all_content' => true ),
'description' => array( 'searchable_in_all_content' => true ),
'display_settings' => array(),
'display_type' => array(),
'duration' => array(),
'embed' => array(),
'entity_ids' => array(),
'entity_types' => array(),
'event_subtitle' => array( 'searchable_in_all_content' => true ),
'excluded_container_ids' => array(),
'exclusions' => array(),
'experience' => array(),
'external_url' => array(),
'featured' => array(),
'featured_image' => array(),
'featured_post' => array(),
'featured_story' => array(),
'fee' => array(),
'filter' => array(),
'follow' => array(),
'footer_text' => array(),
'from_header' => array(),
'fullscreen_view' => array(),
'gallery' => array(),
'genre' => array( 'searchable_in_all_content' => true ),
'guests' => array( 'searchable_in_all_content' => true ),
'has_variations' => array(),
'hashtag' => array(),
'header_image' => array(),
'hidden_from_ui' => array(),
'hide_on_screen' => array(),
'homepage_order' => array(),
'hours' => array(),
'i18n' => array(),
'id' => array(),
'image' => array(),
'image_size' => array(),
'image_source' => array(),
'index' => array(),
'intro_text' => array( 'searchable_in_all_content' => true ),
'job_mention' => array( 'searchable_in_all_content' => true ),
'keywords' => array( 'searchable_in_all_content' => true ),
'latest_news' => array(),
'layout' => array(),
'link' => array(),
'link_dump' => array( 'searchable_in_all_content' => true ),
'link_url' => array(),
'location' => array(),
'logo' => array(),
'main_title' => array( 'searchable_in_all_content' => true ),
'maximum_entity_count' => array(),
'media' => array(),
'mentions' => array(),
'messages' => array(),
'meta_description' => array( 'searchable_in_all_content' => true ),
'meta_id' => array(),
'meta_index' => array(),
'meta_key' => array(),
'meta_value' => array(),
'modal-dialog-id' => array(),
'name' => array( 'searchable_in_all_content' => true ),
'nombre' => array( 'searchable_in_all_content' => true ),
'notes' => array( 'searchable_in_all_content' => true ),
'options' => array(),
'order_by' => array(),
'order_direction' => array(),
'original_cats' => array(),
'original_headers' => array(),
'original_link' => array(),
'original_message' => array(),
'original_subject' => array(),
'original_title' => array(),
'original_to' => array(),
'other_setting' => array(),
'page_canonical' => array(),
'page_layout' => array(),
'page_sidebar' => array(),
'page_tags' => array(),
'panels_data' => array(),
'parking' => array(),
'pdf_upload' => array(),
'photo' => array(),
'play_time' => array(),
'position' => array(),
'post-rating' => array(),
'post_background' => array(),
'post_color' => array(),
'post_sidebar' => array(),
'post_subtitle' => array( 'searchable_in_all_content' => true ),
'price' => array(),
'publication' => array(),
'rating' => array(),
'ratings_average' => array(),
'ratings_score' => array(),
'ratings_users' => array(),
'relation' => array(),
'reply_to_header' => array(),
'required' => array(),
'returns' => array(),
'review_post' => array(),
'rule' => array(),
'section' => array( 'searchable_in_all_content' => true ),
'session_transcript' => array(),
'settings' => array(),
'sex' => array(),
'shares_count' => array(),
'show_description' => array( 'searchable_in_all_content' => true ),
'show_page_title' => array(),
'side' => array(),
'sidebar' => array(),
'site' => array(),
'situation' => array(),
'slide_template' => array(),
'slug' => array(),
'sortorder' => array(),
'source' => array(),
'start_date' => array(),
'status' => array(),
'styles' => array(),
'subtitle' => array( 'searchable_in_all_content' => true ),
'subtitulo' => array(),
'success' => array(),
'summary' => array( 'searchable_in_all_content' => true ),
'synopsis' => array( 'searchable_in_all_content' => true ),
'tel' => array(),
'tema' => array(),
'testimonial' => array(),
'testimonial_author' => array(),
'text_already_subscribed' => array(),
'text_error' => array(),
'text_invalid_email' => array(),
'text_not_subscribed' => array(),
'text_required_field_missing' => array(),
'text_subscribed' => array(),
'text_unsubscribed' => array(),
'thumbnail' => array(),
'time' => array(),
'time_jump_list' => array( 'searchable_in_all_content' => true ),
'title' => array( 'searchable_in_all_content' => true ),
'title_view' => array(),
'titre' => array( 'searchable_in_all_content' => true ),
'titulo' => array( 'searchable_in_all_content' => true ),
'to_header' => array(),
'toc' => array(),
'transcript' => array( 'searchable_in_all_content' => true ),
'transport_uri' => array(),
'type' => array(),
'url' => array(),
'validation' => array(),
'value' => array(),
'values' => array(),
'variation' => array(),
'video' => array(),
'video_type' => array(),
'video_url' => array(),
'videopress_guid' => array(),
'website' => array(),
'weight' => array(),
'year' => array(),
'Link' => array(),
'Location' => array(),
'Title' => array( 'searchable_in_all_content' => true ),
'ad_code' => array(),
'address' => array(),
'admin_mail' => array(),
'admin_only' => array(),
'advertisers' => array( 'searchable_in_all_content' => true ),
'age' => array(),
'aliases' => array(),
'alternate_title' => array(),
'amazon' => array(),
'answer' => array( 'searchable_in_all_content' => true ),
'area' => array(),
'attention' => array(),
'attr' => array(),
'author' => array( 'searchable_in_all_content' => true ),
'author_name' => array( 'searchable_in_all_content' => true ),
'blog' => array(),
'blog_id' => array(),
'call_to_action' => array(),
'campaign_preview' => array(),
'canonical_url' => array(),
'catch_text' => array(),
'category' => array( 'searchable_in_all_content' => true ),
'classificacao' => array(),
'classification' => array(),
'code' => array(),
'codigo' => array(),
'company' => array( 'searchable_in_all_content' => true ),
'company_website' => array(),
'config' => array(),
'construction' => array(),
'container_ids' => array(),
'content' => array( 'searchable_in_all_content' => true ),
'content_body-full_content' => array( 'searchable_in_all_content' => true ),
'copyright' => array(),
'custom_page_title' => array( 'searchable_in_all_content' => true ),
'custom_permalink' => array(),
'customize' => array(),
'data' => array(),
'date' => array(),
'day' => array(),
'descripcion' => array( 'searchable_in_all_content' => true ),
'description' => array( 'searchable_in_all_content' => true ),
'display_settings' => array(),
'display_type' => array(),
'duration' => array(),
'embed' => array(),
'entity_ids' => array(),
'entity_types' => array(),
'event_subtitle' => array( 'searchable_in_all_content' => true ),
'excluded_container_ids' => array(),
'exclusions' => array(),
'experience' => array(),
'external_url' => array(),
'featured' => array(),
'featured_image' => array(),
'featured_post' => array(),
'featured_story' => array(),
'fee' => array(),
'filter' => array(),
'follow' => array(),
'footer_text' => array(),
'from_header' => array(),
'fullscreen_view' => array(),
'gallery' => array(),
'genre' => array( 'searchable_in_all_content' => true ),
'guests' => array( 'searchable_in_all_content' => true ),
'has_variations' => array(),
'hashtag' => array(),
'header_image' => array(),
'hidden_from_ui' => array(),
'hide_on_screen' => array(),
'homepage_order' => array(),
'hours' => array(),
'i18n' => array(),
'id' => array(),
'image' => array(),
'image_size' => array(),
'image_source' => array(),
'index' => array(),
'intro_text' => array( 'searchable_in_all_content' => true ),
'job_mention' => array( 'searchable_in_all_content' => true ),
'keywords' => array( 'searchable_in_all_content' => true ),
'latest_news' => array(),
'layout' => array(),
'link' => array(),
'link_dump' => array( 'searchable_in_all_content' => true ),
'link_url' => array(),
'location' => array(),
'logo' => array(),
'main_title' => array( 'searchable_in_all_content' => true ),
'maximum_entity_count' => array(),
'media' => array(),
'mentions' => array(),
'messages' => array(),
'meta_description' => array( 'searchable_in_all_content' => true ),
'meta_id' => array(),
'meta_index' => array(),
'meta_key' => array(),
'meta_value' => array(),
'modal-dialog-id' => array(),
'name' => array( 'searchable_in_all_content' => true ),
'nombre' => array( 'searchable_in_all_content' => true ),
'notes' => array( 'searchable_in_all_content' => true ),
'options' => array(),
'order_by' => array(),
'order_direction' => array(),
'original_cats' => array(),
'original_headers' => array(),
'original_link' => array(),
'original_message' => array(),
'original_subject' => array(),
'original_title' => array(),
'original_to' => array(),
'other_setting' => array(),
'page_canonical' => array(),
'page_layout' => array(),
'page_sidebar' => array(),
'page_tags' => array(),
'panels_data' => array(),
'parking' => array(),
'pdf_upload' => array(),
'photo' => array(),
'play_time' => array(),
'position' => array(),
'post-rating' => array(),
'post_background' => array(),
'post_color' => array(),
'post_sidebar' => array(),
'post_subtitle' => array( 'searchable_in_all_content' => true ),
'price' => array(),
'publication' => array(),
'rating' => array(),
'ratings_average' => array(),
'ratings_score' => array(),
'ratings_users' => array(),
'relation' => array(),
'reply_to_header' => array(),
'required' => array(),
'returns' => array(),
'review_post' => array(),
'rule' => array(),
'section' => array( 'searchable_in_all_content' => true ),
'session_transcript' => array(),
'settings' => array(),
'sex' => array(),
'shares_count' => array(),
'show_description' => array( 'searchable_in_all_content' => true ),
'show_page_title' => array(),
'side' => array(),
'sidebar' => array(),
'site' => array(),
'situation' => array(),
'slide_template' => array(),
'slug' => array(),
'sortorder' => array(),
'source' => array(),
'start_date' => array(),
'status' => array(),
'styles' => array(),
'subtitle' => array( 'searchable_in_all_content' => true ),
'subtitulo' => array(),
'success' => array(),
'summary' => array( 'searchable_in_all_content' => true ),
'synopsis' => array( 'searchable_in_all_content' => true ),
'tel' => array(),
'tema' => array(),
'testimonial' => array(),
'testimonial_author' => array(),
'text_already_subscribed' => array(),
'text_error' => array(),
'text_invalid_email' => array(),
'text_not_subscribed' => array(),
'text_required_field_missing' => array(),
'text_subscribed' => array(),
'text_unsubscribed' => array(),
'thumbnail' => array(),
'time' => array(),
'time_jump_list' => array( 'searchable_in_all_content' => true ),
'title' => array( 'searchable_in_all_content' => true ),
'title_view' => array(),
'titre' => array( 'searchable_in_all_content' => true ),
'titulo' => array( 'searchable_in_all_content' => true ),
'to_header' => array(),
'toc' => array(),
'transcript' => array( 'searchable_in_all_content' => true ),
'transport_uri' => array(),
'type' => array(),
'url' => array(),
'validation' => array(),
'value' => array(),
'values' => array(),
'variation' => array(),
'video' => array(),
'video_type' => array(),
'video_url' => array(),
'videopress_guid' => array(),
'website' => array(),
'weight' => array(),
'year' => array(),
// wp.com martketplace search - @see https://wp.me/pdh6GB-Ax#comment-2104
'_app_icon' => array(),
'_featured_product_video' => array(),
'wpcom_marketplace_org_slug' => array(),
'_wc_general_product_dependency_theme' => array(),
'_wc_general_product_dependency_plugin' => array(),
'_app_icon' => array(),
'_featured_product_video' => array(),
'wpcom_marketplace_org_slug' => array(),
'_wc_general_product_dependency_theme' => array(),
'_wc_general_product_dependency_plugin' => array(),
'wpcom_marketplace_product_extra_fields' => array(),
); // end indexed post meta.
@ -1014,6 +1015,7 @@ class Search extends Module {
'product-brand',
'product_brand',
'product-category',
'product_cat',
'product_delivery_time',
'product_delivery_times',
'product_price_label',
@ -1083,6 +1085,7 @@ class Search extends Module {
'attachment_category',
'attachment_tag',
'atum_location',
'audience_type',
'avhec_catgroup',
'bartype',
'baths',
@ -1092,6 +1095,8 @@ class Search extends Module {
'booked_custom_calendars',
'brand',
'brands',
'business',
'business_cat',
'business_category',
'bwg_tag',
'byline',
@ -1154,6 +1159,7 @@ class Search extends Module {
'dlm_download_tag',
'doc_tag',
'document-category',
'document-type',
'download_artist',
'download_category',
'download_tag',
@ -1241,6 +1247,7 @@ class Search extends Module {
'incsub_wiki_category',
'industry',
'ingredient',
'insight-type',
'issue',
'issuem_issue',
'issuem_issue_tags',
@ -1302,6 +1309,8 @@ class Search extends Module {
'muviyear',
'news-category',
'news-tag',
'news-type',
'news_type_cat',
'news_category',
'nova_menu',
'nova_menu_item_label',
@ -1318,6 +1327,7 @@ class Search extends Module {
'pdf_lv_tag',
'pec_events_category',
'people',
'people-department',
'people-expertise',
'people-location',
'perfect_quotes_category',
@ -1350,6 +1360,7 @@ class Search extends Module {
'portcat',
'portfolio-category',
'portfolio-gallery',
'portfolio-status',
'portfolio-skills',
'portfolio-tag',
'portfolio-tags',
@ -1378,6 +1389,8 @@ class Search extends Module {
'pricingcats',
'print_section',
'print_status',
'product_asset_class',
'product_name',
'programs',
'project-attributes',
'project-cat',
@ -1407,6 +1420,7 @@ class Search extends Module {
'pwb-brand',
'qmn_log_type',
'qualification',
'qualifications',
'quality',
'question-category',
'question-tag',
@ -1636,6 +1650,7 @@ class Search extends Module {
'wcs-room',
'wcs-type',
'wdca_ad_categories',
'webinar_type_cat',
'where',
'who',
'wiki-category',

View File

@ -0,0 +1,14 @@
# 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).
## 0.1.0 - 2022-11-01
### Added
- Adding the initial empty package
- Duplicate helper script code to a dedicated package
### Changed
- Updated package dependencies.

View File

@ -0,0 +1,357 @@
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===================================
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@ -0,0 +1,38 @@
# Security Policy
Full details of the Automattic Security Policy can be found on [automattic.com](https://automattic.com/security/).
## Supported Versions
Generally, only the latest version of Jetpack has continued support. If a critical vulnerability is found in the current version of Jetpack, we may opt to backport any patches to previous versions.
## Reporting a Vulnerability
[Jetpack](https://jetpack.com/) is an open-source plugin for WordPress. Our HackerOne program covers the plugin software, as well as a variety of related projects and infrastructure.
**For responsible disclosure of security issues and to be eligible for our bug bounty program, please submit your report via the [HackerOne](https://hackerone.com/automattic) portal.**
Our most critical targets are:
* Jetpack and the Jetpack composer packages (all within this repo)
* Jetpack.com -- the primary marketing site.
* cloud.jetpack.com -- a management site.
* wordpress.com -- the shared management site for both Jetpack and WordPress.com sites.
For more targets, see the `In Scope` section on [HackerOne](https://hackerone.com/automattic).
_Please note that the **WordPress software is a separate entity** from Automattic. Please report vulnerabilities for WordPress through [the WordPress Foundation's HackerOne page](https://hackerone.com/wordpress)._
## Guidelines
We're committed to working with security researchers to resolve the vulnerabilities they discover. You can help us by following these guidelines:
* Follow [HackerOne's disclosure guidelines](https://www.hackerone.com/disclosure-guidelines).
* Pen-testing Production:
* Please **setup a local environment** instead whenever possible. Most of our code is open source (see above).
* If that's not possible, **limit any data access/modification** to the bare minimum necessary to reproduce a PoC.
* **_Don't_ automate form submissions!** That's very annoying for us, because it adds extra work for the volunteers who manage those systems, and reduces the signal/noise ratio in our communication channels.
* To be eligible for a bounty, all of these guidelines must be followed.
* Be Patient - Give us a reasonable time to correct the issue before you disclose the vulnerability.
We also expect you to comply with all applicable laws. You're responsible to pay any taxes associated with your bounties.

View File

@ -0,0 +1,32 @@
<?php
/**
* Action Hooks for Jetpack Transport Helper module.
*
* @package automattic/jetpack-transport-helper
*/
// If WordPress's plugin API is available already, use it. If not,
// drop data into `$wp_filter` for `WP_Hook::build_preinitialized_hooks()`.
if ( function_exists( 'add_filter' ) ) {
$add_filter = 'add_filter';
$add_action = 'add_action';
} else {
$add_filter = function ( $name, $cb, $priority = 10, $accepted_args = 1 ) {
global $wp_filter;
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$wp_filter[ $name ][ $priority ][] = array(
'accepted_args' => $accepted_args,
'function' => $cb,
);
};
$add_action = $add_filter;
}
// Clean up expired Jetpack Helper Scripts from a scheduled event.
$add_action( 'jetpack_cleanup_helper_scripts', array( 'Automattic\\Jetpack\\Transport_Helper\\Helper_Script_Manager', 'cleanup_expired_helper_scripts' ) );
// Register REST routes.
$add_action( 'rest_api_init', array( 'Automattic\\Jetpack\\Transport_Helper\\REST_Controller', 'register_rest_routes' ) );
// Set up package version hook.
$add_filter( 'jetpack_package_versions', 'Automattic\\Jetpack\\Transport_Helper\\Package_Version::send_package_version_to_tracker' );

View File

@ -0,0 +1,58 @@
{
"name": "automattic/jetpack-transport-helper",
"description": "Package to help transport server communication",
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"automattic/jetpack-connection": "^1.46"
},
"require-dev": {
"yoast/phpunit-polyfills": "1.0.3",
"automattic/jetpack-changelogger": "^3.2",
"automattic/wordbless": "dev-master"
},
"autoload": {
"files": [
"actions.php"
],
"classmap": [
"src/"
]
},
"scripts": {
"phpunit": [
"./vendor/phpunit/phpunit/phpunit --colors=always"
],
"test-coverage": [
"php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/clover.xml\""
],
"test-php": [
"@composer phpunit"
],
"build-production": "echo 'Add your build step to composer.json, please!'",
"build-development": "echo 'Add your build step to composer.json, please!'",
"post-install-cmd": "WorDBless\\Composer\\InstallDropin::copy",
"post-update-cmd": "WorDBless\\Composer\\InstallDropin::copy"
},
"minimum-stability": "dev",
"prefer-stable": true,
"extra": {
"mirror-repo": "Automattic/jetpack-transport-helper",
"version-constants": {
"::PACKAGE_VERSION": "src/class-package-version.php"
},
"changelogger": {
"link-template": "https://github.com/Automattic/jetpack-transport-helper/compare/v${old}...v${new}"
},
"autotagger": true,
"branch-alias": {
"dev-trunk": "0.1.x-dev"
},
"textdomain": "jetpack-transport-helper"
},
"config": {
"allow-plugins": {
"roots/wordpress-core-installer": true
}
}
}

View File

@ -0,0 +1,346 @@
<?php
/**
* The Jetpack Helper Script Manager class.
*
* @package automattic/jetpack-transport-helper
*/
namespace Automattic\Jetpack\Transport_Helper;
/**
* Helper_Script_Manager manages installation, deletion and cleanup of Helper Scripts
*/
class Helper_Script_Manager {
const TEMP_DIRECTORY = 'jetpack-temp';
const HELPER_HEADER = "<?php /* Jetpack Backup Helper Script */\n";
const EXPIRY_TIME = 8 * 3600; // 8 hours
const MAX_FILESIZE = 1024 * 1024; // 1 MiB
const README_LINES = array(
'These files have been put on your server by Jetpack. They are cleaned up automatically when we no longer need them.',
'If you no longer have Jetpack connected to your site, you can delete them manually.',
'If you have questions or need assistance, please contact Jetpack Support at https://jetpack.com/support/',
'If you like to build amazing things with WordPress, you should visit automattic.com/jobs and apply to join the fun mention this file when you apply!;',
);
const INDEX_FILE = '<?php // Silence is golden';
/**
* Installs a Helper Script, and returns its filesystem path and access url.
*
* @access public
* @static
*
* @param string $script_body Helper Script file contents.
* @return array|WP_Error Either an array containing the path and url of the helper script, or an error.
*/
public static function install_helper_script( $script_body ) {
// Check that the script body contains the correct header.
if ( strncmp( $script_body, self::HELPER_HEADER, strlen( self::HELPER_HEADER ) ) !== 0 ) {
return new \WP_Error( 'invalid_helper', 'Invalid Helper Script header' );
}
// Refuse to install a Helper Script that is too large.
if ( strlen( $script_body ) > self::MAX_FILESIZE ) {
return new \WP_Error( 'invalid_helper', 'Invalid Helper Script size' );
}
// Replace '[wp_path]' in the Helper Script with the WordPress installation location. Allows the Helper Script to find WordPress.
$script_body = str_replace( '[wp_path]', addslashes( ABSPATH ), $script_body );
// Create a jetpack-temp directory for the Helper Script.
$temp_directory = self::create_temp_directory();
if ( \is_wp_error( $temp_directory ) ) {
return $temp_directory;
}
// Generate a random filename, avoid clashes.
$max_attempts = 5;
for ( $attempt = 0; $attempt < $max_attempts; $attempt++ ) {
$file_key = wp_generate_password( 10, false );
$file_name = 'jp-helper-' . $file_key . '.php';
$file_path = trailingslashit( $temp_directory['path'] ) . $file_name;
if ( ! file_exists( $file_path ) ) {
// Attempt to write helper script.
if ( ! self::put_contents( $file_path, $script_body ) ) {
if ( file_exists( $file_path ) ) {
unlink( $file_path );
}
continue;
}
// Always schedule a cleanup run shortly after EXPIRY_TIME.
\wp_schedule_single_event( time() + self::EXPIRY_TIME + 60, 'jetpack_cleanup_helper_scripts' );
// Success! Figure out the URL and return the path and URL.
return array(
'path' => $file_path,
'url' => trailingslashit( $temp_directory['url'] ) . $file_name,
);
}
}
return new \WP_Error( 'install_faied', 'Failed to install Helper Script' );
}
/**
* Given a path, verify it looks like a helper script and then delete it if so.
*
* @access public
* @static
*
* @param string $path Path to Helper Script to delete.
* @return boolean True if the file is deleted (or does not exist).
*/
public static function delete_helper_script( $path ) {
if ( ! file_exists( $path ) ) {
return true;
}
// Check this file looks like a JPR helper script.
if ( ! self::verify_file_header( $path, self::HELPER_HEADER ) ) {
return false;
}
return unlink( $path );
}
/**
* Search for Helper Scripts that are suspiciously old, and clean them out.
*
* @access public
* @static
*/
public static function cleanup_expired_helper_scripts() {
self::cleanup_helper_scripts( time() - self::EXPIRY_TIME );
}
/**
* Search for and delete all Helper Scripts. Used during uninstallation.
*
* @access public
* @static
*/
public static function delete_all_helper_scripts() {
self::cleanup_helper_scripts( null );
}
/**
* Search for and delete Helper Scripts. If an $expiry_time is specified, only delete Helper Scripts
* with an mtime older than $expiry_time. Otherwise, delete them all.
*
* @access public
* @static
*
* @param int|null $expiry_time If specified, only delete scripts older than $expiry_time.
*/
public static function cleanup_helper_scripts( $expiry_time = null ) {
foreach ( self::get_install_locations() as $directory => $url ) {
$temp_dir = trailingslashit( $directory ) . self::TEMP_DIRECTORY;
if ( is_dir( $temp_dir ) ) {
// Find expired helper scripts and delete them.
$helper_scripts = glob( trailingslashit( $temp_dir ) . 'jp-helper-*.php' );
if ( is_array( $helper_scripts ) ) {
foreach ( $helper_scripts as $filename ) {
if ( null === $expiry_time || filemtime( $filename ) < $expiry_time ) {
self::delete_helper_script( $filename );
}
}
}
// Delete the directory if it's empty now.
self::delete_empty_helper_directory( $temp_dir );
}
}
}
/**
* Delete a helper script directory if it's empty
*
* @access public
* @static
*
* @param string $dir Path to Helper Script directory.
* @return boolean True if the directory is deleted
*/
private static function delete_empty_helper_directory( $dir ) {
if ( ! is_dir( $dir ) ) {
return false;
}
// Tally the files in the target directory, and reject if there are too many.
$glob_path = trailingslashit( $dir ) . '*';
$dir_contents = glob( $glob_path );
if ( count( $dir_contents ) > 2 ) {
return false;
}
// Check that the only remaining files are a README and index.php generated by this system.
$allowed_files = array(
'README' => self::README_LINES[0],
'index.php' => self::INDEX_FILE,
);
foreach ( $dir_contents as $path ) {
$basename = basename( $path );
if ( ! isset( $allowed_files[ $basename ] ) ) {
return false;
}
// Verify the file starts with the expected contents.
if ( ! self::verify_file_header( $path, $allowed_files[ $basename ] ) ) {
return false;
}
if ( ! unlink( $path ) ) {
return false;
}
}
// If the directory is now empty, delete it.
if ( count( glob( $glob_path ) ) === 0 ) {
return rmdir( $dir );
}
return false;
}
/**
* Find an appropriate location for a jetpack-temp folder, and create one
*
* @access public
* @static
*
* @return WP_Error|array Array containing the url and path of the temp directory if successful, WP_Error if not.
*/
private static function create_temp_directory() {
foreach ( self::get_install_locations() as $directory => $url ) {
// Check if the install location is writeable.
if ( ! is_writeable( $directory ) ) {
continue;
}
// Create if one doesn't already exist.
$temp_dir = trailingslashit( $directory ) . self::TEMP_DIRECTORY;
if ( ! is_dir( $temp_dir ) ) {
if ( ! mkdir( $temp_dir ) ) {
continue;
}
// Temp directory created. Drop a README and index.php file in there.
self::write_supplementary_temp_files( $temp_dir );
}
return array(
'path' => trailingslashit( $directory ) . self::TEMP_DIRECTORY,
'url' => trailingslashit( $url ) . self::TEMP_DIRECTORY,
);
}
return new \WP_Error( 'temp_directory', 'Failed to create jetpack-temp directory' );
}
/**
* Write out an index.php file and a README file for a new jetpack-temp directory.
*
* @access public
* @static
*
* @param string $dir Path to Helper Script directory.
*/
private static function write_supplementary_temp_files( $dir ) {
$readme_path = trailingslashit( $dir ) . 'README';
self::put_contents( $readme_path, implode( "\n\n", self::README_LINES ) );
$index_path = trailingslashit( $dir ) . 'index.php';
self::put_contents( $index_path, self::INDEX_FILE );
}
/**
* Write a file to the specified location with the specified contents.
*
* @access private
* @static
*
* @param string $file_path Path to write to.
* @param string $contents File contents to write.
* @return boolean True if successfully written.
*/
private static function put_contents( $file_path, $contents ) {
global $wp_filesystem;
if ( ! function_exists( '\\WP_Filesystem' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
if ( ! \WP_Filesystem() ) {
return false;
}
return $wp_filesystem->put_contents( $file_path, $contents );
}
/**
* Checks that a file exists, is readable, and has the expected header.
*
* @access private
* @static
*
* @param string $file_path File to verify.
* @param string $expected_header Header that the file should have.
* @return boolean True if the file exists, is readable, and the header matches.
*/
private static function verify_file_header( $file_path, $expected_header ) {
global $wp_filesystem;
if ( ! function_exists( '\\WP_Filesystem' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
if ( ! \WP_Filesystem() ) {
return false;
}
// Verify the file exists and is readable.
if ( ! $wp_filesystem->exists( $file_path ) || ! $wp_filesystem->is_readable( $file_path ) ) {
return false;
}
// Verify that the file isn't too big or small.
$file_size = $wp_filesystem->size( $file_path );
if ( $file_size < strlen( $expected_header ) || $file_size > self::MAX_FILESIZE ) {
return false;
}
// Read the file and verify its header.
$contents = $wp_filesystem->get_contents( $file_path );
return ( strncmp( $contents, $expected_header, strlen( $expected_header ) ) === 0 );
}
/**
* Gets an associative array of possible places to install a jetpack-temp directory, along with the URL to access each.
*
* @access private
* @static
*
* @return array Array, with keys specifying the full path of install locations, and values with the equivalent URL.
*/
public static function get_install_locations() {
// Include WordPress root and wp-content.
$install_locations = array(
\ABSPATH => \get_site_url(),
\WP_CONTENT_DIR => \WP_CONTENT_URL,
);
// Include uploads folder.
$upload_dir_info = \wp_upload_dir();
$install_locations[ $upload_dir_info['basedir'] ] = $upload_dir_info['baseurl'];
return $install_locations;
}
}

View File

@ -0,0 +1,31 @@
<?php
/**
* The Package_Version class.
*
* @package automattic/jetpack-transport-helper
*/
namespace Automattic\Jetpack\Transport_Helper;
/**
* The Package_Version class.
*/
class Package_Version {
const PACKAGE_VERSION = '0.1.0';
const PACKAGE_SLUG = 'transport-helper';
/**
* Adds the package slug and version to the package version tracker's data.
*
* @param array $package_versions The package version array.
*
* @return array The packge version array.
*/
public static function send_package_version_to_tracker( $package_versions ) {
$package_versions[ self::PACKAGE_SLUG ] = self::PACKAGE_VERSION;
return $package_versions;
}
}

View File

@ -0,0 +1,144 @@
<?php
/**
* The Jetpack Helper Script Rest Controller class.
* Registers the REST routes.
*
* @package automattic/jetpack-transport-helper
*/
namespace Automattic\Jetpack\Transport_Helper;
use Automattic\Jetpack\Connection\Rest_Authentication;
use WP_Error;
use WP_REST_Request;
use WP_REST_Server;
/**
* Registers the REST routes.
*/
class REST_Controller {
/**
* Registers the REST routes.
*
* @access public
* @static
*/
public static function register_rest_routes() {
// Install a Helper Script to assist with fetching data.
register_rest_route(
'jetpack/v4',
// This route can be generalized once we no longer depend on the backup package
'/backup-helper-script',
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => __CLASS__ . '::install_helper_script',
'permission_callback' => __CLASS__ . '::helper_script_permissions_callback',
'args' => array(
'helper' => array(
'description' => __( 'base64 encoded Helper Script body.', 'jetpack-transport-helper' ),
'type' => 'string',
'required' => true,
),
),
)
);
// Delete a Helper Script.
register_rest_route(
'jetpack/v4',
// This route can be generalized once we no longer depend on the backup package
'/backup-helper-script',
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => __CLASS__ . '::delete_helper_script',
'permission_callback' => __CLASS__ . '::helper_script_permissions_callback',
'args' => array(
'path' => array(
'description' => __( 'Path to Helper Script', 'jetpack-transport-helper' ),
'type' => 'string',
'required' => true,
),
),
)
);
}
/**
* The Jetpack endpoints should only be available via site-level authentication.
* This means that the corresponding endpoints can only be accessible from WPCOM.
*
* @access public
* @static
*
* @return bool|WP_Error True if a blog token was used to sign the request, WP_Error otherwise.
*/
public static function helper_script_permissions_callback() {
if ( Rest_Authentication::is_signed_with_blog_token() ) {
return true;
}
$error_msg = esc_html__(
'You are not allowed to perform this action.',
'jetpack-transport-helper'
);
return new WP_Error( 'rest_forbidden', $error_msg, array( 'status' => rest_authorization_required_code() ) );
}
/**
* Install the Helper Script.
*
* @access public
* @static
*
* @param WP_REST_Request $request The request sent to the WP REST API.
* @return array|WP_Error Returns the result of Helper Script installation. Returns one of:
* - WP_Error on failure, or
* - An array with installation info on success:
* 'path' (string) The sinstallation path.
* 'url' (string) The access url.
* 'abspath' (string) The abspath.
*/
public static function install_helper_script( $request ) {
$helper_script = $request->get_param( 'helper' );
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
$helper_script = base64_decode( $helper_script );
if ( ! $helper_script ) {
return new WP_Error( 'invalid_args', __( 'Helper Script body must be base64 encoded', 'jetpack-transport-helper' ), 400 );
}
$installation_info = Helper_Script_Manager::install_helper_script( $helper_script );
Helper_Script_Manager::cleanup_expired_helper_scripts();
// Include ABSPATH with successful result.
if ( ! is_wp_error( $installation_info ) ) {
$installation_info['abspath'] = ABSPATH;
}
return rest_ensure_response( $installation_info );
}
/**
* Delete a Helper Script.
*
* @access public
* @static
*
* @param WP_REST_Request $request The request sent to the WP REST API.
* @return array An array with 'success' key indicating the result of the delete operation.
*/
public static function delete_helper_script( $request ) {
$path_to_helper_script = $request->get_param( 'path' );
$deleted = Helper_Script_Manager::delete_helper_script( $path_to_helper_script );
Helper_Script_Manager::cleanup_expired_helper_scripts();
return rest_ensure_response(
array(
'success' => $deleted,
)
);
}
}

View File

@ -4,49 +4,53 @@ return array(
'domain' => 'jetpack-protect',
'type' => 'plugins',
'packages' => array(
'jetpack-admin-ui' => array(
'path' => 'jetpack_vendor/automattic/jetpack-admin-ui',
'ver' => '0.2.12',
),
'jetpack-assets' => array(
'path' => 'jetpack_vendor/automattic/jetpack-assets',
'ver' => '1.17.25',
),
'jetpack-config' => array(
'path' => 'jetpack_vendor/automattic/jetpack-config',
'ver' => '1.9.6',
'ver' => '1.11.0',
),
'jetpack-connection' => array(
'path' => 'jetpack_vendor/automattic/jetpack-connection',
'ver' => '1.46.3',
),
'jetpack-idc' => array(
'path' => 'jetpack_vendor/automattic/jetpack-identity-crisis',
'ver' => '0.8.31',
),
'jetpack-jitm' => array(
'path' => 'jetpack_vendor/automattic/jetpack-jitm',
'ver' => '2.2.33',
),
'jetpack-licensing' => array(
'path' => 'jetpack_vendor/automattic/jetpack-licensing',
'ver' => '1.7.11',
),
'jetpack-my-jetpack' => array(
'path' => 'jetpack_vendor/automattic/jetpack-my-jetpack',
'ver' => '2.4.0',
),
'jetpack-password-checker' => array(
'path' => 'jetpack_vendor/automattic/jetpack-password-checker',
'ver' => '0.2.7',
),
'jetpack-plugins-installer' => array(
'path' => 'jetpack_vendor/automattic/jetpack-plugins-installer',
'ver' => '0.2.0',
),
'jetpack-admin-ui' => array(
'path' => 'jetpack_vendor/automattic/jetpack-admin-ui',
'ver' => '0.2.11',
),
'jetpack-assets' => array(
'path' => 'jetpack_vendor/automattic/jetpack-assets',
'ver' => '1.17.21',
),
'jetpack-jitm' => array(
'path' => 'jetpack_vendor/automattic/jetpack-jitm',
'ver' => '2.2.26',
),
'jetpack-password-checker' => array(
'path' => 'jetpack_vendor/automattic/jetpack-password-checker',
'ver' => '0.2.6',
),
'jetpack-idc' => array(
'path' => 'jetpack_vendor/automattic/jetpack-identity-crisis',
'ver' => '0.8.22',
),
'jetpack-connection' => array(
'path' => 'jetpack_vendor/automattic/jetpack-connection',
'ver' => '1.44.0',
),
'jetpack-licensing' => array(
'path' => 'jetpack_vendor/automattic/jetpack-licensing',
'ver' => '1.7.8',
),
'jetpack-sync' => array(
'path' => 'jetpack_vendor/automattic/jetpack-sync',
'ver' => '1.38.2',
'ver' => '1.43.0',
),
'jetpack-my-jetpack' => array(
'path' => 'jetpack_vendor/automattic/jetpack-my-jetpack',
'ver' => '2.0.3',
'jetpack-transport-helper' => array(
'path' => 'jetpack_vendor/automattic/jetpack-transport-helper',
'ver' => '0.1.0',
),
),
);

View File

@ -1,10 +1,10 @@
=== Jetpack Protect ===
Contributors: automattic, retrofox, leogermani, renatoagds, bjorsch, ebinnion, fgiannar, zinigor, miguelxavierpenha, dsmart, jeherve, manzoorwanijk, njweller, oskosk, samiff, siddarthan, wpkaren, arsihasi, kraftbj, kev, sermitr, kangzj, pabline
Contributors: automattic, retrofox, leogermani, renatoagds, bjorsch, ebinnion, fgiannar, zinigor, miguelxavierpenha, dsmart, jeherve, manzoorwanijk, njweller, oskosk, samiff, siddarthan, wpkaren, arsihasi, kraftbj, kev, sermitr, kangzj, pabline, dkmyta
Tags: jetpack, protect, security, malware, scan
Requires at least: 5.9
Requires at least: 6.0
Requires PHP: 5.6
Tested up to: 6.0
Stable tag: 1.0.4
Tested up to: 6.1
Stable tag: 1.1.1
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
@ -109,7 +109,7 @@ The features of the new Jetpack Protect plugin are not included in the [Jetpack
3. View all the found vulnerabilities in your site and learn how to fix them.
== Changelog ==
### 1.0.4 - 2022-08-29
#### Fixed
- Fixed NaN error that prevented the pricing interstitial from rendering.
## 1.1.1 - 2022-11-18
### Fixed
- Fix issue with plugin activation.

View File

@ -0,0 +1,56 @@
<?php
/**
* Class to handle Rewind
*
* @package automattic/jetpack-protect-plugin
*/
namespace Automattic\Jetpack\Protect;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Jetpack_Options;
/**
* Class that handles the rewind api call.
*/
class Credentials {
/**
* Get the rewind state, if no creds are set the state will be 'awaiting_for_credentials'
*
* @return bool
*/
public static function get_credential_array() {
$blog_id = Jetpack_Options::get_option( 'id' );
$is_connected = ( new Connection_Manager() )->is_connected();
if ( ! $blog_id || ! $is_connected ) {
return false;
}
$api_url = sprintf( '/sites/%d/scan', $blog_id );
$response = Client::wpcom_json_api_request_as_blog(
$api_url,
'2',
array( 'method' => 'GET' ),
null,
'wpcom'
);
$response_code = wp_remote_retrieve_response_code( $response );
if ( is_wp_error( $response ) || 200 !== $response_code ) {
return false;
}
$parsed_response = json_decode( $response['body'] );
if ( ! $parsed_response ) {
return false;
}
return isset( $parsed_response->credentials ) ? $parsed_response->credentials : array();
}
}

View File

@ -18,10 +18,16 @@ use Automattic\Jetpack\JITMS\JITM as JITM;
use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer;
use Automattic\Jetpack\My_Jetpack\Products as My_Jetpack_Products;
use Automattic\Jetpack\Plugins_Installer;
use Automattic\Jetpack\Protect\Credentials;
use Automattic\Jetpack\Protect\Plan;
use Automattic\Jetpack\Protect\Scan_Status;
use Automattic\Jetpack\Protect\Site_Health;
use Automattic\Jetpack\Protect\Status as Protect_Status;
use Automattic\Jetpack\Protect\Status;
use Automattic\Jetpack\Protect\Threats;
use Automattic\Jetpack\Status as Jetpack_Status;
use Automattic\Jetpack\Sync\Functions as Sync_Functions;
use Automattic\Jetpack\Sync\Sender;
/**
* Class Jetpack_Protect
*/
@ -32,8 +38,9 @@ class Jetpack_Protect {
*/
public function __construct() {
add_action( 'init', array( $this, 'init' ) );
add_action( '_admin_menu', array( $this, 'admin_page_init' ) );
// Init Jetpack packages and ConnectionUI.
// Init Jetpack packages
add_action(
'plugins_loaded',
function () {
@ -54,11 +61,13 @@ class Jetpack_Protect {
'jetpack_sync_modules' => array(
'Automattic\\Jetpack\\Sync\\Modules\\Options',
'Automattic\\Jetpack\\Sync\\Modules\\Callables',
'Automattic\\Jetpack\\Sync\\Modules\\Users',
),
'jetpack_sync_callable_whitelist' => array(
'get_plugins' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_plugins' ),
'get_themes' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_themes' ),
'wp_version' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'wp_version' ),
'main_network_site' => array( 'Automattic\\Jetpack\\Connection\\Urls', 'main_network_site_url' ),
'get_plugins' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_plugins' ),
'get_themes' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_themes' ),
'wp_version' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'wp_version' ),
),
'jetpack_sync_options_contentless' => array(),
'jetpack_sync_options_whitelist' => array(
@ -84,22 +93,6 @@ class Jetpack_Protect {
// Set up the REST authentication hooks.
Connection_Rest_Authentication::init();
$total_vuls = Protect_Status::get_total_vulnerabilities();
$menu_label = _x( 'Protect', 'The Jetpack Protect product name, without the Jetpack prefix', 'jetpack-protect' );
if ( $total_vuls ) {
$menu_label .= sprintf( ' <span class="update-plugins">%d</span>', $total_vuls );
}
$page_suffix = Admin_Menu::add_menu(
__( 'Jetpack Protect', 'jetpack-protect' ),
$menu_label,
'manage_options',
'jetpack-protect',
array( $this, 'plugin_settings_page' ),
99
);
add_action( 'load-' . $page_suffix, array( $this, 'admin_init' ) );
add_action( 'admin_bar_menu', array( $this, 'admin_bar' ), 65 );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) );
// Add custom WP REST API endoints.
@ -113,10 +106,25 @@ class Jetpack_Protect {
}
/**
* Initialize the admin resources.
* Initialize the admin page resources.
*/
public function admin_init() {
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
public function admin_page_init() {
$total_threats = Status::get_total_threats();
$menu_label = _x( 'Protect', 'The Jetpack Protect product name, without the Jetpack prefix', 'jetpack-protect' );
if ( $total_threats ) {
$menu_label .= sprintf( ' <span class="update-plugins">%d</span>', $total_threats );
}
$page_suffix = Admin_Menu::add_menu(
__( 'Jetpack Protect', 'jetpack-protect' ),
$menu_label,
'manage_options',
'jetpack-protect',
array( $this, 'plugin_settings_page' ),
99
);
add_action( 'load-' . $page_suffix, array( $this, 'enqueue_admin_scripts' ) );
}
/**
@ -165,18 +173,26 @@ class Jetpack_Protect {
*/
public function initial_state() {
global $wp_version;
return array(
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$refresh_status_from_wpcom = isset( $_GET['checkPlan'] );
$initial_state = array(
'apiRoot' => esc_url_raw( rest_url() ),
'apiNonce' => wp_create_nonce( 'wp_rest' ),
'registrationNonce' => wp_create_nonce( 'jetpack-registration-nonce' ),
'status' => Protect_Status::get_status(),
'status' => Status::get_status( $refresh_status_from_wpcom ),
'installedPlugins' => Plugins_Installer::get_plugins(),
'installedThemes' => Sync_Functions::get_themes(),
'wpVersion' => $wp_version,
'adminUrl' => admin_url( 'admin.php?page=jetpack-protect' ),
'securityBundle' => My_Jetpack_Products::get_product( 'security' ),
'adminUrl' => 'admin.php?page=jetpack-protect',
'siteSuffix' => ( new Jetpack_Status() )->get_site_suffix(),
'jetpackScan' => My_Jetpack_Products::get_product( 'scan' ),
'productData' => My_Jetpack_Products::get_product( 'protect' ),
'hasRequiredPlan' => Plan::has_required_plan(),
);
$initial_state['jetpackScan']['pricingForUi'] = Plan::get_product( 'jetpack_scan' );
return $initial_state;
}
/**
* Main plugin settings page.
@ -202,11 +218,11 @@ class Jetpack_Protect {
$manager = new Connection_Manager( 'jetpack-protect' );
$manager->remove_connection();
Protect_Status::delete_option();
Status::delete_option();
}
/**
* Create a shortcut on Admin Bar to show the total of vulnerabilities found.
* Create a shortcut on Admin Bar to show the total of threats found.
*
* @param object $wp_admin_bar The Admin Bar object.
* @return void
@ -216,7 +232,7 @@ class Jetpack_Protect {
return;
}
$total = Protect_Status::get_total_vulnerabilities();
$total = Status::get_total_threats();
if ( $total > 0 ) {
$args = array(
@ -224,8 +240,8 @@ class Jetpack_Protect {
'title' => '<span class="ab-icon jp-protect-icon"></span><span class="ab-label">' . $total . '</span>',
'href' => admin_url( 'admin.php?page=jetpack-protect' ),
'meta' => array(
// translators: %d is the number of vulnerabilities found.
'title' => sprintf( _n( '%d vulnerability found by Jetpack Protect', '%d vulnerabilities found by Jetpack Protect', $total, 'jetpack-protect' ), $total ),
// translators: %d is the number of threats found.
'title' => sprintf( _n( '%d threat found by Jetpack Protect', '%d threats found by Jetpack Protect', $total, 'jetpack-protect' ), $total ),
),
);
@ -239,6 +255,18 @@ class Jetpack_Protect {
* @return void
*/
public static function register_rest_endpoints() {
register_rest_route(
'jetpack-protect/v1',
'check-plan',
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => __CLASS__ . '::api_check_plan',
'permission_callback' => function () {
return current_user_can( 'manage_options' );
},
)
);
register_rest_route(
'jetpack-protect/v1',
'status',
@ -250,15 +278,208 @@ class Jetpack_Protect {
},
)
);
register_rest_route(
'jetpack-protect/v1',
'clear-scan-cache',
array(
'methods' => \WP_REST_SERVER::EDITABLE,
'callback' => __CLASS__ . '::api_clear_scan_cache',
'permission_callback' => function () {
return current_user_can( 'manage_options' );
},
)
);
register_rest_route(
'jetpack-protect/v1',
'ignore-threat',
array(
'methods' => \WP_REST_SERVER::EDITABLE,
'callback' => __CLASS__ . '::api_ignore_threat',
'permission_callback' => function () {
return current_user_can( 'manage_options' );
},
)
);
register_rest_route(
'jetpack-protect/v1',
'fix-threats',
array(
'methods' => \WP_REST_SERVER::EDITABLE,
'callback' => __CLASS__ . '::api_fix_threats',
'permission_callback' => function () {
return current_user_can( 'manage_options' );
},
)
);
register_rest_route(
'jetpack-protect/v1',
'fix-threats-status',
array(
'methods' => \WP_REST_SERVER::READABLE,
'callback' => __CLASS__ . '::api_fix_threats_status',
'permission_callback' => function () {
return current_user_can( 'manage_options' );
},
)
);
register_rest_route(
'jetpack-protect/v1',
'check-credentials',
array(
'methods' => \WP_REST_Server::EDITABLE,
'callback' => __CLASS__ . '::api_check_credentials',
'permission_callback' => function () {
return current_user_can( 'manage_options' );
},
)
);
register_rest_route(
'jetpack-protect/v1',
'scan',
array(
'methods' => \WP_REST_SERVER::EDITABLE,
'callback' => __CLASS__ . '::api_scan',
'permission_callback' => function () {
return current_user_can( 'manage_options' );
},
)
);
}
/**
* Return site plan data for the API endpoint
*
* @return WP_REST_Response
*/
public static function api_check_plan() {
$has_required_plan = Plan::has_required_plan();
return rest_ensure_response( $has_required_plan, 200 );
}
/**
* Return Protect Status for the API endpoint
*
* @param WP_REST_Request $request The request object.
*
* @return WP_REST_Response
*/
public static function api_get_status() {
$status = Protect_Status::get_status();
public static function api_get_status( $request ) {
$status = Status::get_status( $request['hard_refresh'] );
return rest_ensure_response( $status, 200 );
}
/**
* Clear the Scan_Status cache for the API endpoint
*
* @return WP_REST_Response
*/
public static function api_clear_scan_cache() {
$cache_cleared = Scan_Status::delete_option();
if ( ! $cache_cleared ) {
return new WP_REST_Response( 'An error occured while attempting to clear the Jetpack Scan cache.', 500 );
}
return new WP_REST_Response( 'Jetpack Scan cache cleared.' );
}
/**
* Ignores a threat for the API endpoint
*
* @param WP_REST_Request $request The request object.
*
* @return WP_REST_Response
*/
public static function api_ignore_threat( $request ) {
if ( ! $request['threat_id'] ) {
return new WP_REST_RESPONSE( 'Missing threat ID.', 400 );
}
$threat_ignored = Threats::ignore_threat( $request['threat_id'] );
if ( ! $threat_ignored ) {
return new WP_REST_Response( 'An error occured while attempting to ignore the threat.', 500 );
}
return new WP_REST_Response( 'Threat ignored.' );
}
/**
* Fixes threats for the API endpoint
*
* @param WP_REST_Request $request The request object.
*
* @return WP_REST_Response
*/
public static function api_fix_threats( $request ) {
if ( empty( $request['threat_ids'] ) ) {
return new WP_REST_RESPONSE( 'Missing threat IDs.', 400 );
}
$threats_fixed = Threats::fix_threats( $request['threat_ids'] );
if ( ! $threats_fixed ) {
return new WP_REST_Response( 'An error occured while attempting to fix the threat.', 500 );
}
return new WP_REST_Response( $threats_fixed );
}
/**
* Fixes threats for the API endpoint
*
* @param WP_REST_Request $request The request object.
*
* @return WP_REST_Response
*/
public static function api_fix_threats_status( $request ) {
if ( empty( $request['threat_ids'] ) ) {
return new WP_REST_RESPONSE( 'Missing threat IDs.', 400 );
}
$threats_fixed = Threats::fix_threats_status( $request['threat_ids'] );
if ( ! $threats_fixed ) {
return new WP_REST_Response( 'An error occured while attempting to get the fixer status of the threats.', 500 );
}
return new WP_REST_Response( $threats_fixed );
}
/**
* Checks if the site has credentials configured
*
* @return WP_REST_Response
*/
public static function api_check_credentials() {
$credential_array = Credentials::get_credential_array();
if ( ! isset( $credential_array ) ) {
return new WP_REST_Response( 'An error occured while attempting to fetch the credentials array', 500 );
}
return new WP_REST_Response( $credential_array );
}
/**
* Enqueues a scan for the API endpoint
*
* @return WP_REST_Response
*/
public static function api_scan() {
$scan_enqueued = Threats::scan();
if ( ! $scan_enqueued ) {
return new WP_REST_Response( 'An error occured while attempting to enqueue the scan.', 500 );
}
return new WP_REST_Response( 'Scan enqueued.' );
}
}

View File

@ -0,0 +1,111 @@
<?php
/**
* Class to handle the Protect plan
*
* @package automattic/jetpack-protect-plugin
*/
namespace Automattic\Jetpack\Protect;
use Automattic\Jetpack\Current_Plan;
/**
* The Plan class.
*/
class Plan {
/**
* The meta name used to store the cache date
*
* @var string
*/
const CACHE_DATE_META_NAME = 'protect-cache-date';
/**
* Valid pediord for the cache: One week.
*/
const CACHE_VALIDITY_PERIOD = 7 * DAY_IN_SECONDS;
/**
* The meta name used to store the cache
*
* @var string
*/
const CACHE_META_NAME = 'protect-cache';
/**
* Checks if the cache is old, meaning we need to fetch new data from WPCOM
*/
private static function is_cache_old() {
if ( empty( self::get_product_from_cache() ) ) {
return true;
}
$cache_date = get_user_meta( get_current_user_id(), self::CACHE_DATE_META_NAME, true );
return time() - (int) $cache_date > ( self::CACHE_VALIDITY_PERIOD );
}
/**
* Gets the product list from the user cache
*/
private static function get_product_from_cache() {
return get_user_meta( get_current_user_id(), self::CACHE_META_NAME, true );
}
/**
* Gets the product data
*
* @param string $wpcom_product The product slug.
* @return array
*/
public static function get_product( $wpcom_product = 'jetpack_scan' ) {
if ( ! self::is_cache_old() ) {
return self::get_product_from_cache();
}
$request_url = 'https://public-api.wordpress.com/rest/v1.1/products?locale=' . get_user_locale() . '&type=jetpack';
$wpcom_request = wp_remote_get( esc_url_raw( $request_url ) );
$response_code = wp_remote_retrieve_response_code( $wpcom_request );
if ( 200 === $response_code ) {
$products = json_decode( wp_remote_retrieve_body( $wpcom_request ) );
// Pick the desired product...
$product = $products->{$wpcom_product};
// ... and store it into the cache.
update_user_meta( get_current_user_id(), self::CACHE_DATE_META_NAME, time() );
update_user_meta( get_current_user_id(), self::CACHE_META_NAME, $product );
return $product;
}
return new \WP_Error(
'failed_to_fetch_data',
esc_html__( 'Unable to fetch the requested data.', 'jetpack-protect' ),
array(
'status' => $response_code,
'request' => $wpcom_request,
)
);
}
/**
* Has Required Plan
*
* @param bool $force_refresh Refresh the local plan cache from wpcom.
* @return bool True when the site has a plan or product that supports the paid Protect tier.
*/
public static function has_required_plan( $force_refresh = false ) {
static $has_scan = null;
if ( null === $has_scan || $force_refresh ) {
$products = array_column( Current_Plan::get_products(), 'product_slug' );
// Check for a plan or product that enables scan.
$plan_supports_scan = Current_Plan::supports( 'scan', true );
$has_scan_product = count( array_intersect( array( 'jetpack_scan', 'jetpack_scan_monthly' ), $products ) ) > 0;
$has_scan = $plan_supports_scan || $has_scan_product;
}
return $has_scan;
}
}

View File

@ -0,0 +1,259 @@
<?php
/**
* Class to handle the Protect Status of Jetpack Protect
*
* @package automattic/jetpack-protect-plugin
*/
namespace Automattic\Jetpack\Protect;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Plugins_Installer;
use Automattic\Jetpack\Redirect;
use Automattic\Jetpack\Sync\Functions as Sync_Functions;
use Jetpack_Options;
use WP_Error;
/**
* Class that handles fetching and caching the Status of vulnerabilities check from the WPCOM servers
*/
class Protect_Status extends Status {
/**
* WPCOM endpoint
*
* @var string
*/
const REST_API_BASE = '/sites/%d/jetpack-protect-status';
/**
* Name of the option where status is stored
*
* @var string
*/
const OPTION_NAME = 'jetpack_protect_status';
/**
* Name of the option where the timestamp of the status is stored
*
* @var string
*/
const OPTION_TIMESTAMP_NAME = 'jetpack_protect_status_time';
/**
* Gets the current status of the Jetpack Protect checks
*
* @param bool $refresh_from_wpcom Refresh the local plan and status cache from wpcom.
* @return Status_Model
*/
public static function get_status( $refresh_from_wpcom = false ) {
if ( self::$status !== null ) {
return self::$status;
}
if ( $refresh_from_wpcom || ! self::should_use_cache() || self::is_cache_expired() ) {
$status = self::fetch_from_server();
} else {
$status = self::get_from_options();
}
if ( is_wp_error( $status ) ) {
$status = new Status_Model(
array(
'error' => true,
'error_code' => $status->get_error_code(),
'error_message' => $status->get_error_message(),
)
);
} else {
$status = self::normalize_protect_report_data( $status );
}
self::$status = $status;
return $status;
}
/**
* Gets the WPCOM API endpoint
*
* @return WP_Error|string
*/
public static function get_api_url() {
$blog_id = Jetpack_Options::get_option( 'id' );
$is_connected = ( new Connection_Manager() )->is_connected();
if ( ! $blog_id || ! $is_connected ) {
return new WP_Error( 'site_not_connected' );
}
$api_url = sprintf( self::REST_API_BASE, $blog_id );
return $api_url;
}
/**
* Fetches the status from WPCOM servers
*
* @return WP_Error|array
*/
public static function fetch_from_server() {
$api_url = self::get_api_url();
if ( is_wp_error( $api_url ) ) {
return $api_url;
}
$response = Client::wpcom_json_api_request_as_blog(
self::get_api_url(),
'2',
array( 'method' => 'GET' ),
null,
'wpcom'
);
$response_code = wp_remote_retrieve_response_code( $response );
if ( is_wp_error( $response ) || 200 !== $response_code || empty( $response['body'] ) ) {
return new WP_Error( 'failed_fetching_status', 'Failed to fetch Protect Status data from server', array( 'status' => $response_code ) );
}
$body = json_decode( wp_remote_retrieve_body( $response ) );
self::update_option( maybe_serialize( $body ) );
return $body;
}
/**
* Normalize data from the Protect Report data source.
*
* @param object $report_data Data from the Protect Report.
* @return Status_Model
*/
protected static function normalize_protect_report_data( $report_data ) {
$status = new Status_Model();
$status->data_source = 'protect_report';
// map report data properties directly into the Status_Model
$status->status = isset( $report_data->status ) ? $report_data->status : null;
$status->last_checked = isset( $report_data->last_checked ) ? $report_data->last_checked : null;
$status->num_threats = isset( $report_data->num_vulnerabilities ) ? $report_data->num_vulnerabilities : null;
$status->num_themes_threats = isset( $report_data->num_themes_vulnerabilities ) ? $report_data->num_themes_vulnerabilities : null;
$status->num_plugins_threats = isset( $report_data->num_plugins_vulnerabilities ) ? $report_data->num_plugins_vulnerabilities : null;
// merge plugins from report with all installed plugins before mapping into the Status_Model
$installed_plugins = Plugins_Installer::get_plugins();
$last_report_plugins = isset( $report_data->plugins ) ? $report_data->plugins : new \stdClass();
$status->plugins = self::merge_installed_and_checked_lists( $installed_plugins, $last_report_plugins, array( 'type' => 'plugins' ) );
// merge themes from report with all installed plugins before mapping into the Status_Model
$installed_themes = Sync_Functions::get_themes();
$last_report_themes = isset( $report_data->themes ) ? $report_data->themes : new \stdClass();
$status->themes = self::merge_installed_and_checked_lists( $installed_themes, $last_report_themes, array( 'type' => 'themes' ) );
// normalize WordPress core report data and map into Status_Model
$status->core = self::normalize_core_information( isset( $report_data->core ) ? $report_data->core : new \stdClass() );
// check if any installed items (themes, plugins, or core) have not been checked in the report
$all_items = array_merge( $status->plugins, $status->themes, array( $status->core ) );
$unchecked_items = array_filter(
$all_items,
function ( $item ) {
return ! isset( $item->checked ) || ! $item->checked;
}
);
$status->has_unchecked_items = ! empty( $unchecked_items );
return $status;
}
/**
* Merges the list of installed extensions with the list of extensions that were checked for known vulnerabilities and return a normalized list to be used in the UI
*
* @param array $installed The list of installed extensions, where each attribute key is the extension slug.
* @param object $checked The list of checked extensions.
* @param array $append Additional data to append to each result in the list.
* @return array Normalized list of extensions.
*/
protected static function merge_installed_and_checked_lists( $installed, $checked, $append ) {
$new_list = array();
foreach ( array_keys( $installed ) as $slug ) {
$checked = (object) $checked;
$extension = new Extension_Model(
array_merge(
array(
'name' => $installed[ $slug ]['Name'],
'version' => $installed[ $slug ]['Version'],
'slug' => $slug,
'threats' => array(),
'checked' => false,
),
$append
)
);
if ( isset( $checked->{ $slug } ) && $checked->{ $slug }->version === $installed[ $slug ]['Version'] ) {
$extension->version = $checked->{ $slug }->version;
$extension->checked = true;
if ( is_array( $checked->{ $slug }->vulnerabilities ) ) {
foreach ( $checked->{ $slug }->vulnerabilities as $threat ) {
$extension->threats[] = new Threat_Model(
array(
'id' => $threat->id,
'title' => $threat->title,
'fixed_in' => $threat->fixed_in,
'description' => isset( $threat->description ) ? $threat->description : null,
'source' => isset( $threat->id ) ? Redirect::get_url( 'jetpack-protect-vul-info', array( 'path' => $threat->id ) ) : null,
)
);
}
}
}
$new_list[] = $extension;
}
$new_list = parent::sort_threats( $new_list );
return $new_list;
}
/**
* Check if the WordPress version that was checked matches the current installed version.
*
* @param object $core_check The object returned by Protect wpcom endpoint.
* @return object The object representing the current status of core checks.
*/
protected static function normalize_core_information( $core_check ) {
global $wp_version;
$core = new Extension_Model(
array(
'type' => 'core',
'name' => 'WordPress',
'version' => $wp_version,
'checked' => false,
)
);
if ( isset( $core_check->version ) && $core_check->version === $wp_version ) {
if ( is_array( $core_check->vulnerabilities ) ) {
$core->checked = true;
$core->set_threats(
array_map(
function ( $vulnerability ) {
$vulnerability->source = isset( $vulnerability->id ) ? Redirect::get_url( 'jetpack-protect-vul-info', array( 'path' => $vulnerability->id ) ) : null;
return $vulnerability;
},
$core_check->vulnerabilities
)
);
}
}
return $core;
}
}

View File

@ -0,0 +1,359 @@
<?php
/**
* Class to handle the Scan Status of Jetpack Protect
*
* @package automattic/jetpack-protect-plugin
*/
namespace Automattic\Jetpack\Protect;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Plugins_Installer;
use Automattic\Jetpack\Sync\Functions as Sync_Functions;
use Jetpack_Options;
use WP_Error;
/**
* Class that handles fetching of threats from the Scan API
*/
class Scan_Status extends Status {
/**
* Scan endpoint
*
* @var string
*/
const SCAN_API_BASE = '/sites/%d/scan';
/**
* Name of the option where status is stored
*
* @var string
*/
const OPTION_NAME = 'jetpack_scan_status';
/**
* Name of the option where the timestamp of the status is stored
*
* @var string
*/
const OPTION_TIMESTAMP_NAME = 'jetpack_scan_status_timestamp';
/**
* Time in seconds that the cache should last
*
* @var int
*/
const OPTION_EXPIRES_AFTER = 300; // 5 minutes.
/**
* Gets the current status of the Jetpack Protect checks
*
* @param bool $refresh_from_wpcom Refresh the local plan and status cache from wpcom.
* @return Status_Model
*/
public static function get_status( $refresh_from_wpcom = false ) {
if ( self::$status !== null ) {
return self::$status;
}
if ( $refresh_from_wpcom || ! self::should_use_cache() || self::is_cache_expired() ) {
$status = self::fetch_from_api();
} else {
$status = self::get_from_options();
}
if ( is_wp_error( $status ) ) {
$status = new Status_Model(
array(
'error' => true,
'error_code' => $status->get_error_code(),
'error_message' => $status->get_error_message(),
)
);
} else {
$status = self::normalize_api_data( $status );
}
self::$status = $status;
return $status;
}
/**
* Gets the Scan API endpoint
*
* @return WP_Error|string
*/
public static function get_api_url() {
$blog_id = Jetpack_Options::get_option( 'id' );
$is_connected = ( new Connection_Manager() )->is_connected();
if ( ! $blog_id || ! $is_connected ) {
return new WP_Error( 'site_not_connected' );
}
$api_url = sprintf( self::SCAN_API_BASE, $blog_id );
return $api_url;
}
/**
* Fetches the status data from the Scan API
*
* @return WP_Error|array
*/
public static function fetch_from_api() {
$api_url = self::get_api_url();
if ( is_wp_error( $api_url ) ) {
return $api_url;
}
$response = Client::wpcom_json_api_request_as_blog(
self::get_api_url(),
'2',
array( 'method' => 'GET' ),
null,
'wpcom'
);
$response_code = wp_remote_retrieve_response_code( $response );
if ( is_wp_error( $response ) || 200 !== $response_code || empty( $response['body'] ) ) {
return new WP_Error( 'failed_fetching_status', 'Failed to fetch Scan data from the server', array( 'status' => $response_code ) );
}
$body = json_decode( wp_remote_retrieve_body( $response ) );
self::update_option( maybe_serialize( $body ) );
return $body;
}
/**
* Normalize API Data
* Formats the payload from the Scan API into an instance of Status_Model.
*
* @param object $scan_data The data returned by the scan API.
*
* @return Status_Model
*/
private static function normalize_api_data( $scan_data ) {
global $wp_version;
$status = new Status_Model();
$status->data_source = 'scan_api';
$status->status = isset( $scan_data->state ) ? $scan_data->state : null;
$status->num_threats = 0;
$status->num_themes_threats = 0;
$status->num_plugins_threats = 0;
$status->has_unchecked_items = false;
if ( ! empty( $scan_data->most_recent->timestamp ) ) {
$date = new \DateTime( $scan_data->most_recent->timestamp );
if ( $date ) {
$status->last_checked = $date->format( 'Y-m-d H:i:s' );
}
}
$status->core = new Extension_Model(
array(
'type' => 'core',
'name' => 'WordPress',
'version' => $wp_version,
'checked' => true, // to do: default to false once Scan API has manifest
)
);
if ( isset( $scan_data->threats ) && is_array( $scan_data->threats ) ) {
foreach ( $scan_data->threats as $threat ) {
if ( isset( $threat->extension->type ) ) {
if ( 'plugin' === $threat->extension->type ) {
// add the extension if it does not yet exist in the status
if ( ! isset( $status->plugins[ $threat->extension->slug ] ) ) {
$status->plugins[ $threat->extension->slug ] = new Extension_Model(
array(
'name' => isset( $threat->extension->name ) ? $threat->extension->name : null,
'slug' => isset( $threat->extension->slug ) ? $threat->extension->slug : null,
'version' => isset( $threat->extension->version ) ? $threat->extension->version : null,
'type' => 'plugin',
'checked' => true,
'threats' => array(),
)
);
}
$status->plugins[ $threat->extension->slug ]->threats[] = new Threat_Model(
array(
'id' => isset( $threat->id ) ? $threat->id : null,
'signature' => isset( $threat->signature ) ? $threat->signature : null,
'title' => isset( $threat->title ) ? $threat->title : null,
'description' => isset( $threat->description ) ? $threat->description : null,
'vulnerability_description' => isset( $threat->vulnerability_description ) ? $threat->vulnerability_description : null,
'fix_description' => isset( $threat->fix_description ) ? $threat->fix_description : null,
'payload_subtitle' => isset( $threat->payload_subtitle ) ? $threat->payload_subtitle : null,
'payload_description' => isset( $threat->payload_description ) ? $threat->payload_description : null,
'first_detected' => isset( $threat->first_detected ) ? $threat->first_detected : null,
'fixed_in' => isset( $threat->fixer->fixer ) && 'update' === $threat->fixer->fixer ? $threat->fixer->target : null,
'severity' => isset( $threat->severity ) ? $threat->severity : null,
'fixable' => isset( $threat->fixer ) ? $threat->fixer : null,
'status' => isset( $threat->status ) ? $threat->status : null,
'filename' => isset( $threat->filename ) ? $threat->filename : null,
'context' => isset( $threat->context ) ? $threat->context : null,
'source' => isset( $threat->source ) ? $threat->source : null,
)
);
$status->num_threats++;
$status->num_plugins_threats++;
continue;
}
if ( 'theme' === $threat->extension->type ) {
// add the extension if it does not yet exist in the status
if ( ! isset( $status->themes[ $threat->extension->slug ] ) ) {
$status->themes[ $threat->extension->slug ] = new Extension_Model(
array(
'name' => isset( $threat->extension->name ) ? $threat->extension->name : null,
'slug' => isset( $threat->extension->slug ) ? $threat->extension->slug : null,
'version' => isset( $threat->extension->version ) ? $threat->extension->version : null,
'type' => 'theme',
'checked' => true,
'threats' => array(),
)
);
}
$status->themes[ $threat->extension->slug ]->threats[] = new Threat_Model(
array(
'id' => isset( $threat->id ) ? $threat->id : null,
'signature' => isset( $threat->signature ) ? $threat->signature : null,
'title' => isset( $threat->title ) ? $threat->title : null,
'description' => isset( $threat->description ) ? $threat->description : null,
'vulnerability_description' => isset( $threat->vulnerability_description ) ? $threat->vulnerability_description : null,
'fix_description' => isset( $threat->fix_description ) ? $threat->fix_description : null,
'payload_subtitle' => isset( $threat->payload_subtitle ) ? $threat->payload_subtitle : null,
'payload_description' => isset( $threat->payload_description ) ? $threat->payload_description : null,
'first_detected' => isset( $threat->first_detected ) ? $threat->first_detected : null,
'fixed_in' => isset( $threat->fixer->fixer ) && 'update' === $threat->fixer->fixer ? $threat->fixer->target : null,
'severity' => isset( $threat->severity ) ? $threat->severity : null,
'fixable' => isset( $threat->fixer ) ? $threat->fixer : null,
'status' => isset( $threat->status ) ? $threat->status : null,
'filename' => isset( $threat->filename ) ? $threat->filename : null,
'context' => isset( $threat->context ) ? $threat->context : null,
'source' => isset( $threat->source ) ? $threat->source : null,
)
);
$status->num_threats++;
$status->num_themes_threats++;
continue;
}
}
if ( isset( $threat->signature ) && 'Vulnerable.WP.Core' === $threat->signature ) {
if ( $threat->version !== $wp_version ) {
continue;
}
$status->core->threats[] = new Threat_Model(
array(
'id' => $threat->id,
'signature' => $threat->signature,
'title' => $threat->title,
'description' => $threat->description,
'first_detected' => $threat->first_detected,
'severity' => $threat->severity,
)
);
$status->num_threats++;
continue;
}
if ( ! empty( $threat->filename ) ) {
$status->files[] = new Threat_Model( $threat );
$status->num_threats++;
continue;
}
if ( ! empty( $threat->table ) ) {
$status->database[] = new Threat_Model( $threat );
$status->num_threats++;
continue;
}
}
}
$installed_plugins = Plugins_Installer::get_plugins();
$status->plugins = self::merge_installed_and_checked_lists( $installed_plugins, $status->plugins, array( 'type' => 'plugins' ), true );
$installed_themes = Sync_Functions::get_themes();
$status->themes = self::merge_installed_and_checked_lists( $installed_themes, $status->themes, array( 'type' => 'themes' ), true );
foreach ( array_merge( $status->themes, $status->plugins ) as $extension ) {
if ( ! $extension->checked ) {
$status->has_unchecked_items = true;
break;
}
}
return $status;
}
/**
* Merges the list of installed extensions with the list of extensions that were checked for known vulnerabilities and return a normalized list to be used in the UI
*
* @param array $installed The list of installed extensions, where each attribute key is the extension slug.
* @param object $checked The list of checked extensions.
* @param array $append Additional data to append to each result in the list.
* @return array Normalized list of extensions.
*/
protected static function merge_installed_and_checked_lists( $installed, $checked, $append ) {
$new_list = array();
$checked = (object) $checked;
foreach ( array_keys( $installed ) as $slug ) {
/**
* Extension Type Map
*
* @var array $extension_type_map Key value pairs of extension types and their corresponding
* identifier used by the Scan API data source.
*/
$extension_type_map = array(
'themes' => 'r1',
'plugins' => 'r2',
);
$version = $installed[ $slug ]['Version'];
$short_slug = str_replace( '.php', '', explode( '/', $slug )[0] );
$scanifest_slug = $extension_type_map[ $append['type'] ] . ":$short_slug@$version";
$extension = new Extension_Model(
array_merge(
array(
'name' => $installed[ $slug ]['Name'],
'version' => $version,
'slug' => $slug,
'threats' => array(),
'checked' => false,
),
$append
)
);
if ( ! isset( $checked->extensions ) // no extension data available from Scan API
|| is_array( $checked->extensions ) && in_array( $scanifest_slug, $checked->extensions, true ) // extension data matches Scan API
) {
$extension->checked = true;
if ( isset( $checked->{ $short_slug }->threats ) ) {
$extension->threats = $checked->{ $short_slug }->threats;
}
}
$new_list[] = $extension;
}
$new_list = parent::sort_threats( $new_list );
return $new_list;
}
}

View File

@ -10,7 +10,7 @@ namespace Automattic\Jetpack\Protect;
/**
* Site_Health.
*
* Displays vulnerabilities in WordPress site health page.
* Displays threats in WordPress site health page.
*/
class Site_Health {
@ -50,20 +50,20 @@ class Site_Health {
* @return array
*/
public static function do_checks() {
$total_vuls = Status::get_total_vulnerabilities();
$vulns = Status::get_all_vulnerabilities();
$vulns = array_map(
$total_threats = Status::get_total_threats();
$threats = Status::get_all_threats();
$threats = array_map(
function ( $v ) {
return $v->title;
},
$vulns
$threats
);
/**
* Default, no vulnerabilities found
* Default, no threats found
*/
$result = array(
'label' => __( 'No known vulnerabilities found', 'jetpack-protect' ),
'label' => __( 'No known threats found', 'jetpack-protect' ),
'status' => 'good',
'badge' => array(
'label' => __( 'Security', 'jetpack-protect' ),
@ -71,25 +71,25 @@ class Site_Health {
),
'description' => sprintf(
'<p>%s</p>',
__( 'Jetpack Protect did not find any known vulnerabilities in your site. Vulnerabilities can be exploited by hackers and cause harm to your website.', 'jetpack-protect' )
__( 'Jetpack Protect did not find any known threats in your site. Threats can be exploited by hackers and cause harm to your website.', 'jetpack-protect' )
),
'actions' => '',
'test' => 'jetpack_protect_checks',
);
/**
* If vulnerabilities found.
* If threats found.
*/
if ( $total_vuls ) {
if ( $total_threats ) {
$result['status'] = 'critical';
/* translators: $d is the number of vulnerabilities found. */
$result['label'] = sprintf( _n( 'Your site is affected by %d security vulnerability', 'Your site is affected by %d security vulnerabilities', $total_vuls, 'jetpack-protect' ), $total_vuls );
$result['description'] = __( 'Jetpack Protect detected the following security vulnerabilities in your site:', 'jetpack-protect' );
/* translators: $d is the number of threats found. */
$result['label'] = sprintf( _n( 'Your site is affected by %d security threat', 'Your site is affected by %d security threats', $total_threats, 'jetpack-protect' ), $total_threats );
$result['description'] = __( 'Jetpack Protect detected the following security threats in your site:', 'jetpack-protect' );
foreach ( $vulns as $vuln ) {
foreach ( $threats as $threat ) {
$result['description'] .= '<p>';
$result['description'] .= "<span class='dashicons dashicons-warning' style='color: crimson;'></span> &nbsp";
$result['description'] .= wp_kses( $vuln, array( 'a' => array( 'href' => array() ) ) ); // Only allow a href HTML tags.
$result['description'] .= wp_kses( $threat, array( 'a' => array( 'href' => array() ) ) ); // Only allow a href HTML tags.
$result['description'] .= '</p>';
}
$result['description'] .= '<p>';

View File

@ -7,38 +7,23 @@
namespace Automattic\Jetpack\Protect;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Plugins_Installer;
use Automattic\Jetpack\Sync\Functions as Sync_Functions;
use Jetpack_Options;
use WP_Error;
/**
* Class that handles fetching and caching the Status of vulnerabilities check from the WPCOM servers
*/
class Status {
/**
* WPCOM endpoint
*
* @var string
*/
const REST_API_BASE = '/sites/%d/jetpack-protect-status';
/**
* Name of the option where status is stored
*
* @var string
*/
const OPTION_NAME = 'jetpack_protect_status';
const OPTION_NAME = '';
/**
* Name of the option where the timestamp of the status is stored
*
* @var string
*/
const OPTION_TIMESTAMP_NAME = 'jetpack_protect_status_time';
const OPTION_TIMESTAMP_NAME = '';
/**
* Time in seconds that the cache should last
@ -57,121 +42,31 @@ class Status {
/**
* Memoization for the current status
*
* @var null|array
* @var null|Status_Model
*/
public static $status = null;
/**
* Gets the current status of the Jetpack Protect checks
*
* @return array
* @param bool $refresh_from_wpcom Refresh the local plan and status cache from wpcom.
* @return Status_Model
*/
public static function get_status() {
if ( self::$status !== null ) {
return self::$status;
}
public static function get_status( $refresh_from_wpcom = false ) {
$use_scan_status = Plan::has_required_plan();
if ( ! self::should_use_cache() || self::is_cache_expired() ) {
$status = self::fetch_from_server();
} else {
$status = self::get_from_options();
}
if ( defined( 'JETPACK_PROTECT_DEV__DATA_SOURCE' ) ) {
if ( 'scan_api' === JETPACK_PROTECT_DEV__DATA_SOURCE ) {
$use_scan_status = true;
}
if ( is_wp_error( $status ) ) {
$status = array(
'error' => true,
'error_code' => $status->get_error_code(),
'error_message' => $status->get_error_message(),
);
} else {
$status = self::normalize_report_data( $status );
}
self::$status = $status;
return $status;
}
/**
* Checks the current status to see if there are any vulnerabilities found
*
* @return boolean
*/
public static function has_vulnerabilities() {
return 0 < self::get_total_vulnerabilities();
}
/**
* Gets the total number of vulnerabilities found
*
* @return integer
*/
public static function get_total_vulnerabilities() {
$status = self::get_status();
return isset( $status->num_vulnerabilities ) && is_int( $status->num_vulnerabilities ) ? $status->num_vulnerabilities : 0;
}
/**
* Get all vulnerabilities combined
*
* @return array
*/
public static function get_all_vulnerabilities() {
return array_merge(
self::get_wordpress_vulnerabilities(),
self::get_themes_vulnerabilities(),
self::get_plugins_vulnerabilities()
);
}
/**
* Get vulnerabilities found for WordPress core
*
* @return array
*/
public static function get_wordpress_vulnerabilities() {
return self::get_vulnerabilities( 'core' );
}
/**
* Get vulnerabilities found for themes
*
* @return array
*/
public static function get_themes_vulnerabilities() {
return self::get_vulnerabilities( 'themes' );
}
/**
* Get vulnerabilities found for plugins
*
* @return array
*/
public static function get_plugins_vulnerabilities() {
return self::get_vulnerabilities( 'plugins' );
}
/**
* Get the vulnerabilities for one type of extension or core
*
* @param string $type What vulnerabilities you want to get. Possible values are 'core', 'themes' and 'plugins'.
*
* @return array
*/
public static function get_vulnerabilities( $type ) {
$status = self::get_status();
if ( 'core' === $type ) {
return isset( $status->$type ) && ! empty( $status->$type->vulnerabilities ) ? $status->$type->vulnerabilities : array();
}
$vuls = array();
if ( isset( $status->$type ) ) {
foreach ( (array) $status->$type as $item ) {
if ( ! empty( $item->vulnerabilities ) ) {
$vuls = array_merge( $vuls, $item->vulnerabilities );
}
if ( 'protect_report' === JETPACK_PROTECT_DEV__DATA_SOURCE ) {
$use_scan_status = false;
}
}
return $vuls;
self::$status = $use_scan_status ? Scan_Status::get_status( $refresh_from_wpcom ) : Protect_Status::get_status( $refresh_from_wpcom );
return self::$status;
}
/**
@ -180,7 +75,7 @@ class Status {
* @return boolean
*/
public static function is_cache_expired() {
$option_timestamp = get_option( self::OPTION_TIMESTAMP_NAME );
$option_timestamp = get_option( static::OPTION_TIMESTAMP_NAME );
if ( ! $option_timestamp ) {
return true;
@ -198,61 +93,13 @@ class Status {
return defined( 'JETPACK_PROTECT_DEV__BYPASS_CACHE' ) && JETPACK_PROTECT_DEV__BYPASS_CACHE ? false : true;
}
/**
* Gets the WPCOM API endpoint
*
* @return WP_Error|string
*/
public static function get_api_url() {
$blog_id = Jetpack_Options::get_option( 'id' );
$is_connected = ( new Connection_Manager() )->is_connected();
if ( ! $blog_id || ! $is_connected ) {
return new WP_Error( 'site_not_connected' );
}
$api_url = sprintf( self::REST_API_BASE, $blog_id );
return $api_url;
}
/**
* Fetches the status from WPCOM servers
*
* @return WP_Error|array
*/
public static function fetch_from_server() {
$api_url = self::get_api_url();
if ( is_wp_error( $api_url ) ) {
return $api_url;
}
$response = Client::wpcom_json_api_request_as_blog(
self::get_api_url(),
'2',
array( 'method' => 'GET' ),
null,
'wpcom'
);
$response_code = wp_remote_retrieve_response_code( $response );
if ( is_wp_error( $response ) || 200 !== $response_code || empty( $response['body'] ) ) {
return new WP_Error( 'failed_fetching_status', 'Failed to fetch Protect Status data from server', array( 'status' => $response_code ) );
}
$body = json_decode( wp_remote_retrieve_body( $response ) );
self::update_option( $body );
return $body;
}
/**
* Gets the current cached status
*
* @return bool|array False if value is not found. Array with values if cache is found.
*/
public static function get_from_options() {
return get_option( self::OPTION_NAME );
return maybe_unserialize( get_option( static::OPTION_NAME ) );
}
/**
@ -263,9 +110,9 @@ class Status {
*/
public static function update_option( $status ) {
// TODO: Sanitize $status.
update_option( self::OPTION_NAME, $status );
update_option( static::OPTION_NAME, $status );
$end_date = self::get_cache_end_date_by_status( $status );
update_option( self::OPTION_TIMESTAMP_NAME, $end_date );
update_option( static::OPTION_TIMESTAMP_NAME, $end_date );
}
/**
@ -278,94 +125,175 @@ class Status {
*/
public static function get_cache_end_date_by_status( $status ) {
if ( ! is_object( $status ) || empty( $status->last_checked ) ) {
return time() + self::INITIAL_OPTION_EXPIRES_AFTER;
return time() + static::INITIAL_OPTION_EXPIRES_AFTER;
}
return time() + self::OPTION_EXPIRES_AFTER;
return time() + static::OPTION_EXPIRES_AFTER;
}
/**
* Delete the cached status and its timestamp
*
* @return void
* @return bool Whether all related status options were successfully deleted.
*/
public static function delete_option() {
delete_option( self::OPTION_NAME );
delete_option( self::OPTION_TIMESTAMP_NAME );
$option_deleted = delete_option( static::OPTION_NAME );
$option_timestamp_deleted = delete_option( static::OPTION_TIMESTAMP_NAME );
return $option_deleted && $option_timestamp_deleted;
}
/**
* Prepare the report data for the UI
* Checks the current status to see if there are any threats found
*
* @param string $report_data The report status report response.
* @return object The normalized report data.
* @return boolean
*/
private static function normalize_report_data( $report_data ) {
$installed_plugins = Plugins_Installer::get_plugins();
$last_report_plugins = isset( $report_data->plugins ) ? $report_data->plugins : new \stdClass();
$report_data->plugins = self::merge_installed_and_checked_lists( $installed_plugins, $last_report_plugins, array( 'type' => 'plugin' ) );
public static function has_threats() {
return 0 < self::get_total_threats();
}
$installed_themes = Sync_Functions::get_themes();
$last_report_themes = isset( $report_data->themes ) ? $report_data->themes : new \stdClass();
$report_data->themes = self::merge_installed_and_checked_lists( $installed_themes, $last_report_themes, array( 'type' => 'theme' ) );
/**
* Gets the total number of threats found
*
* @return integer
*/
public static function get_total_threats() {
$status = static::get_status();
return isset( $status->num_threats ) && is_int( $status->num_threats ) ? $status->num_threats : 0;
}
$report_data->core = self::normalize_core_information( isset( $report_data->core ) ? $report_data->core : new \stdClass() );
$all_items = array_merge( $report_data->plugins, $report_data->themes, array( $report_data->core ) );
$unchecked_items = array_filter(
$all_items,
function ( $item ) {
return ! isset( $item->checked ) || ! $item->checked;
}
/**
* Get all threats combined
*
* @return array
*/
public static function get_all_threats() {
return array_merge(
self::get_wordpress_threats(),
self::get_themes_threats(),
self::get_plugins_threats(),
self::get_files_threats(),
self::get_database_threats()
);
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$report_data->hasUncheckedItems = ! empty( $unchecked_items );
return $report_data;
}
/**
* Merges the list of installed extensions with the list of extensions that were checked for known vulnerabilities and return a normalized list to be used in the UI
* Get threats found for WordPress core
*
* @param array $installed The list of installed extensions, where each attribute key is the extension slug.
* @param array $checked The list of checked extensions.
* @param array $append Additional data to append to each result in the list.
* @return array Normalized list of extensions.
* @return array
*/
private static function merge_installed_and_checked_lists( $installed, $checked, $append ) {
$new_list = array();
foreach ( $installed as $slug => $item ) {
if ( isset( $checked->{ $slug } ) && $checked->{ $slug }->version === $installed[ $slug ]['Version'] ) {
$new_list[] = (object) array_merge(
array(
'name' => $installed[ $slug ]['Name'],
'version' => $checked->{ $slug }->version,
'slug' => $slug,
'vulnerabilities' => $checked->{ $slug }->vulnerabilities,
'checked' => true,
),
$append
);
} else {
$new_list[] = (object) array_merge(
array(
'name' => $installed[ $slug ]['Name'],
'version' => $installed[ $slug ]['Version'],
'slug' => $slug,
'vulnerabilities' => array(),
'checked' => false,
),
$append
);
public static function get_wordpress_threats() {
return self::get_threats( 'core' );
}
/**
* Get threats found for themes
*
* @return array
*/
public static function get_themes_threats() {
return self::get_threats( 'themes' );
}
/**
* Get threats found for plugins
*
* @return array
*/
public static function get_plugins_threats() {
return self::get_threats( 'plugins' );
}
/**
* Get threats found for files
*
* @return array
*/
public static function get_files_threats() {
return self::get_threats( 'files' );
}
/**
* Get threats found for plugins
*
* @return array
*/
public static function get_database_threats() {
return self::get_threats( 'database' );
}
/**
* Get the threats for one type of extension or core
*
* @param string $type What threats you want to get. Possible values are 'core', 'themes' and 'plugins'.
*
* @return array
*/
public static function get_threats( $type ) {
$status = static::get_status();
if ( 'core' === $type ) {
return isset( $status->$type ) && ! empty( $status->$type->threats ) ? $status->$type->threats : array();
}
if ( 'files' === $type || 'database' === $type ) {
return isset( $status->$type ) && ! empty( $status->$type ) ? $status->$type : array();
}
$threats = array();
if ( isset( $status->$type ) ) {
foreach ( (array) $status->$type as $item ) {
if ( ! empty( $item->threats ) ) {
$threats = array_merge( $threats, $item->threats );
}
}
}
return $threats;
}
/**
* Check if the WordPress version that was checked matches the current installed version.
*
* @param object $core_check The object returned by Protect wpcom endpoint.
* @return object The object representing the current status of core checks.
*/
protected static function normalize_core_information( $core_check ) {
global $wp_version;
$core = new Extension_Model(
array(
'type' => 'core',
'name' => 'WordPress',
'version' => $wp_version,
'checked' => false,
)
);
if ( isset( $core_check->version ) && $core_check->version === $wp_version ) {
if ( is_array( $core_check->vulnerabilities ) ) {
$core->checked = true;
$core->set_threats( $core_check->vulnerabilities );
}
}
return $core;
}
/**
* Sort By Threats
*
* @param array<object> $threats Array of threats to sort.
*
* @return array<object> The sorted $threats array.
*/
protected static function sort_threats( $threats ) {
usort(
$new_list,
$threats,
function ( $a, $b ) {
// sort primarily based on the presence of vulnerabilities
if ( ! empty( $a->vulnerabilities ) && empty( $b->vulnerabilities ) ) {
// sort primarily based on the presence of threats
if ( ! empty( $a->threats ) && empty( $b->threats ) ) {
return -1;
}
if ( empty( $a->vulnerabilities ) && ! empty( $b->vulnerabilities ) ) {
if ( empty( $a->threats ) && ! empty( $b->threats ) ) {
return 1;
}
// sort secondarily on whether the item has been checked
@ -379,31 +307,8 @@ class Status {
return 0;
}
);
return $new_list;
}
/**
* Check if the WordPress version that was checked matches the current installed version.
*
* @param object $core_check The object returned by Protect wpcom endpoint.
* @return object The object representing the current status of core checks.
*/
private static function normalize_core_information( $core_check ) {
global $wp_version;
$core = new \stdClass();
if ( isset( $core_check->version ) && $core_check->version === $wp_version ) {
$core = $core_check;
$core->name = 'WordPress';
$core->type = 'core';
} else {
$core->version = $wp_version;
$core->vulnerabilities = array();
$core->checked = false;
$core->name = 'WordPress';
$core->type = 'core';
}
return $core;
return $threats;
}
}

View File

@ -0,0 +1,203 @@
<?php
/**
* Class to handle Threats
*
* @package automattic/jetpack-protect-plugin
*/
namespace Automattic\Jetpack\Protect;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Jetpack_Options;
use WP_Error;
/**
* Class that handles management of individual threats.
*/
class Threats {
/**
* Gets the base "Alerts" (Threats) endpoint.
*
* @return WP_Error|string
*/
private static function get_api_base() {
$blog_id = Jetpack_Options::get_option( 'id' );
$is_connected = ( new Connection_Manager() )->is_connected();
if ( ! $blog_id || ! $is_connected ) {
return new WP_Error( 'site_not_connected' );
}
$api_url = sprintf( '/sites/%d/alerts', $blog_id );
return $api_url;
}
/**
* Update Threat
*
* @param string $threat_id The threat ID.
* @param array $updates The keys/values to update.
*
* @return bool
*/
public static function update_threat( $threat_id, $updates ) {
$api_base = self::get_api_base( $threat_id );
if ( is_wp_error( $api_base ) ) {
return false;
}
$response = Client::wpcom_json_api_request_as_user(
"$api_base/$threat_id",
'2',
array( 'method' => 'POST' ),
wp_json_encode( $updates ),
'wpcom'
);
$response_code = wp_remote_retrieve_response_code( $response );
if ( is_wp_error( $response ) || 200 !== $response_code ) {
return false;
}
// clear the now out-of-date cache
Scan_Status::delete_option();
return true;
}
/**
* Ignore Threat
*
* @param string $threat_id The threat ID.
*
* @return bool
*/
public static function ignore_threat( $threat_id ) {
return self::update_threat( $threat_id, array( 'ignore' => true ) );
}
/**
* Fix Threats
*
* @param array<string> $threat_ids Threat IDs.
*
* @return bool|array
*/
public static function fix_threats( $threat_ids ) {
$api_base = self::get_api_base();
if ( is_wp_error( $api_base ) ) {
return false;
}
$response = Client::wpcom_json_api_request_as_user(
"$api_base/fix",
'2',
array( 'method' => 'POST' ),
wp_json_encode(
array(
'threat_ids' => $threat_ids,
)
),
'wpcom'
);
$response_code = wp_remote_retrieve_response_code( $response );
if ( is_wp_error( $response ) || 200 !== $response_code ) {
return false;
}
// clear the now out-of-date cache
Scan_Status::delete_option();
$parsed_response = json_decode( $response['body'] );
if ( ! $parsed_response ) {
return false;
}
return $parsed_response;
}
/**
* Fix Threats Status
*
* @param array<string> $threat_ids Threat IDs.
*
* @return bool|array
*/
public static function fix_threats_status( $threat_ids ) {
$api_base = self::get_api_base();
if ( is_wp_error( $api_base ) ) {
return false;
}
$response = Client::wpcom_json_api_request_as_user(
add_query_arg( 'threat_ids', $threat_ids, "$api_base/fix" ),
'2',
array( 'method' => 'GET' ),
null,
'wpcom'
);
$response_code = wp_remote_retrieve_response_code( $response );
if ( is_wp_error( $response ) || 200 !== $response_code ) {
return false;
}
$parsed_response = json_decode( $response['body'] );
if ( ! $parsed_response ) {
return false;
}
// clear the potentially out-of-date cache
Scan_Status::delete_option();
return $parsed_response;
}
/**
* Scan enqueue
*
* @return bool
*/
public static function scan() {
$blog_id = Jetpack_Options::get_option( 'id' );
$is_connected = ( new Connection_Manager() )->is_connected();
if ( ! $blog_id || ! $is_connected ) {
return false;
}
$api_base = sprintf( '/sites/%d/scan', $blog_id );
if ( is_wp_error( $api_base ) ) {
return false;
}
$response = Client::wpcom_json_api_request_as_blog(
"$api_base/enqueue",
'2',
array( 'method' => 'POST' ),
null,
'wpcom'
);
$response_code = wp_remote_retrieve_response_code( $response );
if ( is_wp_error( $response ) || 200 !== $response_code ) {
return false;
}
// clear the now out-of-date cache
Scan_Status::delete_option();
return true;
}
}

View File

@ -0,0 +1,111 @@
<?php
/**
* Model class for extensions.
*
* @package automattic/jetpack-protect-plugin
*/
namespace Automattic\Jetpack\Protect;
/**
* Model class for extension data.
*/
class Extension_Model {
/**
* The extension name.
*
* @var null|string
*/
public $name;
/**
* The extension slug.
*
* @var null|string
*/
public $slug;
/**
* The extension version.
*
* @var null|string
*/
public $version;
/**
* A collection of threats related to this version of the extension.
*
* @var array<Threat_Model>
*/
public $threats = array();
/**
* Whether the extension has been checked for threats.
*
* @var null|bool
*/
public $checked;
/**
* The type of extension ("plugins", "themes", or "core").
*
* @var null|string
*/
public $type;
/**
* Extension Model Constructor
*
* @param array|object $extension Extension data to load into the model instance.
*/
public function __construct( $extension = array() ) {
if ( is_object( $extension ) ) {
$extension = (array) $extension;
}
foreach ( $extension as $property => $value ) {
if ( property_exists( $this, $property ) ) {
// use the property's setter method when possible
if ( method_exists( $this, "set_$property" ) ) {
$this->{ "set_$property" }( $value );
continue;
}
// otherwise, map the value directly into the class property
$this->$property = $value;
}
}
}
/**
* Set Threats
*
* @param array<Threat_Model|array|object> $threats An array of threat data to add to the extension.
*/
public function set_threats( $threats ) {
if ( ! is_array( $threats ) ) {
$this->threats = array();
return;
}
// convert each provided threat item into an instance of Threat_Model
$threats = array_map(
function ( $threat ) {
if ( is_a( $threat, 'Threat_Model' ) ) {
return $threat;
}
if ( is_object( $threat ) ) {
$threat = (array) $threat;
}
return new Threat_Model( $threat );
},
$threats
);
$this->threats = $threats;
}
}

View File

@ -0,0 +1,135 @@
<?php
/**
* Model class for Protect status report data.
*
* @package automattic/jetpack-protect-plugin
*/
namespace Automattic\Jetpack\Protect;
/**
* Model class for the Protect status report data.
*/
class Status_Model {
/**
* Data source.
*
* @var string protect_report|scan_api
*/
public $data_source;
/**
* The date and time when the status was generated.
*
* @var string
*/
public $last_checked;
/**
* The number of threats.
*
* @var int
*/
public $num_threats;
/**
* The number of plugin threats.
*
* @var int
*/
public $num_plugins_threats;
/**
* The number of theme threats.
*
* @var int
*/
public $num_themes_threats;
/**
* The current report status.
*
* @var string in_progress|scheduled|idle|scanning|provisioning|unavailable
*/
public $status;
/**
* WordPress core status.
*
* @var object
*/
public $core;
/**
* Status themes.
*
* @var array<Extension_Model>
*/
public $themes = array();
/**
* Status plugins.
*
* @var array<Extension_Model>
*/
public $plugins = array();
/**
* File threats.
*
* @var array<Extension_Model>
*/
public $files = array();
/**
* Database threats.
*
* @var array<Extension_Model>
*/
public $database = array();
/**
* Whether the site includes items that have not been checked.
*
* @var boolean
*/
public $has_unchecked_items;
/**
* Whether there was an error loading the status.
*
* @var bool
*/
public $error = false;
/**
* The error code thrown when loading the status.
*
* @var string
*/
public $error_code;
/**
* The error message thrown when loading the status.
*
* @var string
*/
public $error_message;
/**
* Status constructor.
*
* @param array $status The status data to load into the class instance.
*/
public function __construct( $status = array() ) {
// set status defaults
$this->core = new \stdClass();
foreach ( $status as $property => $value ) {
if ( property_exists( $this, $property ) ) {
$this->$property = $value;
}
}
}
}

View File

@ -0,0 +1,116 @@
<?php
/**
* Model class for threat data.
*
* @package automattic/jetpack-protect-plugin
*/
namespace Automattic\Jetpack\Protect;
/**
* Model class for threat data.
*/
class Threat_Model {
/**
* Threat ID.
*
* @var null|string
*/
public $id;
/**
* Threat Signature.
*
* @var null|string
*/
public $signature;
/**
* Threat Title.
*
* @var null|string
*/
public $title;
/**
* Threat Description.
*
* @var null|string
*/
public $description;
/**
* The data the threat was first detected.
*
* @var null|string
*/
public $first_detected;
/**
* The version the threat is fixed in.
*
* @var null|string
*/
public $fixed_in;
/**
* The severity of the threat between 1-5.
*
* @var null|int
*/
public $severity;
/**
* Information about the auto-fix available for this threat. False when not auto-fixable.
*
* @var null|bool|object
*/
public $fixable;
/**
* The current status of the threat.
*
* @var null|string
*/
public $status;
/**
* The filename of the threat.
*
* @var null|string
*/
public $filename;
/**
* The context of the threat.
*
* @var null|object
*/
public $context;
/**
* The source URL of the threat.
*
* @var null|string
*/
public $source;
/**
* Threat Constructor
*
* @param array|object $threat Threat data to load into the class instance.
*/
public function __construct( $threat ) {
if ( is_object( $threat ) ) {
$threat = (array) $threat;
}
foreach ( $threat as $property => $value ) {
if ( property_exists( $this, $property ) ) {
$this->$property = $value;
}
}
}
}

View File

@ -9,4 +9,4 @@ if (PHP_VERSION_ID < 50600) {
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInitc4802e05bbcf59fd3b6350e8d3e5482c_protectⓥ1_0_4::getLoader();
return ComposerAutoloaderInitc4802e05bbcf59fd3b6350e8d3e5482c_protectⓥ1_1_0::getLoader();

View File

@ -5,7 +5,7 @@
* @package automattic/jetpack-autoloader
*/
namespace Automattic\Jetpack\Autoloader\jpc4802e05bbcf59fd3b6350e8d3e5482c_protectⓥ1_0_4;
namespace Automattic\Jetpack\Autoloader\jpc4802e05bbcf59fd3b6350e8d3e5482c_protectⓥ1_1_0;
// phpcs:ignore

View File

@ -0,0 +1,69 @@
# 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).
## [0.2.6] - 2022-11-07
### Changed
- Updated package dependencies. [#27278]
## [0.2.5] - 2022-10-25
### Changed
- Updated package dependencies. [#26705]
## [0.2.4] - 2022-09-20
### Changed
- Updated package dependencies.
## [0.2.3] - 2022-09-08
### Changed
- Updated package dependencies.
## [0.2.2] - 2022-08-30
### Changed
- Updated package dependencies. [#25694]
## [0.2.1] - 2022-08-23
### Changed
- Updated package dependencies. [#25628]
## [0.2.0] - 2022-08-16
### Added
- Tweaked the supports method of the plans package to refresh the plan data. [#25347]
## [0.1.3] - 2022-08-03
### Changed
- Updated package dependencies. [#25300, #25315]
## [0.1.2] - 2022-07-26
### Changed
- Add mirror repository information to package info. [#25142]
- Updated package dependencies. [#25158]
## 0.1.1 - 2022-07-19
### Changed
- Updated package dependencies.
## 0.1.0 - 2022-07-06
### Added
- Add support for WordPress.com Starter plan. [#24496]
- Package created. [#23503]
### Changed
- Renaming master to trunk. [#24661]
- Updated package dependencies.
### Deprecated
- Moved the options class into Connection. [#24095]
[0.2.6]: https://github.com/Automattic/jetpack-plans/compare/v0.2.5...v0.2.6
[0.2.5]: https://github.com/Automattic/jetpack-plans/compare/v0.2.4...v0.2.5
[0.2.4]: https://github.com/Automattic/jetpack-plans/compare/v0.2.3...v0.2.4
[0.2.3]: https://github.com/Automattic/jetpack-plans/compare/v0.2.2...v0.2.3
[0.2.2]: https://github.com/Automattic/jetpack-plans/compare/v0.2.1...v0.2.2
[0.2.1]: https://github.com/Automattic/jetpack-plans/compare/v0.2.0...v0.2.1
[0.2.0]: https://github.com/Automattic/jetpack-plans/compare/v0.1.3...v0.2.0
[0.1.3]: https://github.com/Automattic/jetpack-plans/compare/v0.1.2...v0.1.3
[0.1.2]: https://github.com/Automattic/jetpack-plans/compare/v0.1.1...v0.1.2

View File

@ -0,0 +1,357 @@
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===================================
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

Some files were not shown because too many files have changed in this diff Show More