updated plugin `Jetpack Protect` version 2.0.0

This commit is contained in:
KawaiiPunk 2024-02-08 12:31:43 +00:00 committed by Gitium
parent ce653dd56c
commit 8d5e7cc070
192 changed files with 5244 additions and 2003 deletions

View File

@ -5,6 +5,21 @@ 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.0.0 - 2024-01-18
### Changed
- Firewall: use datetime versioning for rules file updates. [#34698]
- General: unify display of notifications across Scan and Firewall screens [#34702]
- General: indicate full compatibility with the latest version of WordPress, 6.4. [#33776]
- General: update PHP requirement to PHP 7.0+ [#34126]
- General: update WordPress version requirements to WordPress 6.3. [#34127]
- General: update package dependencies. [#34882]
- General: update lockfile. [#33607]
### Fixed
- Fix Modal component overflow scrolling. [#34475]
- Fix Popover component styling. [#34424]
- Improved helper script installer logging. [#34297]
## 1.4.2 - 2023-10-19
### Changed
- General: update WordPress version requirements to WordPress 6.2. [#32762] [#32772]

View File

@ -1 +1 @@
<?php return array('dependencies' => array('moment', 'react', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-url'), 'version' => 'ba1541fec46b75d66cd4');
<?php return array('dependencies' => array('moment', 'react', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-url'), 'version' => '69b5f82ff2ab6a921833');

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,23 +5,23 @@
"license": "GPL-2.0-or-later",
"require": {
"ext-json": "*",
"automattic/jetpack-assets": "^1.18.13",
"automattic/jetpack-admin-ui": "^0.2.23",
"automattic/jetpack-autoloader": "^2.12.0",
"automattic/jetpack-composer-plugin": "^1.1.14",
"automattic/jetpack-config": "^1.15.4",
"automattic/jetpack-identity-crisis": "^0.11.1",
"automattic/jetpack-my-jetpack": "^3.9.1",
"automattic/jetpack-plugins-installer": "^0.2.5",
"automattic/jetpack-sync": "^1.58.1",
"automattic/jetpack-transport-helper": "^0.1.6",
"automattic/jetpack-plans": "^0.3.4",
"automattic/jetpack-waf": "^0.11.13",
"automattic/jetpack-status": "^1.18.5"
"automattic/jetpack-assets": "^2.0.4",
"automattic/jetpack-admin-ui": "^0.3.1",
"automattic/jetpack-autoloader": "^3.0.2",
"automattic/jetpack-composer-plugin": "^2.0.0",
"automattic/jetpack-config": "^2.0.0",
"automattic/jetpack-identity-crisis": "^0.15.0",
"automattic/jetpack-my-jetpack": "^4.6.0",
"automattic/jetpack-plugins-installer": "^0.3.1",
"automattic/jetpack-sync": "^2.4.2",
"automattic/jetpack-transport-helper": "^0.2.0",
"automattic/jetpack-plans": "^0.4.1",
"automattic/jetpack-waf": "^0.12.4",
"automattic/jetpack-status": "^2.1.0"
},
"require-dev": {
"yoast/phpunit-polyfills": "1.1.0",
"automattic/jetpack-changelogger": "^3.3.11",
"automattic/jetpack-changelogger": "^4.0.5",
"automattic/wordbless": "0.4.2"
},
"autoload": {
@ -71,6 +71,6 @@
"automattic/jetpack-autoloader": true,
"automattic/jetpack-composer-plugin": true
},
"autoloader-suffix": "c4802e05bbcf59fd3b6350e8d3e5482c_protectⓥ1_4_2"
"autoloader-suffix": "c4802e05bbcf59fd3b6350e8d3e5482c_protectⓥ2_0_0"
}
}

View File

@ -3,7 +3,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.4.2
* Version: 2.0.0
* Author: Automattic - Jetpack Security team
* Author URI: https://jetpack.com/protect/
* License: GPLv2 or later
@ -32,7 +32,7 @@ if ( ! defined( 'ABSPATH' ) ) {
exit;
}
define( 'JETPACK_PROTECT_VERSION', '1.4.2' );
define( 'JETPACK_PROTECT_VERSION', '2.0.0' );
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,7 +5,12 @@ 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.0.0] - 2023-11-20
### Changed
- Updated required PHP version to >= 7.0. [#34192]
## [1.4.22] - 2023-09-19
- Minor internal updates.
## [1.4.21] - 2023-08-23
@ -126,6 +131,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Creates the MC Stats package
[2.0.0]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.22...v2.0.0
[1.4.22]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.21...v1.4.22
[1.4.21]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.20...v1.4.21
[1.4.20]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.19...v1.4.20

View File

@ -4,11 +4,20 @@ Full details of the Automattic Security Policy can be found on [automattic.com](
## 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.
Generally, only the latest version of Jetpack and its associated plugins have continued support. If a critical vulnerability is found in the current version of a plugin, 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.
Our HackerOne program covers the below plugin software, as well as a variety of related projects and infrastructure:
* [Jetpack](https://jetpack.com/)
* Jetpack Backup
* Jetpack Boost
* Jetpack CRM
* Jetpack Protect
* Jetpack Search
* Jetpack Social
* Jetpack VideoPress
**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.**

View File

@ -3,10 +3,12 @@
"description": "Used to record internal usage stats for Automattic. Not visible to site owners.",
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {},
"require": {
"php": ">=7.0"
},
"require-dev": {
"yoast/phpunit-polyfills": "1.1.0",
"automattic/jetpack-changelogger": "^3.3.9"
"automattic/jetpack-changelogger": "^4.0.0"
},
"suggest": {
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
@ -33,7 +35,7 @@
"link-template": "https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "1.4.x-dev"
"dev-trunk": "2.0.x-dev"
}
}
}

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).
## [0.3.1] - 2023-11-24
## [0.3.0] - 2023-11-20
### Changed
- Updated required PHP version to >= 7.0. [#34192]
## [0.2.25] - 2023-11-14
## [0.2.24] - 2023-10-30
### Fixed
- Handle Akismet submenu even if Jetpack is present, as Jetpack now relies on this package to do so. [#33559]
## [0.2.23] - 2023-09-19
### Changed
- Updated Jetpack submenu sort order so individual features are alpha-sorted. [#32958]
@ -120,6 +132,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Fixing menu visibility issues.
[0.3.1]: https://github.com/Automattic/jetpack-admin-ui/compare/0.3.0...0.3.1
[0.3.0]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.25...0.3.0
[0.2.25]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.24...0.2.25
[0.2.24]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.23...0.2.24
[0.2.23]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.22...0.2.23
[0.2.22]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.21...0.2.22
[0.2.21]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.20...0.2.21

View File

@ -4,11 +4,20 @@ Full details of the Automattic Security Policy can be found on [automattic.com](
## 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.
Generally, only the latest version of Jetpack and its associated plugins have continued support. If a critical vulnerability is found in the current version of a plugin, 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.
Our HackerOne program covers the below plugin software, as well as a variety of related projects and infrastructure:
* [Jetpack](https://jetpack.com/)
* Jetpack Backup
* Jetpack Boost
* Jetpack CRM
* Jetpack Protect
* Jetpack Search
* Jetpack Social
* Jetpack VideoPress
**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.**

View File

@ -3,11 +3,13 @@
"description": "Generic Jetpack wp-admin UI elements",
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {},
"require": {
"php": ">=7.0"
},
"require-dev": {
"yoast/phpunit-polyfills": "1.1.0",
"automattic/jetpack-changelogger": "^3.3.9",
"automattic/jetpack-logo": "^1.6.3",
"automattic/jetpack-changelogger": "^4.0.3",
"automattic/jetpack-logo": "^2.0.0",
"automattic/wordbless": "dev-master"
},
"suggest": {
@ -38,7 +40,7 @@
"link-template": "https://github.com/Automattic/jetpack-admin-ui/compare/${old}...${new}"
},
"branch-alias": {
"dev-trunk": "0.2.x-dev"
"dev-trunk": "0.3.x-dev"
},
"version-constants": {
"::PACKAGE_VERSION": "src/class-admin-menu.php"

View File

@ -13,7 +13,7 @@ namespace Automattic\Jetpack\Admin_UI;
*/
class Admin_Menu {
const PACKAGE_VERSION = '0.2.23';
const PACKAGE_VERSION = '0.3.1';
/**
* Whether this class has been initialized
@ -49,7 +49,7 @@ class Admin_Menu {
* we use this method to move the menu item.
*/
private static function handle_akismet_menu() {
if ( ! class_exists( 'Jetpack' ) && class_exists( 'Akismet_Admin' ) ) {
if ( class_exists( 'Akismet_Admin' ) ) {
// Prevent Akismet from adding a menu item.
add_action(
'admin_menu',
@ -104,7 +104,7 @@ class Admin_Menu {
function ( $a, $b ) {
$position_a = empty( $a['position'] ) ? 0 : $a['position'];
$position_b = empty( $b['position'] ) ? 0 : $b['position'];
$result = $position_a - $position_b;
$result = $position_a <=> $position_b;
if ( 0 === $result ) {
$result = strcmp( $a['menu_title'], $b['menu_title'] );

View File

@ -5,6 +5,30 @@ 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.0.4] - 2024-01-04
### Changed
- Updated package dependencies. [#34815]
## [2.0.3] - 2023-12-11
### Changed
- Updated package dependencies. [#34492]
## [2.0.2] - 2023-12-03
### Changed
- Updated package dependencies. [#34411] [#34427]
## [2.0.1] - 2023-11-21
## [2.0.0] - 2023-11-20
### Changed
- Updated required PHP version to >= 7.0. [#34192]
## [1.18.15] - 2023-11-14
### Changed
- Updated package dependencies. [#34093]
## [1.18.14] - 2023-11-03
## [1.18.13] - 2023-10-19
### Changed
- Updated package dependencies. [#33687]
@ -372,6 +396,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Statically access asset tools
[2.0.4]: https://github.com/Automattic/jetpack-assets/compare/v2.0.3...v2.0.4
[2.0.3]: https://github.com/Automattic/jetpack-assets/compare/v2.0.2...v2.0.3
[2.0.2]: https://github.com/Automattic/jetpack-assets/compare/v2.0.1...v2.0.2
[2.0.1]: https://github.com/Automattic/jetpack-assets/compare/v2.0.0...v2.0.1
[2.0.0]: https://github.com/Automattic/jetpack-assets/compare/v1.18.15...v2.0.0
[1.18.15]: https://github.com/Automattic/jetpack-assets/compare/v1.18.14...v1.18.15
[1.18.14]: https://github.com/Automattic/jetpack-assets/compare/v1.18.13...v1.18.14
[1.18.13]: https://github.com/Automattic/jetpack-assets/compare/v1.18.12...v1.18.13
[1.18.12]: https://github.com/Automattic/jetpack-assets/compare/v1.18.11...v1.18.12
[1.18.11]: https://github.com/Automattic/jetpack-assets/compare/v1.18.10...v1.18.11

View File

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

View File

@ -4,13 +4,14 @@
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"automattic/jetpack-constants": "^1.6.23"
"php": ">=7.0",
"automattic/jetpack-constants": "^2.0.0"
},
"require-dev": {
"brain/monkey": "2.6.1",
"yoast/phpunit-polyfills": "1.1.0",
"automattic/jetpack-changelogger": "^3.3.11",
"wikimedia/testing-access-wrapper": "^1.0 || ^2.0"
"automattic/jetpack-changelogger": "^4.0.5",
"wikimedia/testing-access-wrapper": "^1.0 || ^2.0 || ^3.0"
},
"suggest": {
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
@ -50,7 +51,7 @@
"link-template": "https://github.com/Automattic/jetpack-assets/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "1.18.x-dev"
"dev-trunk": "2.0.x-dev"
}
}
}

View File

@ -450,6 +450,7 @@ class Assets {
$content_dir = Jetpack_Constants::get_constant( 'WP_CONTENT_DIR' );
$abspath = Jetpack_Constants::get_constant( 'ABSPATH' );
// Note: str_starts_with() is not used here, as wp-includes/compat.php may not be loaded at this point.
if ( strpos( $lang_dir, $content_dir ) === 0 ) {
$data['baseUrl'] = content_url( substr( trailingslashit( $lang_dir ), strlen( trailingslashit( $content_dir ) ) ) );
} elseif ( strpos( $lang_dir, $abspath ) === 0 ) {

View File

@ -0,0 +1,16 @@
# 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.0] - 2024-01-04
### Fixed
- Backup: Add namespace versioning to Helper_Script_Manager and other classes. [#34739]
## 0.1.0 - 2023-12-13
### Fixed
- Initial release (improved helper script installer logging). [#34297]
[0.2.0]: https://github.com/Automattic/jetpack-backup-helper-script-manager/compare/v0.1.0...v0.2.0

View File

@ -4,11 +4,20 @@ Full details of the Automattic Security Policy can be found on [automattic.com](
## 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.
Generally, only the latest version of Jetpack and its associated plugins have continued support. If a critical vulnerability is found in the current version of a plugin, 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.
Our HackerOne program covers the below plugin software, as well as a variety of related projects and infrastructure:
* [Jetpack](https://jetpack.com/)
* Jetpack Backup
* Jetpack Boost
* Jetpack CRM
* Jetpack Protect
* Jetpack Search
* Jetpack Social
* Jetpack VideoPress
**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.**

View File

@ -1,16 +1,14 @@
{
"name": "automattic/jetpack-partner",
"description": "Support functions for Jetpack hosting partners.",
"name": "automattic/jetpack-backup-helper-script-manager",
"description": "Install / delete helper script for backup and transport server. Not visible to site owners.",
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"automattic/jetpack-connection": "^1.57.5",
"automattic/jetpack-status": "^1.18.4"
"php": ">=7.0"
},
"require-dev": {
"brain/monkey": "2.6.1",
"yoast/phpunit-polyfills": "1.1.0",
"automattic/jetpack-changelogger": "^3.3.9",
"automattic/jetpack-changelogger": "^4.0.5",
"automattic/wordbless": "@dev"
},
"suggest": {
@ -25,27 +23,29 @@
"phpunit": [
"./vendor/phpunit/phpunit/phpunit --colors=always"
],
"post-install-cmd": "WorDBless\\Composer\\InstallDropin::copy",
"post-update-cmd": "WorDBless\\Composer\\InstallDropin::copy",
"test-php": [
"@composer phpunit"
]
],
"post-install-cmd": "WorDBless\\Composer\\InstallDropin::copy",
"post-update-cmd": "WorDBless\\Composer\\InstallDropin::copy"
},
"minimum-stability": "dev",
"prefer-stable": true,
"extra": {
"autotagger": true,
"mirror-repo": "Automattic/jetpack-partner",
"mirror-repo": "Automattic/jetpack-backup-helper-script-manager",
"changelogger": {
"link-template": "https://github.com/Automattic/jetpack-partner/compare/v${old}...v${new}"
"link-template": "https://github.com/Automattic/jetpack-backup-helper-script-manager/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "1.7.x-dev"
"dev-trunk": "0.2.x-dev"
}
},
"config": {
"allow-plugins": {
"roots/wordpress-core-installer": true
"roots/wordpress-core-installer": true,
"automattic/jetpack-autoloader": true,
"automattic/jetpack-composer-plugin": true
}
}
}

View File

@ -0,0 +1,621 @@
<?php
/**
* The Jetpack Backup Helper Script Manager class (implementation).
*
* @package automattic/jetpack-backup
*/
// After changing this file, consider increasing the version number ("VXXX") in all the files using this namespace, in
// order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
// to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
// are installed, or in some other cases).
namespace Automattic\Jetpack\Backup\V0001;
use Exception;
use WP_Error;
use function content_url;
use function get_site_url;
use function is_wp_error;
use function set_url_scheme;
use function trailingslashit;
use function wp_generate_password;
use function wp_http_validate_url;
use function wp_schedule_single_event;
use function wp_upload_dir;
use const ABSPATH;
use const WP_CONTENT_DIR;
/**
* Manage installation, deletion and cleanup of Helper Scripts to assist with backing up Jetpack Sites.
*
* Does *not* use WP_Filesystem, because if there are permissions issues between the webserver's user and the FTP/SSH
* user, then we'll just install the helper script and do a backup/restore using FTP/SSH credentials (that we collect
* ourselves), without using WP_Filesystem in any way.
*
* Also, if we can't write that helper script somewhere (due to writes being inaccessible to the webserver's user, or
* for other reasons), we want to know about it (in the form of an error response), instead of having that helper
* script silently uploaded via FTP/SFTP, so that we could fall back to a backup/restore using credentials.
*
* Lastly, PHP provides us with better error reporting than WP_Filesystem.
*/
class Helper_Script_Manager_Impl {
/**
* Name of a directory that will be created for storing the helper script.
*/
const TEMP_DIRECTORY = 'jetpack-temp';
/**
* How long until the helper script will "expire" and refuse taking requests, in seconds.
*/
const EXPIRY_TIME = 60 * 60 * 8;
/**
* Maximum size of the helper script, in bytes.
*/
const MAX_FILESIZE = 1024 * 1024;
/**
* Associative array of possible places to install a jetpack-temp directory, along with the URL to access each.
*
* Keys specify the full path of install locations, and values point to the equivalent URL.
*
* If null, then install locations will be determined dynamically at the point of an install.
*
* @var array|null
*/
protected $custom_install_locations;
/**
* Filenames to ignore in scandir()'s return value.
*
* @var string[]
*/
protected $scandir_ignored_names = array( '.', '..' );
/**
* Header that the helper script is expected to start with.
*/
const HELPER_HEADER = "<?php /* Jetpack Backup Helper Script */\n";
/**
* Lines that will be written to README in the helper directory.
*/
const README_LINES = array(
'These files have been put on your server by Jetpack to assist with backups, restores, and scans of your ' .
'site content. 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!',
);
/**
* Data that will be written to index.php in the helper directory.
*/
const INDEX_FILE = '<?php // Silence is golden';
/**
* Create Helper Script Manager.
*
* @param array|null $custom_install_locations Associative array of possible places to install a jetpack-temp
* directory, along with the URL to access each.
*/
public function __construct( $custom_install_locations = null ) {
$this->custom_install_locations = $custom_install_locations;
}
/**
* Get either the default install locations, or the ones configured in the constructor.
*
* Has to be done late, i.e. can't be done in constructor, because in __construct() not all constants / functions
* might be available.
*
* @return array<string, string|WP_Error> Array with keys specifying the full path of install locations, and values
* either pointing to the equivalent URL, or being WP_Error if a specific path is not accessible.
*/
public function install_locations() {
if ( $this->custom_install_locations !== null ) {
return $this->custom_install_locations;
}
$abspath_url = get_site_url();
$locations = array();
// Prioritize trying to write to "wp-content/" and "wp-content/uploads/" first, because those locations are
// expected to be writable more often (unlike ABSPATH), and ABSPATH on some setups might have a weird value
// which doesn't point to document root.
try {
if ( Throw_On_Errors::t_is_dir( WP_CONTENT_DIR ) ) {
$wp_content_dir = Throw_On_Errors::t_realpath( WP_CONTENT_DIR );
// Using content_url() instead of WP_CONTENT_URL as it tests for whether we're using SSL.
$wp_content_url = content_url();
// I think we mess up the order in which we load things somewhere in a test, so "wp-content" and
// "wp-content/uploads/" URLs don't actually have the scheme+host part in them.
if ( ! wp_http_validate_url( $wp_content_url ) ) {
$wp_content_url = $abspath_url . $wp_content_url;
}
$locations[ $wp_content_dir ] = $wp_content_url;
}
} catch ( Exception $exception ) {
$locations[ WP_CONTENT_DIR ] = new WP_Error(
'content_path_missing',
'Unable to access content path "' . WP_CONTENT_DIR . '"' . $exception->getMessage(),
array( 'status' => 500 )
);
}
$upload_dir_info = wp_upload_dir();
$wp_uploads_dir = $upload_dir_info['basedir'];
try {
if ( Throw_On_Errors::t_is_dir( $wp_uploads_dir ) ) {
$wp_uploads_dir = Throw_On_Errors::t_realpath( $wp_uploads_dir );
$wp_uploads_url = $upload_dir_info['baseurl'];
// wp_upload_dir() doesn't check for whether we're using SSL:
//
// https://core.trac.wordpress.org/ticket/25449
//
// so set the scheme manually.
$wp_uploads_url = set_url_scheme( $wp_uploads_url );
if ( ! wp_http_validate_url( $wp_uploads_url ) ) {
$wp_uploads_url = $abspath_url . $wp_uploads_url;
}
$locations[ $wp_uploads_dir ] = $wp_uploads_url;
}
} catch ( Exception $exception ) {
$locations[ $wp_uploads_dir ] = new WP_Error(
'uploads_path_missing',
'Unable to access uploads path "' . $wp_uploads_dir . '"' . $exception->getMessage(),
array( 'status' => 500 )
);
}
try {
if ( Throw_On_Errors::t_is_dir( ABSPATH ) ) {
$abspath_dir = Throw_On_Errors::t_realpath( ABSPATH );
$locations[ $abspath_dir ] = $abspath_url;
}
} catch ( Exception $exception ) {
$locations[ ABSPATH ] = new WP_Error(
'abspath_missing',
'Unable to access WordPress root "' . ABSPATH . '": ' . $exception->getMessage(),
array( 'status' => 500 )
);
}
return $locations;
}
/**
* Installs a Helper Script, and returns its filesystem path and access url.
*
* @param string $script_body Helper Script file contents.
*
* @return array|WP_Error Either an array containing the filesystem path ("path"), the URL ("url") of the helper
* script, and the WordPress root ("abspath"), or an instance of WP_Error.
*/
public function install_helper_script( $script_body ) {
// Check that the script body contains the correct header.
$actual_header = static::string_starts_with_substring( $script_body, static::HELPER_HEADER );
if ( true !== $actual_header ) {
return new WP_Error(
'bad_header',
'Bad helper script header: 0x' . bin2hex( $actual_header ),
array( 'status' => 400 )
);
}
// Refuse to install a Helper Script that is too large.
$helper_script_size = strlen( $script_body );
if ( $helper_script_size > static::MAX_FILESIZE ) {
return new WP_Error(
'too_big',
"Helper script is bigger ($helper_script_size bytes) " .
'than the max. size (' . static::MAX_FILESIZE . ' bytes)',
array( 'status' => 413 )
);
}
// Replace '[wp_path]' in the Helper Script with the WordPress installation location. Allows the Helper Script
// to find WordPress.
$wp_path_marker = '[wp_path]';
try {
$normalized_abspath = addslashes( Throw_On_Errors::t_realpath( ABSPATH ) );
} catch ( Exception $exception ) {
return new WP_Error(
'abspath_missing',
'Error while resolving ABSPATH "' . ABSPATH . '": ' . $exception->getMessage(),
array( 'status' => 500 )
);
}
$script_body = str_replace(
$wp_path_marker,
$normalized_abspath,
$script_body,
$wp_path_marker_replacement_count
);
if ( 0 === $wp_path_marker_replacement_count ) {
return new WP_Error(
'no_wp_path_marker',
"Helper script does not have the '$wp_path_marker' marker",
array( 'status' => 400 )
);
}
$failure_paths_and_reasons = array();
foreach ( $this->install_locations() as $directory => $url ) {
if ( is_wp_error( $url ) ) {
$failure_paths_and_reasons[] = "directory '$directory': " . $url->get_error_message();
continue;
}
try {
$installed = $this->install_to_location_or_throw( $script_body, $directory, $url );
// Always schedule a cleanup run shortly after EXPIRY_TIME.
wp_schedule_single_event(
time() + static::EXPIRY_TIME + 60,
'jetpack_backup_cleanup_helper_scripts'
);
return array(
'path' => $installed['path'],
'url' => $installed['url'],
'abspath' => Throw_On_Errors::t_realpath( ABSPATH ),
);
} catch ( Exception $exception ) {
$failure_paths_and_reasons[] = "directory '$directory' (URL '$url'): " . $exception->getMessage();
}
}
return new WP_Error(
'all_locations_failed',
'Unable to write the helper script to any install locations; ' .
'tried: ' . implode( ';', $failure_paths_and_reasons ),
array( 'status' => 500 )
);
}
/**
* Install helper script to a directory, or throw an exception.
*
* @param string $script_body Helper script's body.
* @param string $directory Candidate directory to create "jetpack-temp" in and write the helper script.
* @param string $url Base URL that the files in a directory are expected to be available at.
*
* @return string[] Array with "path" (location to the installed helper script) and "url"
* (URL of the installed helper script) keys.
* @throws Exception On I/O errors.
*/
protected function install_to_location_or_throw( $script_body, $directory, $url ) {
if ( ! Throw_On_Errors::t_is_writable( $directory ) ) {
throw new Exception( "Directory '$directory' is not writable" );
}
$temp_dir = trailingslashit( $directory ) . static::TEMP_DIRECTORY;
if ( ! Throw_On_Errors::t_is_dir( $temp_dir ) ) {
Throw_On_Errors::t_mkdir( $temp_dir );
}
$readme_path = trailingslashit( $temp_dir ) . 'README';
Throw_On_Errors::t_file_put_contents( $readme_path, implode( "\n\n", static::README_LINES ) );
$index_path = trailingslashit( $temp_dir ) . 'index.php';
Throw_On_Errors::t_file_put_contents( $index_path, static::INDEX_FILE );
$file_key = wp_generate_password( 10, false );
$file_name = 'jp-helper-' . $file_key . '.php';
$file_path = trailingslashit( $temp_dir ) . $file_name;
// Very unlikely, but check nonetheless.
if ( Throw_On_Errors::t_file_exists( $file_path ) ) {
throw new Exception( "Helper script at '$file_path' already exists" );
}
Throw_On_Errors::t_file_put_contents( $file_path, $script_body );
return array(
'path' => $file_path,
'url' => trailingslashit( $url ) . trailingslashit( static::TEMP_DIRECTORY ) . $file_name,
);
}
/**
* Ensure that the helper script is gone (by deleting it, if needed).
*
* @param string $path Path to the helper script to delete.
*
* @return true|WP_Error True if the file helper script is gone (either it got deleted, or it was never there), or
* WP_Error instance on deletion failures.
*/
public function delete_helper_script( $path ) {
try {
$this->delete_helper_script_or_throw( $path );
} catch ( Exception $exception ) {
return new WP_Error(
'deletion_failure',
"Unable to delete helper script at '$path': " . $exception->getMessage(),
array( 'status' => 500 )
);
}
return true;
}
/**
* Ensure that the helper script is gone (by deleting it, if needed), throw an exception on errors.
*
* @param string $path Path to the helper script to delete.
*
* @return void
* @throws Exception On deletion failures.
*/
protected function delete_helper_script_or_throw( $path ) {
if ( ! Throw_On_Errors::t_file_exists( $path ) ) {
return;
}
if ( ! Throw_On_Errors::t_is_readable( $path ) ) {
throw new Exception( "File '$path' is not readable" );
}
if ( ! Throw_On_Errors::t_is_writable( $path ) ) {
throw new Exception( "File '$path' is not writable" );
}
$helper_script_size = Throw_On_Errors::t_filesize( $path );
// Check this file looks like a JPR helper script.
$helper_header_size = strlen( static::HELPER_HEADER );
if ( $helper_script_size < $helper_header_size ) {
throw new Exception(
"Helper script is smaller ($helper_script_size bytes) " .
"than the expected header ($helper_header_size bytes)"
);
}
if ( $helper_script_size > static::MAX_FILESIZE ) {
throw new Exception(
"Helper script is bigger ($helper_script_size bytes) " .
'than the max. size (' . static::MAX_FILESIZE . ' bytes)'
);
}
$actual_header = static::verify_file_header( $path, static::HELPER_HEADER );
if ( true !== $actual_header ) {
throw new Exception( 'Bad helper script header: 0x' . bin2hex( $actual_header ) );
}
Throw_On_Errors::t_unlink( $path );
$this->delete_helper_directory_if_empty( dirname( $path ) );
}
/**
* Search for Helper Scripts that are suspiciously old, and clean them out.
*
* @return true|WP_Error True if all expired helper scripts got cleaned up successfully, or an instance of
* WP_Error if one or more expired helper scripts didn't manage to get cleaned up.
*/
public function cleanup_expired_helper_scripts() {
try {
$this->cleanup_helper_scripts( time() - static::EXPIRY_TIME );
} catch ( Exception $exception ) {
return new WP_Error(
'cleanup_failed',
'Unable to clean up expired helper scripts: ' . $exception->getMessage(),
array( 'status' => 500 )
);
}
return true;
}
/**
* Search for and delete all Helper Scripts. Used during uninstallation.
*
* @return true|WP_Error True if all helper scripts got deleted successfully, or an instance of WP_Error if one or
* more helper scripts didn't manage to get deleted.
*/
public function delete_all_helper_scripts() {
try {
$this->cleanup_helper_scripts();
} catch ( Exception $exception ) {
return new WP_Error(
'cleanup_failed',
'Unable to clean up all helper scripts: ' . $exception->getMessage(),
array( 'status' => 500 )
);
}
return true;
}
/**
* Search for and delete Helper Scripts. If an $expiry_time is specified, only delete Helper Scripts
* with a mtime older than $expiry_time. Otherwise, delete them all.
*
* @param int|null $expiry_time If specified, only delete scripts older than this UNIX timestamp.
*
* @return void
* @throws Exception If one or more helper scripts doesn't manage to get cleaned up.
*/
protected function cleanup_helper_scripts( $expiry_time = null ) {
$error_messages = array();
foreach ( $this->install_locations() as $directory => $url ) {
if ( is_wp_error( $url ) ) {
$error_messages[] = $url->get_error_message();
continue;
}
$temp_dir = trailingslashit( trailingslashit( $directory ) . static::TEMP_DIRECTORY );
if ( Throw_On_Errors::t_is_dir( $temp_dir ) ) {
// Find expired helper scripts and delete them.
$temp_dir_contents = Throw_On_Errors::t_scandir( $temp_dir );
foreach ( $temp_dir_contents as $name ) {
if ( in_array( $name, $this->scandir_ignored_names, true ) ) {
continue;
}
$full_path = $temp_dir . $name;
$last_modified = Throw_On_Errors::t_filemtime( $full_path );
if ( preg_match( '/^jp-helper-.*\.php$/', $name ) ) {
if ( null === $expiry_time || $last_modified < $expiry_time ) {
try {
$this->delete_helper_script_or_throw( $full_path );
} catch ( Exception $exception ) {
$error_messages[] = $exception->getMessage();
}
}
}
}
// Delete the directory if it's empty now.
$this->delete_helper_directory_if_empty( $temp_dir );
}
}
if ( count( $error_messages ) > 0 ) {
throw new Exception(
'Unable to clean up one or more helper scripts: ' . implode( ';', $error_messages )
);
}
}
/**
* Delete a helper script directory if it's empty.
*
* @param string $dir Path to the helper script directory.
*
* @return bool True if the directory is missing, or was empty and got deleted; false if directory still contains
* something and wasn't deleted.
* @throws Exception On I/O errors.
*/
protected function delete_helper_directory_if_empty( $dir ) {
if ( ! Throw_On_Errors::t_is_dir( $dir ) ) {
return true;
}
// Check that the only remaining files are a README and index.php generated by this system.
$allowed_files_and_headers = array(
'README' => static::README_LINES[0],
'index.php' => static::INDEX_FILE,
);
$dir_contents = Throw_On_Errors::t_scandir( $dir );
if ( count( $dir_contents ) > count( $allowed_files_and_headers ) + count( $this->scandir_ignored_names ) ) {
return false;
}
foreach ( $dir_contents as $name ) {
if ( in_array( $name, $this->scandir_ignored_names, true ) ) {
continue;
}
$full_path = trailingslashit( $dir ) . $name;
if ( ! isset( $allowed_files_and_headers[ $name ] ) ) {
return false;
}
// Verify the file starts with the expected contents.
$actual_header = static::verify_file_header( $full_path, $allowed_files_and_headers[ $name ] );
if ( true !== $actual_header ) {
throw new Exception( "Bad header for file '$full_path': 0x" . bin2hex( $actual_header ) );
}
Throw_On_Errors::t_unlink( $full_path );
}
// If the directory is now empty, delete it.
$dir_contents_after_cleanup = Throw_On_Errors::t_scandir( $dir );
if ( count( $dir_contents_after_cleanup ) <= count( $this->scandir_ignored_names ) ) {
Throw_On_Errors::t_rmdir( $dir );
}
return true;
}
/**
* Test if string starts with a substring, and if it doesn't, return the actual prefix.
*
* @param string $string String to search in.
* @param string $expected_prefix Expected prefix.
*
* @return bool|string True if string starts with a substring, or the actual prefix that was found instead of the
* expected prefix.
*/
protected static function string_starts_with_substring( $string, $expected_prefix ) {
$actual_prefix = substr( $string, 0, strlen( $expected_prefix ) );
if ( $actual_prefix !== $expected_prefix ) {
return $actual_prefix;
}
return true;
}
/**
* Verify that a file exists, is readable, and has the expected header.
*
* @param string $path File to verify.
* @param string $expected_header Header that the file should have.
*
* @return bool|string True if header matches, or an actual header if it doesn't match.
* @throws Exception If the file doesn't exist, isn't readable, or is of the wrong size.
*/
protected static function verify_file_header( $path, $expected_header ) {
if ( ! Throw_On_Errors::t_file_exists( $path ) ) {
throw new Exception( "File '$path' does not exist" );
}
if ( ! Throw_On_Errors::t_is_readable( $path ) ) {
throw new Exception( "File '$path' is not readable" );
}
$file_size = Throw_On_Errors::t_filesize( $path );
// Check this file looks like a JPR helper script.
$expected_header_size = strlen( $expected_header );
if ( $file_size < $expected_header_size ) {
throw new Exception(
"File is smaller ($file_size bytes) " .
"than the expected header ($expected_header_size bytes)"
);
}
if ( $file_size > static::MAX_FILESIZE ) {
throw new Exception(
"File is bigger ($file_size bytes) " .
'than the max. size (' . static::MAX_FILESIZE . ' bytes)'
);
}
$file_contents = Throw_On_Errors::t_file_get_contents( $path );
return static::string_starts_with_substring( $file_contents, $expected_header );
}
}

View File

@ -0,0 +1,89 @@
<?php
/**
* Jetpack Backup Helper Script Manager class (static wrapper).
*
* @package automattic/jetpack-backup
*/
// After changing this file, consider increasing the version number ("VXXX") in all the files using this namespace, in
// order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
// to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
// are installed, or in some other cases).
namespace Automattic\Jetpack\Backup\V0001;
/**
* Manage installation, deletion and cleanup of Helper Scripts to assist with backing up Jetpack Sites.
*
* A static wrapper around an "implementation" class so that it gets autoloaded late, and we always get the latest
* version of the class instead of a random version of it:
*
* https://github.com/Automattic/jetpack/pull/34297#discussion_r1424227489
*/
class Helper_Script_Manager {
/**
* Instance of helper script manager implementation, or null if not initialized yet.
*
* @var Helper_Script_Manager_Impl|null
*/
protected static $impl = null;
/**
* Initialize an instance of helper script manager implementation (if needed).
*
* @return void
*/
protected static function initialize_impl_if_needed() {
if ( null === static::$impl ) {
static::$impl = new Helper_Script_Manager_Impl();
}
}
/**
* Install a Helper Script, and returns its filesystem path and access url.
*
* @param string $script_body Helper Script file contents.
*
* @return array|\WP_Error Either an array containing the filesystem path ("path"), the URL ("url") of the helper
* script, and the WordPress root ("abspath"), or an instance of WP_Error.
*/
public static function install_helper_script( $script_body ) {
static::initialize_impl_if_needed();
return static::$impl->install_helper_script( $script_body );
}
/**
* Ensure that the helper script is gone (by deleting it, if needed).
*
* @param string $path Path to the helper script to delete.
*
* @return true|\WP_Error True if the file helper script is gone (either it got deleted, or it was never there), or
* WP_Error instance on deletion failures.
*/
public static function delete_helper_script( $path ) {
static::initialize_impl_if_needed();
return static::$impl->delete_helper_script( $path );
}
/**
* Search for Helper Scripts that are suspiciously old, and clean them out.
*
* @return true|\WP_Error True if all expired helper scripts got cleaned up successfully, or an instance of
* WP_Error if one or more expired helper scripts didn't manage to get cleaned up.
*/
public static function cleanup_expired_helper_scripts() {
static::initialize_impl_if_needed();
return static::$impl->cleanup_expired_helper_scripts();
}
/**
* Search for and delete all Helper Scripts. Used during uninstallation.
*
* @return true|\WP_Error True if all helper scripts got deleted successfully, or an instance of WP_Error if one or
* more helper scripts didn't manage to get deleted.
*/
public static function delete_all_helper_scripts() {
static::initialize_impl_if_needed();
return static::$impl->delete_all_helper_scripts();
}
}

View File

@ -0,0 +1,496 @@
<?php // phpcs:disable Squiz.Commenting.FileComment.Missing
// phpcs:disable WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting
// phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_error_reporting
// phpcs:disable WordPress.PHP.IniSet.display_errors_Disallowed
// After changing this file, consider increasing the version number ("VXXX") in all the files using this namespace, in
// order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
// to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
// are installed, or in some other cases).
namespace Automattic\Jetpack\Backup\V0001;
use Exception;
use Throwable;
/**
* Wrappers for functions which throw an exception on errors and warnings instead of silently continuing operation.
*
* PHP is pretty lax with error reporting when doing I/O operations, e.g. a typical I/O helper function returns false,
* null, and/or emits a warning, but the whole PHP application continues operation. It's for the caller of the I/O
* helper function to test the returned result of the function, and notice + act upon I/O errors.
*
* We really want to know about each and every I/O error. Therefore, this class provides wrappers for some common
* (mostly) I/O operations that throw an exception on errors instead of just returning false, null, or something else.
* This wrapper class treats warnings as errors too.
*
* Given that static method names are similar to the ones used by PHP, they're prefixed with "t_" to not erroneously
* trigger various security scanners.
*/
class Throw_On_Errors {
/**
* Execute a callable, throw an exception (together with a descriptive label) on PHP warnings / errors.
*
* @param callable $callable Callable to execute.
* @param string $label Label to add to the thrown exception to clarify what was attempted.
*
* @return mixed Callable's return value, if any.
* @throws Exception On warnings thrown by the callable.
* @noinspection PhpUnusedParameterInspection
*/
private static function throw_on_warnings( $callable, $label ) {
$old_error_reporting = error_reporting( - 1 );
$old_display_errors = ini_set( 'display_errors', 'stderr' );
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
set_error_handler(
/**
* Temporary error handler.
*
* @see https://php-legacy-docs.zend.com/manual/php5/en/function.set-error-handler
* @see https://www.php.net/manual/en/function.set-error-handler.php
*
* @param int $errno Level of the error raised.
* @param string $errstr Error message.
* @param string|null $errfile Filename that the error was raised in.
* @param int|null $errline Line number where the error was raised.
* @param array|null $errcontext Deprecated, unused.
*
* @return mixed
* @throws Exception
*/
// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
function ( $errno, $errstr, $errfile = null, $errline = null, $errcontext = null ) {
throw new Exception( "$errstr (file: $errfile; line: $errline)" );
}
);
$result = null;
$error_message = null;
try {
$result = $callable();
} catch ( Throwable $throwable ) {
$error_message = $throwable->getMessage();
}
restore_error_handler();
ini_set( 'display_errors', $old_display_errors );
error_reporting( $old_error_reporting );
if ( $error_message !== null ) {
throw new Exception( "$label failed: $error_message" );
}
return $result;
}
/**
* Return canonicalized absolute pathname, throw on warnings / errors.
*
* @see https://php-legacy-docs.zend.com/manual/php5/en/function.realpath
* @see https://www.php.net/manual/en/function.realpath.php
*
* @param string $path Path being checked.
*
* @return string Canonicalized absolute pathname
* @throws Exception On invalid parameters, or if realpath() has returned false or thrown warnings.
*/
public static function t_realpath( $path ) {
// PHP 5.x won't complain about parameter being unset, so let's do it ourselves.
if ( ! $path ) {
throw new Exception( 'Filename for realpath() is unset' );
}
$label = "realpath( '$path' )";
$realpath_result = static::throw_on_warnings(
function () use ( $path ) {
return realpath( $path );
},
$label
);
if ( false === $realpath_result ) {
throw new Exception( "Unable to $label" );
}
return $realpath_result;
}
/**
* Check whether a file or directory exists, throw on warnings / errors.
*
* @see https://php-legacy-docs.zend.com/manual/php5/en/function.file-exists
* @see https://www.php.net/manual/en/function.file-exists.php
*
* @param string $filename Path to the file or directory.
*
* @return bool True if the file or directory specified by filename exists; false otherwise.
* @throws Exception On invalid parameters, or if file_exists() has thrown warnings.
*/
public static function t_file_exists( $filename ) {
// PHP 5.x won't complain about parameter being unset, so let's do it ourselves.
if ( ! $filename ) {
throw new Exception( 'Filename for file_exists() is unset' );
}
return static::throw_on_warnings(
function () use ( $filename ) {
return file_exists( $filename );
},
"file_exists( '$filename' )"
);
}
/**
* Tell whether the filename (or a directory) is readable, throw on warnings / errors.
*
* @see https://php-legacy-docs.zend.com/manual/php5/en/function.is-readable
* @see https://www.php.net/manual/en/function.is-readable.php
*
* @param string $filename Filename (or directory) to check.
*
* @return bool True if the filename (or a directory) exists and is readable, false otherwise.
* @throws Exception On invalid parameters, or if is_readable() has thrown warnings.
*/
public static function t_is_readable( $filename ) {
// PHP 5.x won't complain about parameter being unset, so let's do it ourselves.
if ( ! $filename ) {
throw new Exception( 'Filename for is_readable() is unset' );
}
return static::throw_on_warnings(
function () use ( $filename ) {
return is_readable( $filename );
},
"is_readable( '$filename' )"
);
}
/**
* Tell whether the filename (or a directory) is writable, throw on warnings / errors.
*
* @see https://php-legacy-docs.zend.com/manual/php5/en/function.is-writable
* @see https://www.php.net/manual/en/function.is-writable.php
*
* @param string $filename Filename (or directory) to check.
*
* @return bool True if the filename (or a directory) exists and is writable, false otherwise.
* @throws Exception On invalid parameters, or if is_writable() has thrown warnings.
*/
public static function t_is_writable( $filename ) {
// PHP 5.x won't complain about parameter being unset, so let's do it ourselves.
if ( ! $filename ) {
throw new Exception( 'Filename for is_writable() is unset' );
}
return static::throw_on_warnings(
function () use ( $filename ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable
return is_writable( $filename );
},
"is_writable( '$filename' )"
);
}
/**
* Get file size, throw on warnings / errors.
*
* @see https://php-legacy-docs.zend.com/manual/php5/en/function.filesize
* @see https://www.php.net/manual/en/function.filesize.php
*
* @param string $filename Path to the file.
*
* @return int Size of the file in bytes
* @throws Exception On invalid parameters, or if filesize() has thrown warnings.
*/
public static function t_filesize( $filename ) {
// PHP 5.x won't complain about parameter being unset, so let's do it ourselves.
if ( ! $filename ) {
throw new Exception( 'Filename for filesize() is unset' );
}
$label = "filesize( '$filename' )";
$filesize_result = static::throw_on_warnings(
function () use ( $filename ) {
return filesize( $filename );
},
$label
);
if ( false === $filesize_result ) {
throw new Exception( "Unable to $label" );
}
return $filesize_result;
}
/**
* Get file modification time, throw on warnings / errors.
*
* @see https://php-legacy-docs.zend.com/manual/php5/en/function.filemtime
* @see https://www.php.net/manual/en/function.filemtime.php
*
* @param string $filename Path to the file.
*
* @return int The time the file was last modified
* @throws Exception On invalid parameters, or if filemtime() has thrown warnings.
*/
public static function t_filemtime( $filename ) {
// PHP 5.x won't complain about parameter being unset, so let's do it ourselves.
if ( ! $filename ) {
throw new Exception( 'Filename for filemtime() is unset' );
}
$label = "filemtime( '$filename' )";
$filemtime_result = static::throw_on_warnings(
function () use ( $filename ) {
return filemtime( $filename );
},
$label
);
if ( false === $filemtime_result ) {
throw new Exception( "Unable to $label" );
}
return $filemtime_result;
}
/**
* Tell whether the filename is a directory (follow symlinks), throw on warnings / errors.
*
* @see https://php-legacy-docs.zend.com/manual/php5/en/function.is-dir
* @see https://www.php.net/manual/en/function.is-dir.php
*
* @param string $filename Path to the file.
*
* @return bool True if the filename (or the symlink's target) exists and is a directory, false otherwise.
* @throws Exception On invalid parameters, if is_dir() has thrown warnings, or has failed.
*/
public static function t_is_dir( $filename ) {
// PHP 5.x won't complain about parameter being unset, so let's do it ourselves.
if ( ! $filename ) {
throw new Exception( 'Filename for is_dir() is unset' );
}
return static::throw_on_warnings(
function () use ( $filename ) {
return is_dir( $filename );
},
"is_dir( '$filename' )"
);
}
/**
* Make a directory, throw on warnings / errors.
*
* @see https://php-legacy-docs.zend.com/manual/php5/en/function.mkdir
* @see https://www.php.net/manual/en/function.mkdir.php
*
* @param string $directory Directory path.
* @param int $permissions Permissions of the newly created directory.
* @param bool $recursive If true, then any parent directories to the directory specified will also be created,
* with the same permissions.
*
* @return void
* @throws Exception On invalid parameters, if mkdir() has thrown warnings, or has failed.
*/
public static function t_mkdir( $directory, $permissions = 0777, $recursive = false ) {
// PHP 5.x won't complain about permissions being null, so let's do it ourselves.
if ( $permissions === null ) {
throw new Exception( 'Permissions for mkdir() are unset' );
}
$label = "mkdir( '$directory', 0" . decoct( $permissions ) . ', ' . ( $recursive ? 'true' : 'false' ) . ' )';
$mkdir_result = static::throw_on_warnings(
function () use ( $directory, $permissions, $recursive ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_mkdir
return mkdir( $directory, $permissions, $recursive );
},
$label
);
if ( false === $mkdir_result ) {
throw new Exception( "Unable to $label" );
}
}
/**
* List files and directories inside the specified path, throw on warnings / errors.
*
* @see https://php-legacy-docs.zend.com/manual/php5/en/function.scandir
* @see https://www.php.net/manual/en/function.scandir.php
*
* @param string $directory Directory that will be scanned.
*
* @return string An array of filenames.
* @throws Exception If scandir() has thrown warnings, or has failed.
*/
public static function t_scandir( $directory ) {
// PHP 5.x won't complain about parameter being unset, so let's do it ourselves.
if ( ! $directory ) {
throw new Exception( 'Directory for scandir() is unset' );
}
$label = "scandir( '$directory' )";
$scandir_result = static::throw_on_warnings(
function () use ( $directory ) {
return scandir( $directory );
},
$label
);
if ( false === $scandir_result ) {
throw new Exception( "Unable to $label" );
}
return $scandir_result;
}
/**
* Remove a directory, throw on warnings / errors.
*
* @see https://php-legacy-docs.zend.com/manual/php5/en/function.rmdir
* @see https://www.php.net/manual/en/function.rmdir.php
*
* @param string $directory Directory path.
*
* @return void
* @throws Exception On invalid parameters, if rmdir() has thrown warnings, or has failed.
*/
public static function t_rmdir( $directory ) {
// PHP 5.x won't complain about parameter being unset, so let's do it ourselves.
if ( ! $directory ) {
throw new Exception( 'Directory for mkdir() is unset' );
}
$label = "rmdir( '$directory' )";
$rmdir_result = static::throw_on_warnings(
function () use ( $directory ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir
return rmdir( $directory );
},
$label
);
if ( false === $rmdir_result ) {
throw new Exception( "Unable to $label" );
}
}
/**
* Delete a file, throw on warnings / errors.
*
* @see https://php-legacy-docs.zend.com/manual/php5/en/function.unlink
* @see https://www.php.net/manual/en/function.unlink.php
*
* @param string $filename Path to the file.
*
* @return void
* @throws Exception If unlink() has thrown warnings, or has failed.
*/
public static function t_unlink( $filename ) {
$label = "unlink( '$filename' )";
$unlink_result = static::throw_on_warnings(
function () use ( $filename ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
return unlink( $filename );
},
$label
);
if ( false === $unlink_result ) {
throw new Exception( "Unable to $label" );
}
}
/**
* Write data to a file, throw on warnings / errors.
*
* @see https://php-legacy-docs.zend.com/manual/php5/en/function.file-put-contents
* @see https://www.php.net/manual/en/function.file-put-contents.php
*
* @param string $filename Path to the file where to write the data.
* @param string $data The data to write.
*
* @return void
* @throws Exception If file_put_contents() has thrown warnings, has failed, or if it didn't write all the bytes.
*/
public static function t_file_put_contents( $filename, $data ) {
// PHP 5.x won't complain about parameter being unset, so let's do it ourselves.
if ( ! $filename ) {
throw new Exception( 'Filename for file_put_contents() is unset' );
}
if ( $data === null ) {
throw new Exception( 'Data to write is null' );
}
$data_length = strlen( $data );
$label = "file_put_contents( '$filename', $data_length bytes of data )";
$number_of_bytes_written = static::throw_on_warnings(
function () use ( $filename, $data ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
return file_put_contents( $filename, $data );
},
$label
);
if ( false === $number_of_bytes_written ) {
throw new Exception( "Unable to $label" );
}
if ( $number_of_bytes_written !== $data_length ) {
throw new Exception(
"$label was expected to write $data_length bytes, but wrote $number_of_bytes_written bytes"
);
}
}
/**
* Read entire file into a string, throw on warnings / errors.
*
* @see https://php-legacy-docs.zend.com/manual/php5/en/function.file-get-contents
* @see https://www.php.net/manual/en/function.file-get-contents.php
*
* @param string $filename Name of the file to read.
*
* @return string The read data.
* @throws Exception If file_get_contents() has thrown warnings, or has failed.
*/
public static function t_file_get_contents( $filename ) {
// PHP 5.x won't complain about parameter being unset, so let's do it ourselves.
if ( ! $filename ) {
throw new Exception( 'Filename for file_get_contents() is unset' );
}
$label = "file_get_contents( '$filename' )";
$file_get_contents_result = static::throw_on_warnings(
function () use ( $filename ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
return file_get_contents( $filename );
},
$label
);
if ( false === $file_get_contents_result ) {
throw new Exception( "Unable to $label" );
}
return $file_get_contents_result;
}
}

View File

@ -5,7 +5,12 @@ 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.0.0] - 2023-11-20
### Changed
- Updated required PHP version to >= 7.0. [#34192]
## [1.15.4] - 2023-09-19
- Minor internal updates.
## [1.15.3] - 2023-06-26
@ -187,6 +192,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Trying to add deterministic initialization.
[2.0.0]: https://github.com/Automattic/jetpack-config/compare/v1.15.4...v2.0.0
[1.15.4]: https://github.com/Automattic/jetpack-config/compare/v1.15.3...v1.15.4
[1.15.3]: https://github.com/Automattic/jetpack-config/compare/v1.15.2...v1.15.3
[1.15.2]: https://github.com/Automattic/jetpack-config/compare/v1.15.1...v1.15.2

View File

@ -4,11 +4,20 @@ Full details of the Automattic Security Policy can be found on [automattic.com](
## 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.
Generally, only the latest version of Jetpack and its associated plugins have continued support. If a critical vulnerability is found in the current version of a plugin, 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.
Our HackerOne program covers the below plugin software, as well as a variety of related projects and infrastructure:
* [Jetpack](https://jetpack.com/)
* Jetpack Backup
* Jetpack Boost
* Jetpack CRM
* Jetpack Protect
* Jetpack Search
* Jetpack Social
* Jetpack VideoPress
**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.**

View File

@ -3,9 +3,11 @@
"description": "Jetpack configuration package that initializes other packages and configures Jetpack's functionality. Can be used as a base for all variants of Jetpack package usage.",
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {},
"require": {
"php": ">=7.0"
},
"require-dev": {
"automattic/jetpack-changelogger": "^3.3.9"
"automattic/jetpack-changelogger": "^4.0.0"
},
"suggest": {
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
@ -25,7 +27,7 @@
"link-template": "https://github.com/Automattic/jetpack-config/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "1.15.x-dev"
"dev-trunk": "2.0.x-dev"
}
}
}

View File

@ -5,6 +5,60 @@ 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.0] - 2024-01-18
### Added
- Adding support for IDC when site URL is an IP address. [#34753]
### Changed
- Adjust 'get_site_id()' method to return null if there's no blog ID. [#34976]
## [2.1.1] - 2024-01-04
### Changed
- Updated package dependencies. [#34815]
## [2.1.0] - 2023-12-03
### Added
- Added the welcome banner to My Jetpack. [#34384]
- Updated XMLRPC endpoint 'jetpack.idcUrlValidation' to accept an argument specifying whether to attempt reusing existing URL secret. [#34262]
### Changed
- Updated package dependencies. [#34411]
## [2.0.3] - 2023-11-24
## [2.0.2] - 2023-11-21
### Changed
- Replaced usage of strpos() with str_contains(). [#34137]
## [2.0.1] - 2023-11-21
## [2.0.0] - 2023-11-20
### Added
- Confirm blog ID and access token were saved before proceeding with connection flow. [#34136]
### Changed
- Replace usage of strpos() with str_starts_with(). [#34135]
- Updated required PHP version to >= 7.0. [#34192]
### Fixed
- Ensured that partner partners are passed on during the connection process, regardless of the plugin you use to connect. [#33832]
## [1.60.1] - 2023-11-14
### Changed
- Updated package dependencies. [#34093]
## [1.60.0] - 2023-11-13
### Added
- Added a 'source' query param to the Jetpack connect URL. [#33984]
## [1.59.0] - 2023-11-08
### Added
- Added a method to check if Jetpack is ready for uninstall cleanup. [#33920]
## [1.58.3] - 2023-11-03
### Fixed
- Make sure scheme history option is an array. [#33905]
## [1.58.2] - 2023-10-19
### Changed
- Updated package dependencies. [#33687]
@ -896,6 +950,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Separate the connection library into its own package.
[2.2.0]: https://github.com/Automattic/jetpack-connection/compare/v2.1.1...v2.2.0
[2.1.1]: https://github.com/Automattic/jetpack-connection/compare/v2.1.0...v2.1.1
[2.1.0]: https://github.com/Automattic/jetpack-connection/compare/v2.0.3...v2.1.0
[2.0.3]: https://github.com/Automattic/jetpack-connection/compare/v2.0.2...v2.0.3
[2.0.2]: https://github.com/Automattic/jetpack-connection/compare/v2.0.1...v2.0.2
[2.0.1]: https://github.com/Automattic/jetpack-connection/compare/v2.0.0...v2.0.1
[2.0.0]: https://github.com/Automattic/jetpack-connection/compare/v1.60.1...v2.0.0
[1.60.1]: https://github.com/Automattic/jetpack-connection/compare/v1.60.0...v1.60.1
[1.60.0]: https://github.com/Automattic/jetpack-connection/compare/v1.59.0...v1.60.0
[1.59.0]: https://github.com/Automattic/jetpack-connection/compare/v1.58.3...v1.59.0
[1.58.3]: https://github.com/Automattic/jetpack-connection/compare/v1.58.2...v1.58.3
[1.58.2]: https://github.com/Automattic/jetpack-connection/compare/v1.58.1...v1.58.2
[1.58.1]: https://github.com/Automattic/jetpack-connection/compare/v1.58.0...v1.58.1
[1.58.0]: https://github.com/Automattic/jetpack-connection/compare/v1.57.5...v1.58.0

View File

@ -4,18 +4,19 @@
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"automattic/jetpack-a8c-mc-stats": "^1.4.22",
"automattic/jetpack-admin-ui": "^0.2.23",
"automattic/jetpack-constants": "^1.6.23",
"automattic/jetpack-roles": "^1.4.25",
"automattic/jetpack-status": "^1.18.5",
"automattic/jetpack-redirect": "^1.7.27"
"php": ">=7.0",
"automattic/jetpack-a8c-mc-stats": "^2.0.0",
"automattic/jetpack-admin-ui": "^0.3.1",
"automattic/jetpack-constants": "^2.0.0",
"automattic/jetpack-roles": "^2.0.0",
"automattic/jetpack-status": "^2.0.2",
"automattic/jetpack-redirect": "^2.0.0"
},
"require-dev": {
"automattic/wordbless": "@dev",
"yoast/phpunit-polyfills": "1.1.0",
"brain/monkey": "2.6.1",
"automattic/jetpack-changelogger": "^3.3.11"
"automattic/jetpack-changelogger": "^4.0.5"
},
"suggest": {
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
@ -56,7 +57,7 @@
"link-template": "https://github.com/Automattic/jetpack-connection/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "1.58.x-dev"
"dev-trunk": "2.2.x-dev"
}
},
"config": {

View File

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

View File

@ -1 +1 @@
!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}();
(()=>{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})();

View File

@ -129,6 +129,8 @@ class Jetpack_Options {
'dismissed_backup_review_restore', // (bool) Determines if the component review request is dismissed for successful restore requests.
'dismissed_backup_review_backups', // (bool) Determines if the component review request is dismissed for successful backup requests.
'identity_crisis_url_secret', // (array) The IDC URL secret and its expiration date.
'identity_crisis_ip_requester', // (array) The IDC IP address and its expiration date.
'dismissed_welcome_banner', // (bool) Determines if the welcome banner has been dismissed or not.
);
}

View File

@ -160,7 +160,7 @@ class Jetpack_Signature {
$signature_details = compact( 'token', 'timestamp', 'nonce', 'body_hash', 'method', 'url' );
if ( 0 !== strpos( $token, "$this->token:" ) ) {
if ( ! str_starts_with( $token, "$this->token:" ) ) {
return new WP_Error( 'token_mismatch', 'Incorrect token', compact( 'signature_details' ) );
}

View File

@ -10,6 +10,7 @@ namespace Automattic\Jetpack\Connection;
use Automattic\Jetpack\A8c_Mc_Stats;
use Automattic\Jetpack\Constants;
use Automattic\Jetpack\Heartbeat;
use Automattic\Jetpack\Partner;
use Automattic\Jetpack\Roles;
use Automattic\Jetpack\Status;
use Automattic\Jetpack\Status\Host;
@ -138,6 +139,9 @@ class Manager {
// Initialize token locks.
new Tokens_Locks();
// Initial Partner management.
Partner::init();
}
/**
@ -274,7 +278,7 @@ class Manager {
$jetpack_methods = array();
foreach ( $methods as $method => $callback ) {
if ( 0 === strpos( $method, 'jetpack.' ) ) {
if ( str_starts_with( $method, 'jetpack.' ) ) {
$jetpack_methods[ $method ] = $callback;
}
}
@ -441,7 +445,7 @@ class Manager {
$post_data = $_POST;
$file_hashes = array();
foreach ( $post_data as $post_data_key => $post_data_value ) {
if ( 0 !== strpos( $post_data_key, '_jetpack_file_hmac_' ) ) {
if ( ! str_starts_with( $post_data_key, '_jetpack_file_hmac_' ) ) {
continue;
}
$post_data_key = substr( $post_data_key, strlen( '_jetpack_file_hmac_' ) );
@ -1188,7 +1192,7 @@ class Manager {
$jetpack_public = false;
}
\Jetpack_Options::update_options(
Jetpack_Options::update_options(
array(
'id' => (int) $registration_details->jetpack_id,
'public' => $jetpack_public,
@ -1199,6 +1203,13 @@ class Manager {
$this->get_tokens()->update_blog_token( (string) $registration_details->jetpack_secret );
if ( ! Jetpack_Options::get_option( 'id' ) || ! $this->get_tokens()->get_access_token() ) {
return new WP_Error(
'connection_data_save_failed',
'Failed to save connection data in the database'
);
}
$alternate_authorization_url = isset( $registration_details->alternate_authorization_url ) ? $registration_details->alternate_authorization_url : '';
add_filter(
@ -1916,6 +1927,7 @@ class Manager {
'site_created' => $this->get_assumed_site_creation_date(),
'allow_site_connection' => ! $this->has_connected_owner(),
'calypso_env' => ( new Host() )->get_calypso_env(),
'source' => ( new Host() )->get_source_query(),
)
);
@ -1923,7 +1935,10 @@ class Manager {
$api_url = $this->api_url( 'authorize' );
return add_query_arg( $body, $api_url );
$url = add_query_arg( $body, $api_url );
/** This filter is documented in plugins/jetpack/class-jetpack.php */
return apply_filters( 'jetpack_build_authorize_url', $url );
}
/**
@ -2544,18 +2559,36 @@ class Manager {
/**
* Get the WPCOM or self-hosted site ID.
*
* @return int|WP_Error
* @param bool $quiet Return null instead of an error.
*
* @return int|WP_Error|null
*/
public static function get_site_id() {
public static function get_site_id( $quiet = false ) {
$is_wpcom = ( defined( 'IS_WPCOM' ) && IS_WPCOM );
$site_id = $is_wpcom ? get_current_blog_id() : \Jetpack_Options::get_option( 'id' );
if ( ! $site_id ) {
return new \WP_Error(
'unavailable_site_id',
__( 'Sorry, something is wrong with your Jetpack connection.', 'jetpack-connection' ),
403
);
return $quiet
? null
: new \WP_Error(
'unavailable_site_id',
__( 'Sorry, something is wrong with your Jetpack connection.', 'jetpack-connection' ),
403
);
}
return (int) $site_id;
}
/**
* Check if Jetpack is ready for uninstall cleanup.
*
* @param string $current_plugin_slug The current plugin's slug.
*
* @return bool
*/
public static function is_ready_for_cleanup( $current_plugin_slug ) {
$active_plugins = get_option( Plugin_Storage::ACTIVE_PLUGINS_OPTION_NAME );
return empty( $active_plugins ) || ! is_array( $active_plugins )
|| ( count( $active_plugins ) === 1 && array_key_exists( $current_plugin_slug, $active_plugins ) );
}
}

View File

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

View File

@ -2,7 +2,7 @@
/**
* Class for the Jetpack partner coupon logic.
*
* @package automattic/jetpack-partner
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack;
@ -21,7 +21,8 @@ if ( ! defined( 'ABSPATH' ) ) {
/**
* Class Jetpack_Partner_Coupon
*
* @since 1.6.0
* @since partner-1.6.0
* @since 2.0.0
*/
class Partner_Coupon {
@ -436,7 +437,8 @@ class Partner_Coupon {
/**
* Allow external code to add additional supported partners.
*
* @since 1.6.0
* @since partner-1.6.0
* @since 2.0.0
*
* @param array $supported_partners A list of supported partners.
* @return array
@ -453,7 +455,8 @@ class Partner_Coupon {
/**
* Allow external code to add additional supported presets.
*
* @since 1.6.0
* @since partner-1.6.0
* @since 2.0.0
*
* @param array $supported_presets A list of supported presets.
* @return array

View File

@ -1,8 +1,8 @@
<?php
/**
* Jetpack Partner package.
* Jetpack Partner utilities.
*
* @package automattic/jetpack-partner
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack;
@ -10,7 +10,8 @@ namespace Automattic\Jetpack;
/**
* This class introduces functionality used by Jetpack hosting partners.
*
* @since 1.0.0
* @since partner-1.0.0
* @since 2.0.0
*/
class Partner {
@ -27,7 +28,8 @@ class Partner {
/**
* Singleton instance.
*
* @since 1.0.0
* @since partner-1.0.0
* @since 2.0.0
*
* @var Partner This class instance.
*/
@ -42,8 +44,10 @@ class Partner {
/**
* Initializes the class or returns the singleton.
*
* @since partner-1.0.0
* @since 2.0.0
*
* @return Partner | false
* @since 1.0.0
*/
public static function init() {
if ( self::$instance === null ) {
@ -85,10 +89,12 @@ class Partner {
/**
* Adds the partner subsidiary code to the passed array.
*
* @since partner-1.5.0
* @since 2.0.0
*
* @param array $params The parameters array.
*
* @return array
* @since 1.5.0
*/
public function add_subsidiary_id_to_params_array( $params ) {
if ( ! is_array( $params ) ) {
@ -100,10 +106,12 @@ class Partner {
/**
* Adds the affiliate code to the passed array.
*
* @since partner-1.5.0
* @since 2.0.0
*
* @param array $params The parameters array.
*
* @return array
* @since 1.5.0
*/
public function add_affiliate_code_to_params_array( $params ) {
if ( ! is_array( $params ) ) {
@ -115,11 +123,13 @@ class Partner {
/**
* Returns the passed URL with the partner code added as a URL query arg.
*
* @since partner-1.0.0
* @since 2.0.0
*
* @param string $type The partner code.
* @param string $url The URL where the partner subsidiary id will be added.
*
* @return string The passed URL with the partner code added.
* @since 1.0.0
*/
public function add_code_as_query_arg( $type, $url ) {
return add_query_arg( $this->get_code_as_array( $type ), $url );
@ -128,9 +138,11 @@ class Partner {
/**
* Gets the partner code in an associative array format
*
* @since partner-1.5.0
* @since 2.0.0
*
* @param string $type The partner code.
* @return array
* @since 1.5.0
*/
private function get_code_as_array( $type ) {
switch ( $type ) {
@ -156,10 +168,12 @@ class Partner {
/**
* Returns a partner code.
*
* @since partner-1.0.0
* @since 2.0.0
*
* @param string $type This can be either 'affiliate' or 'subsidiary'. Returns empty string when code is unknown.
*
* @return string The partner code.
* @since 1.0.0
*/
public function get_partner_code( $type ) {
switch ( $type ) {
@ -167,19 +181,21 @@ class Partner {
/**
* Allow to filter the affiliate code.
*
* @param string $affiliate_code The affiliate code, blank by default.
*
* @since 1.0.0
* @since partner-1.0.0
* @since-jetpack 6.9.0
* @since 2.0.0
*
* @param string $affiliate_code The affiliate code, blank by default.
*/
return apply_filters( 'jetpack_affiliate_code', get_option( 'jetpack_affiliate_code', '' ) );
case self::SUBSIDIARY_CODE:
/**
* Allow to filter the partner subsidiary id.
*
* @param string $subsidiary_id The partner subsidiary id, blank by default.
* @since partner-1.0.0
* @since 2.0.0
*
* @since 1.0.0
* @param string $subsidiary_id The partner subsidiary id, blank by default.
*/
return apply_filters(
'jetpack_partner_subsidiary_id',

View File

@ -90,6 +90,7 @@ class Urls {
$option_key = self::HTTPS_CHECK_OPTION_PREFIX . $callable;
$parsed_url = wp_parse_url( $new_value );
if ( ! $parsed_url ) {
return $new_value;
}
@ -98,7 +99,12 @@ class Urls {
} else {
$scheme = '';
}
$scheme_history = get_option( $option_key, array() );
$scheme_history = get_option( $option_key, array() );
if ( ! is_array( $scheme_history ) ) {
$scheme_history = array();
}
$scheme_history[] = $scheme;
// Limit length to self::HTTPS_CHECK_HISTORY.

View File

@ -60,6 +60,7 @@ class XMLRPC_Async_Call {
self::$clients[ $client_blog_id ][ $user_id ] = new Jetpack_IXR_ClientMulticall( array( 'user_id' => $user_id ) );
}
// https://plugins.trac.wordpress.org/ticket/2041
if ( function_exists( 'ignore_user_abort' ) ) {
ignore_user_abort( true );
}

View File

@ -107,9 +107,7 @@ class Authorize_Redirect {
remove_filter( 'jetpack_connect_request_body', array( __CLASS__, 'filter_connect_request_body' ) );
remove_filter( 'jetpack_connect_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
/**
* This filter is documented in plugins/jetpack/class-jetpack.php
*/
/** This filter is documented in plugins/jetpack/class-jetpack.php */
return apply_filters( 'jetpack_build_authorize_url', $url );
}

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).
## [2.0.0] - 2023-11-20
### Changed
- Updated required PHP version to >= 7.0. [#34192]
## [1.6.23] - 2023-08-23
### Changed
- Updated package dependencies. [#32605]
@ -154,6 +158,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Packages: Finish the constants package
[2.0.0]: https://github.com/Automattic/jetpack-constants/compare/v1.6.23...v2.0.0
[1.6.23]: https://github.com/Automattic/jetpack-constants/compare/v1.6.22...v1.6.23
[1.6.22]: https://github.com/Automattic/jetpack-constants/compare/v1.6.21...v1.6.22
[1.6.21]: https://github.com/Automattic/jetpack-constants/compare/v1.6.20...v1.6.21

View File

@ -4,11 +4,20 @@ Full details of the Automattic Security Policy can be found on [automattic.com](
## 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.
Generally, only the latest version of Jetpack and its associated plugins have continued support. If a critical vulnerability is found in the current version of a plugin, 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.
Our HackerOne program covers the below plugin software, as well as a variety of related projects and infrastructure:
* [Jetpack](https://jetpack.com/)
* Jetpack Backup
* Jetpack Boost
* Jetpack CRM
* Jetpack Protect
* Jetpack Search
* Jetpack Social
* Jetpack VideoPress
**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.**

View File

@ -3,11 +3,13 @@
"description": "A wrapper for defining constants in a more testable way.",
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {},
"require": {
"php": ">=7.0"
},
"require-dev": {
"brain/monkey": "2.6.1",
"yoast/phpunit-polyfills": "1.1.0",
"automattic/jetpack-changelogger": "^3.3.8"
"automattic/jetpack-changelogger": "^4.0.0"
},
"suggest": {
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
@ -34,7 +36,7 @@
"link-template": "https://github.com/Automattic/jetpack-constants/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "1.6.x-dev"
"dev-trunk": "2.0.x-dev"
}
}
}

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).
## [2.0.1] - 2023-11-21
### Changed
- Added a note of non-usage of PHP8+ functions yet. [#34137]
## [2.0.0] - 2023-11-20
### Changed
- Updated required PHP version to >= 7.0. [#34192]
## [1.5.1] - 2023-11-14
## [1.5.0] - 2023-11-13
### Added
- Added 'cookieinformationscanner' and 'facebookexternalhit' to the bot user agent list. [#34026]
## [1.4.27] - 2023-08-23
### Changed
- Updated package dependencies. [#32605]
@ -163,6 +177,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Moving jetpack_is_mobile into a package
[2.0.1]: https://github.com/Automattic/jetpack-device-detection/compare/v2.0.0...v2.0.1
[2.0.0]: https://github.com/Automattic/jetpack-device-detection/compare/v1.5.1...v2.0.0
[1.5.1]: https://github.com/Automattic/jetpack-device-detection/compare/v1.5.0...v1.5.1
[1.5.0]: https://github.com/Automattic/jetpack-device-detection/compare/v1.4.27...v1.5.0
[1.4.27]: https://github.com/Automattic/jetpack-device-detection/compare/v1.4.26...v1.4.27
[1.4.26]: https://github.com/Automattic/jetpack-device-detection/compare/v1.4.25...v1.4.26
[1.4.25]: https://github.com/Automattic/jetpack-device-detection/compare/v1.4.24...v1.4.25

View File

@ -4,11 +4,20 @@ Full details of the Automattic Security Policy can be found on [automattic.com](
## 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.
Generally, only the latest version of Jetpack and its associated plugins have continued support. If a critical vulnerability is found in the current version of a plugin, 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.
Our HackerOne program covers the below plugin software, as well as a variety of related projects and infrastructure:
* [Jetpack](https://jetpack.com/)
* Jetpack Backup
* Jetpack Boost
* Jetpack CRM
* Jetpack Protect
* Jetpack Search
* Jetpack Social
* Jetpack VideoPress
**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.**

View File

@ -3,10 +3,12 @@
"description": "A way to detect device types based on User-Agent header.",
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {},
"require": {
"php": ">=7.0"
},
"require-dev": {
"yoast/phpunit-polyfills": "1.1.0",
"automattic/jetpack-changelogger": "^3.3.8"
"automattic/jetpack-changelogger": "^4.0.2"
},
"suggest": {
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
@ -33,7 +35,7 @@
"link-template": "https://github.com/Automattic/jetpack-device-detection/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "1.4.x-dev"
"dev-trunk": "2.0.x-dev"
}
}
}

View File

@ -17,6 +17,9 @@ use function Automattic\Jetpack\Device_Detection\wp_unslash;
* Class Device_Detection
*
* Determine if the current User Agent matches the passed $kind.
*
* Note: str_contains() and other PHP8+ functions that have a polyfill in core are not used here,
* as wp-includes/compat.php may not be loaded yet.
*/
class Device_Detection {

View File

@ -17,6 +17,9 @@ require_once __DIR__ . '/functions.php';
/**
* A class providing device properties detection.
*
* Note: str_contains() and other PHP8+ functions that have a polyfill in core are not used here,
* as wp-includes/compat.php may not be loaded yet.
*/
class User_Agent_Info {
@ -147,63 +150,63 @@ class User_Agent_Info {
* @return string The matched User Agent name, false otherwise.
*/
public function get_mobile_user_agent_name() {
if ( $this->is_chrome_for_iOS() ) { // Keep this check before the safari rule.
if ( static::is_chrome_for_iOS() ) { // Keep this check before the safari rule.
return 'chrome-for-ios';
} elseif ( $this->is_iphone_or_ipod( 'iphone-safari' ) ) {
} elseif ( static::is_iphone_or_ipod( 'iphone-safari' ) ) {
return 'iphone';
} elseif ( $this->is_ipad( 'ipad-safari' ) ) {
} elseif ( static::is_ipad( 'ipad-safari' ) ) {
return 'ipad';
} elseif ( $this->is_android_tablet() ) { // Keep this check before the android rule.
} elseif ( static::is_android_tablet() ) { // Keep this check before the android rule.
return 'android_tablet';
} elseif ( $this->is_android() ) {
} elseif ( static::is_android() ) {
return 'android';
} elseif ( $this->is_blackberry_10() ) {
} elseif ( static::is_blackberry_10() ) {
return 'blackberry_10';
} elseif ( $this->is_blackbeberry() ) {
} elseif ( static::is_blackbeberry() ) {
return 'blackberry';
} elseif ( $this->is_WindowsPhone7() ) {
} elseif ( static::is_WindowsPhone7() ) {
return 'win7';
} elseif ( $this->is_windows_phone_8() ) {
} elseif ( static::is_windows_phone_8() ) {
return 'winphone8';
} elseif ( $this->is_opera_mini() ) {
} elseif ( static::is_opera_mini() ) {
return 'opera-mini';
} elseif ( $this->is_opera_mini_dumb() ) {
} elseif ( static::is_opera_mini_dumb() ) {
return 'opera-mini-dumb';
} elseif ( $this->is_opera_mobile() ) {
} elseif ( static::is_opera_mobile() ) {
return 'opera-mobi';
} elseif ( $this->is_blackberry_tablet() ) {
} elseif ( static::is_blackberry_tablet() ) {
return 'blackberry_tablet';
} elseif ( $this->is_kindle_fire() ) {
} elseif ( static::is_kindle_fire() ) {
return 'kindle-fire';
} elseif ( $this->is_PalmWebOS() ) {
} elseif ( static::is_PalmWebOS() ) {
return 'webos';
} elseif ( $this->is_S60_OSSBrowser() ) {
} elseif ( static::is_S60_OSSBrowser() ) {
return 'series60';
} elseif ( $this->is_firefox_os() ) {
} elseif ( static::is_firefox_os() ) {
return 'firefoxOS';
} elseif ( $this->is_firefox_mobile() ) {
} elseif ( static::is_firefox_mobile() ) {
return 'firefox_mobile';
} elseif ( $this->is_MaemoTablet() ) {
} elseif ( static::is_MaemoTablet() ) {
return 'maemo';
} elseif ( $this->is_MeeGo() ) {
} elseif ( static::is_MeeGo() ) {
return 'meego';
} elseif ( $this->is_TouchPad() ) {
} elseif ( static::is_TouchPad() ) {
return 'hp_tablet';
} elseif ( $this->is_facebook_for_iphone() ) {
} elseif ( static::is_facebook_for_iphone() ) {
return 'facebook-for-iphone';
} elseif ( $this->is_facebook_for_ipad() ) {
} elseif ( static::is_facebook_for_ipad() ) {
return 'facebook-for-ipad';
} elseif ( $this->is_twitter_for_iphone() ) {
} elseif ( static::is_twitter_for_iphone() ) {
return 'twitter-for-iphone';
} elseif ( $this->is_twitter_for_ipad() ) {
} elseif ( static::is_twitter_for_ipad() ) {
return 'twitter-for-ipad';
} elseif ( $this->is_wordpress_for_ios() ) {
} elseif ( static::is_wordpress_for_ios() ) {
return 'ios-app';
} elseif ( $this->is_iphone_or_ipod( 'iphone-not-safari' ) ) {
} elseif ( static::is_iphone_or_ipod( 'iphone-not-safari' ) ) {
return 'iphone-unknown';
} elseif ( $this->is_ipad( 'ipad-not-safari' ) ) {
} elseif ( static::is_ipad( 'ipad-not-safari' ) ) {
return 'ipad-unknown';
} elseif ( $this->is_Nintendo_3DS() ) {
} elseif ( static::is_Nintendo_3DS() ) {
return 'nintendo-3ds';
} else {
$agent = $this->useragent;
@ -245,26 +248,26 @@ class User_Agent_Info {
} elseif ( strpos( $this->useragent, 'iphone' ) !== false ) {
$this->platform = self::PLATFORM_IPHONE;
} elseif ( strpos( $this->useragent, 'android' ) !== false ) {
if ( $this->is_android_tablet() ) {
if ( static::is_android_tablet() ) {
$this->platform = self::PLATFORM_ANDROID_TABLET;
} else {
$this->platform = self::PLATFORM_ANDROID;
}
} elseif ( $this->is_kindle_fire() ) {
} elseif ( static::is_kindle_fire() ) {
$this->platform = self::PLATFORM_ANDROID_TABLET;
} elseif ( $this->is_blackberry_10() ) {
} elseif ( static::is_blackberry_10() ) {
$this->platform = self::PLATFORM_BLACKBERRY_10;
} elseif ( strpos( $this->useragent, 'blackberry' ) !== false ) {
$this->platform = self::PLATFORM_BLACKBERRY;
} elseif ( $this->is_blackberry_tablet() ) {
} elseif ( static::is_blackberry_tablet() ) {
$this->platform = self::PLATFORM_BLACKBERRY;
} elseif ( $this->is_symbian_platform() ) {
} elseif ( static::is_symbian_platform() ) {
$this->platform = self::PLATFORM_SYMBIAN;
} elseif ( $this->is_symbian_s40_platform() ) {
} elseif ( static::is_symbian_s40_platform() ) {
$this->platform = self::PLATFORM_SYMBIAN_S40;
} elseif ( $this->is_J2ME_platform() ) {
} elseif ( static::is_J2ME_platform() ) {
$this->platform = self::PLATFORM_J2ME_MIDP;
} elseif ( $this->is_firefox_os() ) {
} elseif ( static::is_firefox_os() ) {
$this->platform = self::PLATFORM_FIREFOX_OS;
} else {
$this->platform = false;
@ -286,77 +289,77 @@ class User_Agent_Info {
$this->isTierIphone = true;
$this->isTierRichCss = false;
$this->isTierGenericMobile = false;
} elseif ( $this->is_android() ) {
} elseif ( static::is_android() ) {
$this->matched_agent = 'android';
$this->isTierIphone = true;
$this->isTierRichCss = false;
$this->isTierGenericMobile = false;
} elseif ( $this->is_windows_phone_8() ) {
} elseif ( static::is_windows_phone_8() ) {
$this->matched_agent = 'winphone8';
$this->isTierIphone = true;
$this->isTierRichCss = false;
$this->isTierGenericMobile = false;
} elseif ( $this->is_WindowsPhone7() ) {
} elseif ( static::is_WindowsPhone7() ) {
$this->matched_agent = 'win7';
$this->isTierIphone = true;
$this->isTierRichCss = false;
$this->isTierGenericMobile = false;
} elseif ( $this->is_blackberry_10() ) {
} elseif ( static::is_blackberry_10() ) {
$this->matched_agent = 'blackberry-10';
$this->isTierIphone = true;
$this->isTierRichCss = false;
$this->isTierGenericMobile = false;
} elseif ( $this->is_blackbeberry() && 'blackberry-webkit' === $this->detect_blackberry_browser_version() ) {
} elseif ( static::is_blackbeberry() && 'blackberry-webkit' === static::detect_blackberry_browser_version() ) {
$this->matched_agent = 'blackberry-webkit';
$this->isTierIphone = true;
$this->isTierRichCss = false;
$this->isTierGenericMobile = false;
} elseif ( $this->is_blackberry_tablet() ) {
} elseif ( static::is_blackberry_tablet() ) {
$this->matched_agent = 'blackberry_tablet';
$this->isTierIphone = true;
$this->isTierRichCss = false;
$this->isTierGenericMobile = false;
} elseif ( $this->is_PalmWebOS() ) {
} elseif ( static::is_PalmWebOS() ) {
$this->matched_agent = 'webos';
$this->isTierIphone = true;
$this->isTierRichCss = false;
$this->isTierGenericMobile = false;
} elseif ( $this->is_TouchPad() ) {
} elseif ( static::is_TouchPad() ) {
$this->matched_agent = 'hp_tablet';
$this->isTierIphone = true;
$this->isTierRichCss = false;
$this->isTierGenericMobile = false;
} elseif ( $this->is_firefox_os() ) {
} elseif ( static::is_firefox_os() ) {
$this->matched_agent = 'firefoxOS';
$this->isTierIphone = true;
$this->isTierRichCss = false;
$this->isTierGenericMobile = false;
} elseif ( $this->is_firefox_mobile() ) {
} elseif ( static::is_firefox_mobile() ) {
$this->matched_agent = 'fennec';
$this->isTierIphone = true;
$this->isTierRichCss = false;
$this->isTierGenericMobile = false;
} elseif ( $this->is_opera_mobile() ) {
} elseif ( static::is_opera_mobile() ) {
$this->matched_agent = 'opera-mobi';
$this->isTierIphone = true;
$this->isTierRichCss = false;
$this->isTierGenericMobile = false;
} elseif ( $this->is_MaemoTablet() ) {
} elseif ( static::is_MaemoTablet() ) {
$this->matched_agent = 'maemo';
$this->isTierIphone = true;
$this->isTierRichCss = false;
$this->isTierGenericMobile = false;
} elseif ( $this->is_MeeGo() ) {
} elseif ( static::is_MeeGo() ) {
$this->matched_agent = 'meego';
$this->isTierIphone = true;
$this->isTierRichCss = false;
$this->isTierGenericMobile = false;
} elseif ( $this->is_kindle_touch() ) {
} elseif ( static::is_kindle_touch() ) {
$this->matched_agent = 'kindle-touch';
$this->isTierIphone = true;
$this->isTierRichCss = false;
$this->isTierGenericMobile = false;
} elseif ( $this->is_Nintendo_3DS() ) {
} elseif ( static::is_Nintendo_3DS() ) {
$this->matched_agent = 'nintendo-3ds';
$this->isTierIphone = true;
$this->isTierRichCss = false;
@ -381,18 +384,18 @@ class User_Agent_Info {
}
// The following devices are explicitly ok.
if ( $this->is_S60_OSSBrowser() ) {
if ( static::is_S60_OSSBrowser() ) {
$this->matched_agent = 'series60';
$this->isTierIphone = false;
$this->isTierRichCss = true;
$this->isTierGenericMobile = false;
} elseif ( $this->is_opera_mini() ) {
} elseif ( static::is_opera_mini() ) {
$this->matched_agent = 'opera-mini';
$this->isTierIphone = false;
$this->isTierRichCss = true;
$this->isTierGenericMobile = false;
} elseif ( $this->is_blackbeberry() ) {
$detectedDevice = $this->detect_blackberry_browser_version();
} elseif ( static::is_blackbeberry() ) {
$detectedDevice = static::detect_blackberry_browser_version();
if (
'blackberry-5' === $detectedDevice
|| 'blackberry-4.7' === $detectedDevice
@ -1559,6 +1562,8 @@ class User_Agent_Info {
'domaintunocrawler',
'grapeshotcrawler',
'cloudflare-alwaysonline',
'cookieinformationscanner', // p1699315886066389-slack-C0438NHCLSY
'facebookexternalhit', // https://www.facebook.com/externalhit_uatext.php
);
foreach ( $bot_agents as $bot_agent ) {

View File

@ -5,6 +5,44 @@ 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.15.0] - 2024-01-18
### Added
- Adding support for IDC when site URL is an IP address. [#34753]
## [0.14.1] - 2024-01-04
### Changed
- Updated package dependencies. [#34815]
## [0.14.0] - 2023-12-06
### Added
- Send a verifcation secret when URL is IP. [#34436]
### Changed
- Updated package dependencies. [#34416]
## [0.13.0] - 2023-12-03
### Added
- Store for persistent blog ID for multi-URL purposes. [#34262]
### Changed
- Updated package dependencies. [#34411]
## [0.12.1] - 2023-11-24
## [0.12.0] - 2023-11-20
### Added
- Added idc query argument to url for tracking multisite idcs. [#34090]
### Changed
- Replaced usage of strpos() with str_starts_with(). [#34135]
- Updated required PHP version to >= 7.0. [#34192]
## [0.11.3] - 2023-11-14
### Changed
- Updated package dependencies. [#34093]
## [0.11.2] - 2023-11-03
## [0.11.1] - 2023-10-19
### Changed
- Updated package dependencies. [#33687]
@ -435,6 +473,14 @@ 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.15.0]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.14.1...v0.15.0
[0.14.1]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.14.0...v0.14.1
[0.14.0]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.13.0...v0.14.0
[0.13.0]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.12.1...v0.13.0
[0.12.1]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.12.0...v0.12.1
[0.12.0]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.11.3...v0.12.0
[0.11.3]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.11.2...v0.11.3
[0.11.2]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.11.1...v0.11.2
[0.11.1]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.11.0...v0.11.1
[0.11.0]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.10.7...v0.11.0
[0.10.7]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.10.6...v0.10.7

View File

@ -1 +1 @@
<?php return array('dependencies' => array('react', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-url'), 'version' => '40e79e96702db6f6da6d');
<?php return array('dependencies' => array('react', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-url'), 'version' => '2770a423a89e22f7aed3');

View File

@ -4,14 +4,15 @@
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"automattic/jetpack-connection": "^1.58.2",
"automattic/jetpack-constants": "^1.6.23",
"automattic/jetpack-status": "^1.18.5",
"automattic/jetpack-logo": "^1.6.3",
"automattic/jetpack-assets": "^1.18.13"
"php": ">=7.0",
"automattic/jetpack-connection": "^2.2.0",
"automattic/jetpack-constants": "^2.0.0",
"automattic/jetpack-status": "^2.0.2",
"automattic/jetpack-logo": "^2.0.0",
"automattic/jetpack-assets": "^2.0.4"
},
"require-dev": {
"automattic/jetpack-changelogger": "^3.3.11",
"automattic/jetpack-changelogger": "^4.0.5",
"yoast/phpunit-polyfills": "1.1.0",
"automattic/wordbless": "@dev"
},
@ -56,7 +57,7 @@
"link-template": "https://github.com/Automattic/jetpack-identity-crisis/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "0.11.x-dev"
"dev-trunk": "0.15.x-dev"
}
},
"config": {

View File

@ -27,7 +27,12 @@ class Identity_Crisis {
/**
* Package Version
*/
const PACKAGE_VERSION = '0.11.1';
const PACKAGE_VERSION = '0.15.0';
/**
* Persistent WPCOM blog ID that stays in the options after disconnect.
*/
const PERSISTENT_BLOG_ID_OPTION_NAME = 'jetpack_persistent_blog_id';
/**
* Instance of the object.
@ -87,9 +92,13 @@ class Identity_Crisis {
add_filter( 'jetpack_remote_request_url', array( $this, 'add_idc_query_args_to_url' ) );
add_filter( 'jetpack_connection_validate_urls_for_idc_mitigation_response', array( static::class, 'add_secret_to_url_validation_response' ) );
add_filter( 'jetpack_connection_validate_urls_for_idc_mitigation_response', array( static::class, 'add_ip_requester_to_url_validation_response' ) );
add_filter( 'jetpack_options', array( static::class, 'reverse_wpcom_urls_for_idc' ) );
add_filter( 'jetpack_register_request_body', array( static::class, 'register_request_body' ) );
add_action( 'jetpack_site_registered', array( static::class, 'site_registered' ) );
$urls_in_crisis = self::check_identity_crisis();
if ( false === $urls_in_crisis ) {
return;
@ -113,6 +122,8 @@ class Identity_Crisis {
$connection->disconnect_site( false );
}
delete_option( static::PERSISTENT_BLOG_ID_OPTION_NAME );
// Clear IDC options.
self::clear_all_idc_options();
}
@ -199,10 +210,18 @@ class Identity_Crisis {
|| self::validate_sync_error_idc_option() ) {
return $url;
}
$home_url = Urls::home_url();
$site_url = Urls::site_url();
$hostname = wp_parse_url( $site_url, PHP_URL_HOST );
// If request is from an IP, make sure ip_requester option is set
if ( self::url_is_ip( $hostname ) ) {
self::maybe_update_ip_requester( $hostname );
}
$query_args = array(
'home' => Urls::home_url(),
'siteurl' => Urls::site_url(),
'home' => $home_url,
'siteurl' => $site_url,
);
if ( self::should_handle_idc() ) {
@ -213,6 +232,10 @@ class Identity_Crisis {
$query_args['migrate_for_idc'] = true;
}
if ( is_multisite() ) {
$query_args['multisite'] = true;
}
return add_query_arg( $query_args, $url );
}
@ -1337,4 +1360,153 @@ class Identity_Crisis {
return $response;
}
/**
* Check if URL is an IP.
*
* @param string $hostname The hostname to check.
* @return bool
*/
public static function url_is_ip( $hostname = null ) {
if ( ! $hostname ) {
$hostname = wp_parse_url( Urls::site_url(), PHP_URL_HOST );
}
$is_ip = filter_var( $hostname, FILTER_VALIDATE_IP ) !== false ? $hostname : false;
return $is_ip;
}
/**
* Add IDC-related data to the registration query.
*
* @param array $params The existing query params.
*
* @return array
*/
public static function register_request_body( array $params ) {
$persistent_blog_id = get_option( static::PERSISTENT_BLOG_ID_OPTION_NAME );
if ( $persistent_blog_id ) {
$params['persistent_blog_id'] = $persistent_blog_id;
$params['url_secret'] = URL_Secret::create_secret( 'registration_request_url_secret_failed' );
}
return $params;
}
/**
* Set the necessary options when site gets registered.
*
* @param int $blog_id The blog ID.
*
* @return void
*/
public static function site_registered( $blog_id ) {
update_option( static::PERSISTENT_BLOG_ID_OPTION_NAME, (int) $blog_id, false );
}
/**
* Check if we need to update the ip_requester option.
*
* @param string $hostname The hostname to check.
*
* @return void
*/
public static function maybe_update_ip_requester( $hostname ) {
// Check if transient exists
$transient_key = ip2long( $hostname );
if ( $transient_key && ! get_transient( 'jetpack_idc_ip_requester_' . $transient_key ) ) {
self::set_ip_requester_for_idc( $hostname, $transient_key );
}
}
/**
* If URL is an IP, add the IP value to the ip_requester option with its expiry value.
*
* @param string $hostname The hostname to check.
* @param int $transient_key The transient key.
*/
public static function set_ip_requester_for_idc( $hostname, $transient_key ) {
// Check if option exists
$data = Jetpack_Options::get_option( 'identity_crisis_ip_requester' );
$ip_requester = array(
'ip' => $hostname,
'expires_at' => time() + 360,
);
// If not set, initialize it
if ( empty( $data ) ) {
$data = array( $ip_requester );
} else {
$updated_data = array();
$updated_value = false;
// Remove expired values and update existing IP
foreach ( $data as $item ) {
if ( time() > $item['expires_at'] ) {
continue; // Skip expired IP
}
if ( $item['ip'] === $hostname ) {
$item['expires_at'] = time() + 360;
$updated_value = true;
}
$updated_data[] = $item;
}
if ( ! $updated_value || empty( $updated_data ) ) {
$updated_data[] = $ip_requester;
}
$data = $updated_data;
}
self::update_ip_requester( $data, $transient_key );
}
/**
* Update the ip_requester option and set a transient to expire in 5 minutes.
*
* @param array $data The data to be updated.
* @param int $transient_key The transient key.
*
* @return void
*/
public static function update_ip_requester( $data, $transient_key ) {
// Update the option
$updated = Jetpack_Options::update_option( 'identity_crisis_ip_requester', $data );
// Set a transient to expire in 5 minutes
if ( $updated ) {
$transient_name = 'jetpack_idc_ip_requester_' . $transient_key;
set_transient( $transient_name, $data, 300 );
}
}
/**
* Adds `ip_requester` to the `jetpack.idcUrlValidation` URL validation endpoint.
*
* @param array $response The enpoint response that we're modifying.
*
* @return array
*/
public static function add_ip_requester_to_url_validation_response( array $response ) {
$requesters = Jetpack_Options::get_option( 'identity_crisis_ip_requester' );
if ( $requesters ) {
// Loop through the requesters and add the IP to the response if it's not expired
$i = 0;
foreach ( $requesters as $ip ) {
if ( $ip['expires_at'] > time() ) {
$response['ip_requester'][] = $ip['ip'];
}
// Limit the response to five IPs
$i = ++$i;
if ( $i === 5 ) {
break;
}
}
return $response;
}
}
}

View File

@ -74,6 +74,24 @@ class REST_Endpoints {
'permission_callback' => array( static::class, 'url_secret_permission_check' ),
)
);
// Fetch URL verification secret.
register_rest_route(
'jetpack/v4',
'/identity-crisis/compare-url-secret',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( static::class, 'compare_url_secret' ),
'permission_callback' => array( static::class, 'compare_url_secret_permission_check' ),
'args' => array(
'secret' => array(
'description' => __( 'URL secret to compare to the ones stored in the database.', 'jetpack-idc' ),
'type' => 'string',
'required' => true,
),
),
)
);
}
/**
@ -219,6 +237,31 @@ class REST_Endpoints {
);
}
/**
* Endpoint for comparing the existing secret.
*
* @param \WP_REST_Request $request The request sent to the WP REST API.
*
* @return WP_Error|\WP_REST_Response
*/
public static function compare_url_secret( $request ) {
$match = false;
$storage = new URL_Secret();
if ( $storage->exists() ) {
$remote_secret = $request->get_param( 'secret' );
$match = $remote_secret && hash_equals( $storage->get_secret(), $remote_secret );
}
return rest_ensure_response(
array(
'code' => 'success',
'match' => $match,
)
);
}
/**
* Verify url_secret create/fetch permissions (valid blog token authentication).
*
@ -233,4 +276,20 @@ class REST_Endpoints {
array( 'status' => rest_authorization_required_code() )
);
}
/**
* The endpoint is only available on non-connected sites.
* use `/identity-crisis/url-secret` for connected sites.
*
* @return true|WP_Error
*/
public static function compare_url_secret_permission_check() {
return ( new Connection_Manager() )->is_connected()
? new WP_Error(
'invalid_connection_status',
esc_html__( 'The endpoint is not available on connected sites.', 'jetpack-idc' ),
array( 'status' => 403 )
)
: true;
}
}

View File

@ -153,7 +153,7 @@ class UI {
$priority1 = ( array_key_exists( 'priority', $c1 ) && (int) $c1['priority'] ) ? (int) $c1['priority'] : 10;
$priority2 = ( array_key_exists( 'priority', $c2 ) && (int) $c2['priority'] ) ? (int) $c2['priority'] : 10;
return $priority1 > $priority2 ? 1 : -1;
return $priority1 <=> $priority2;
}
);
@ -165,7 +165,7 @@ class UI {
continue;
}
if ( isset( $_SERVER['REQUEST_URI'] ) && 0 === strpos( filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ) ), $consumer['admin_page'] ) && strlen( $consumer['admin_page'] ) > $consumer_url_length ) {
if ( isset( $_SERVER['REQUEST_URI'] ) && str_starts_with( filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ) ), $consumer['admin_page'] ) && strlen( $consumer['admin_page'] ) > $consumer_url_length ) {
$consumer_chosen = $consumer;
$consumer_url_length = strlen( $consumer['admin_page'] );
}

View File

@ -131,4 +131,27 @@ class URL_Secret {
private function generate_secret() {
return wp_generate_password( 12, false );
}
/**
* Generate secret for response.
*
* @param string $flow used to tell which flow generated the exception.
* @return string
*/
public static function create_secret( $flow = 'generating_secret_failed' ) {
$secret = null;
try {
$secret = new self();
$secret->create();
if ( $secret->exists() ) {
$secret = $secret->get_secret();
}
} catch ( Exception $e ) {
// Track the error and proceed.
( new Tracking() )->record_user_event( $flow, array( 'current_url' => Urls::site_url() ) );
}
return $secret;
}
}

View File

@ -5,7 +5,16 @@ 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.1] - 2023-11-21
### Changed
- Added a note of non-usage of PHP8+ functions yet. [#34137]
## [0.2.0] - 2023-11-20
### Changed
- Updated required PHP version to >= 7.0. [#34192]
## [0.1.6] - 2023-09-19
- Minor internal updates.
## [0.1.5] - 2023-08-23
@ -34,6 +43,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add jetpack-ip package functionality [#28846]
- Initialized the package. [#28765]
[0.2.1]: https://github.com/automattic/jetpack-ip/compare/v0.2.0...v0.2.1
[0.2.0]: https://github.com/automattic/jetpack-ip/compare/v0.1.6...v0.2.0
[0.1.6]: https://github.com/automattic/jetpack-ip/compare/v0.1.5...v0.1.6
[0.1.5]: https://github.com/automattic/jetpack-ip/compare/v0.1.4...v0.1.5
[0.1.4]: https://github.com/automattic/jetpack-ip/compare/v0.1.3...v0.1.4

View File

@ -4,11 +4,20 @@ Full details of the Automattic Security Policy can be found on [automattic.com](
## 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.
Generally, only the latest version of Jetpack and its associated plugins have continued support. If a critical vulnerability is found in the current version of a plugin, 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.
Our HackerOne program covers the below plugin software, as well as a variety of related projects and infrastructure:
* [Jetpack](https://jetpack.com/)
* Jetpack Backup
* Jetpack Boost
* Jetpack CRM
* Jetpack Protect
* Jetpack Search
* Jetpack Social
* Jetpack VideoPress
**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.**

View File

@ -3,11 +3,13 @@
"description": "Utilities for working with IP addresses.",
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {},
"require": {
"php": ">=7.0"
},
"require-dev": {
"brain/monkey": "2.6.1",
"yoast/phpunit-polyfills": "1.1.0",
"automattic/jetpack-changelogger": "^3.3.9"
"automattic/jetpack-changelogger": "^4.0.2"
},
"suggest": {
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
@ -34,7 +36,7 @@
"link-template": "https://github.com/automattic/jetpack-ip/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "0.1.x-dev"
"dev-trunk": "0.2.x-dev"
},
"textdomain": "jetpack-ip",
"version-constants": {

View File

@ -12,7 +12,7 @@ namespace Automattic\Jetpack\IP;
*/
class Utils {
const PACKAGE_VERSION = '0.1.6';
const PACKAGE_VERSION = '0.2.1';
/**
* Get the current user's IP address.
@ -92,6 +92,7 @@ class Utils {
*/
public static function ip_is_private( $ip ) {
// We are dealing with ipv6, so we can simply rely on filter_var.
// Note: str_contains() is not used here, as wp-includes/compat.php may not be loaded in this file.
if ( false === strpos( $ip, '.' ) ) {
return ! filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE );
}
@ -117,28 +118,17 @@ class Utils {
/**
* Uses inet_pton if available to convert an IP address to a binary string.
* If inet_pton is not available, ip2long will convert the address to an integer.
* Returns false if an invalid IP address is given.
*
* NOTE: ip2long will return false for any ipv6 address. servers that do not support
* inet_pton will not support ipv6
*
* @param mixed $ip IP address.
* @return int|string|bool
*/
public static function convert_ip_address( $ip ) {
if ( function_exists( 'inet_pton' ) ) {
return inet_pton( $ip );
}
return ip2long( $ip );
return inet_pton( $ip );
}
/**
* Checks that a given IP address is within a given low - high range.
* Servers that support inet_pton will use that function to convert the ip to number,
* while other servers will use ip2long.
*
* NOTE: servers that do not support inet_pton cannot support ipv6.
*
* @param mixed $ip IP.
* @param mixed $range_low Range Low.
@ -146,23 +136,11 @@ class Utils {
* @return Bool
*/
public static function ip_address_is_in_range( $ip, $range_low, $range_high ) {
// The inet_pton will give us binary string of an ipv4 or ipv6.
// We can then use strcmp to see if the address is in range.
if ( function_exists( 'inet_pton' ) ) {
$ip_num = inet_pton( $ip );
$ip_low = inet_pton( $range_low );
$ip_high = inet_pton( $range_high );
if ( $ip_num && $ip_low && $ip_high && strcmp( $ip_num, $ip_low ) >= 0 && strcmp( $ip_num, $ip_high ) <= 0 ) {
return true;
}
// The ip2long will give us an integer of an ipv4 address only. it will produce FALSE for ipv6.
} else {
$ip_num = ip2long( $ip );
$ip_low = ip2long( $range_low );
$ip_high = ip2long( $range_high );
if ( $ip_num && $ip_low && $ip_high && $ip_num >= $ip_low && $ip_num <= $ip_high ) {
return true;
}
$ip_num = inet_pton( $ip );
$ip_low = inet_pton( $range_low );
$ip_high = inet_pton( $range_high );
if ( $ip_num && $ip_low && $ip_high && strcmp( $ip_num, $ip_low ) >= 0 && strcmp( $ip_num, $ip_high ) <= 0 ) {
return true;
}
return false;
}
@ -204,8 +182,6 @@ class Utils {
/**
* Validates the low and high IP addresses of a range.
*
* NOTE: servers that do not support inet_pton cannot support ipv6.
*
* @param string $range_low Low IP address.
* @param string $range_high High IP address.
* @return bool True if the range is valid, false otherwise.
@ -217,27 +193,15 @@ class Utils {
}
// Validate that the $range_low is lower or equal to $range_high.
if ( function_exists( 'inet_pton' ) ) {
// The inet_pton will give us binary string of an ipv4 or ipv6.
// We can then use strcmp to see if the address is in range.
$ip_low = inet_pton( $range_low );
$ip_high = inet_pton( $range_high );
if ( false === $ip_low || false === $ip_high ) {
return false;
}
if ( strcmp( $ip_low, $ip_high ) > 0 ) {
return false;
}
} else {
// The ip2long will give us an integer of an ipv4 address only. it will produce FALSE for ipv6.
$ip_low = ip2long( $range_low );
$ip_high = ip2long( $range_high );
if ( false === $ip_low || false === $ip_high ) {
return false;
}
if ( $ip_low > $ip_high ) {
return false;
}
// The inet_pton will give us binary string of an ipv4 or ipv6.
// We can then use strcmp to see if the address is in range.
$ip_low = inet_pton( $range_low );
$ip_high = inet_pton( $range_high );
if ( false === $ip_low || false === $ip_high ) {
return false;
}
if ( strcmp( $ip_low, $ip_high ) > 0 ) {
return false;
}
return true;

View File

@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [3.0.2] - 2024-01-04
### Changed
- Updated package dependencies. [#34815]
## [3.0.1] - 2023-12-03
### Changed
- Updated package dependencies. [#34411]
## [3.0.0] - 2023-11-20
### Changed
- Updated required PHP version to >= 7.0. [#34192]
### Removed
- Removed the Partner package requirement. Relying on the Connection package instead. [#33832]
## [2.5.3] - 2023-11-14
### Changed
- Updated package dependencies. [#34093]
## [2.5.2] - 2023-11-03
## [2.5.1] - 2023-10-19
### Changed
- Updated package dependencies. [#33687]
@ -641,6 +662,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Update Jetpack to use new JITM package
[3.0.2]: https://github.com/Automattic/jetpack-jitm/compare/v3.0.1...v3.0.2
[3.0.1]: https://github.com/Automattic/jetpack-jitm/compare/v3.0.0...v3.0.1
[3.0.0]: https://github.com/Automattic/jetpack-jitm/compare/v2.5.3...v3.0.0
[2.5.3]: https://github.com/Automattic/jetpack-jitm/compare/v2.5.2...v2.5.3
[2.5.2]: https://github.com/Automattic/jetpack-jitm/compare/v2.5.1...v2.5.2
[2.5.1]: https://github.com/Automattic/jetpack-jitm/compare/v2.5.0...v2.5.1
[2.5.0]: https://github.com/Automattic/jetpack-jitm/compare/v2.4.0...v2.5.0
[2.4.0]: https://github.com/Automattic/jetpack-jitm/compare/v2.3.19...v2.4.0

View File

@ -1 +1 @@
<?php return array('dependencies' => array('jquery', 'wp-polyfill'), 'version' => '76ec3c26b0b3d8144645');
<?php return array('dependencies' => array('jquery', 'wp-polyfill'), 'version' => 'be1957276194cda30e35');

File diff suppressed because one or more lines are too long

View File

@ -4,19 +4,19 @@
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"automattic/jetpack-a8c-mc-stats": "^1.4.22",
"automattic/jetpack-assets": "^1.18.13",
"automattic/jetpack-connection": "^1.58.2",
"automattic/jetpack-device-detection": "^1.4.27",
"automattic/jetpack-logo": "^1.6.3",
"automattic/jetpack-partner": "^1.7.25",
"automattic/jetpack-redirect": "^1.7.27",
"automattic/jetpack-status": "^1.18.5"
"php": ">=7.0",
"automattic/jetpack-a8c-mc-stats": "^2.0.0",
"automattic/jetpack-assets": "^2.0.4",
"automattic/jetpack-connection": "^2.1.1",
"automattic/jetpack-device-detection": "^2.0.1",
"automattic/jetpack-logo": "^2.0.0",
"automattic/jetpack-redirect": "^2.0.0",
"automattic/jetpack-status": "^2.0.2"
},
"require-dev": {
"brain/monkey": "2.6.1",
"yoast/phpunit-polyfills": "1.1.0",
"automattic/jetpack-changelogger": "^3.3.11"
"automattic/jetpack-changelogger": "^4.0.5"
},
"suggest": {
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
@ -57,7 +57,7 @@
"link-template": "https://github.com/Automattic/jetpack-jitm/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "2.5.x-dev"
"dev-trunk": "3.0.x-dev"
}
}
}

View File

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

View File

@ -5,7 +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).
## [2.0.1] - 2023-11-21
## [2.0.0] - 2023-11-20
### Changed
- Updated required PHP version to >= 7.0. [#34192]
## [1.8.4] - 2023-09-19
- Minor internal updates.
## [1.8.3] - 2023-08-23
@ -249,6 +256,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Licensing: Add support for Jetpack licenses
[2.0.1]: https://github.com/Automattic/jetpack-licensing/compare/v2.0.0...v2.0.1
[2.0.0]: https://github.com/Automattic/jetpack-licensing/compare/v1.8.4...v2.0.0
[1.8.4]: https://github.com/Automattic/jetpack-licensing/compare/v1.8.3...v1.8.4
[1.8.3]: https://github.com/Automattic/jetpack-licensing/compare/v1.8.2...v1.8.3
[1.8.2]: https://github.com/Automattic/jetpack-licensing/compare/v1.8.1...v1.8.2

View File

@ -4,11 +4,20 @@ Full details of the Automattic Security Policy can be found on [automattic.com](
## 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.
Generally, only the latest version of Jetpack and its associated plugins have continued support. If a critical vulnerability is found in the current version of a plugin, 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.
Our HackerOne program covers the below plugin software, as well as a variety of related projects and infrastructure:
* [Jetpack](https://jetpack.com/)
* Jetpack Backup
* Jetpack Boost
* Jetpack CRM
* Jetpack Protect
* Jetpack Search
* Jetpack Social
* Jetpack VideoPress
**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.**

View File

@ -4,12 +4,13 @@
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"automattic/jetpack-connection": "^1.57.5"
"php": ">=7.0",
"automattic/jetpack-connection": "^2.0.1"
},
"require-dev": {
"automattic/wordbless": "@dev",
"yoast/phpunit-polyfills": "1.1.0",
"automattic/jetpack-changelogger": "^3.3.9"
"automattic/jetpack-changelogger": "^4.0.1"
},
"suggest": {
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
@ -39,7 +40,7 @@
"link-template": "https://github.com/Automattic/jetpack-licensing/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "1.8.x-dev"
"dev-trunk": "2.0.x-dev"
}
},
"config": {

View File

@ -5,7 +5,12 @@ 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.0.0] - 2023-11-20
### Changed
- Updated required PHP version to >= 7.0. [#34192]
## [1.6.3] - 2023-09-19
- Minor internal updates.
## [1.6.2] - 2023-08-23
@ -161,6 +166,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Packages: Add a basic Jetpack Logo package
[2.0.0]: https://github.com/Automattic/jetpack-logo/compare/v1.6.3...v2.0.0
[1.6.3]: https://github.com/Automattic/jetpack-logo/compare/v1.6.2...v1.6.3
[1.6.2]: https://github.com/Automattic/jetpack-logo/compare/v1.6.1...v1.6.2
[1.6.1]: https://github.com/Automattic/jetpack-logo/compare/v1.6.0...v1.6.1

View File

@ -4,11 +4,20 @@ Full details of the Automattic Security Policy can be found on [automattic.com](
## 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.
Generally, only the latest version of Jetpack and its associated plugins have continued support. If a critical vulnerability is found in the current version of a plugin, 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.
Our HackerOne program covers the below plugin software, as well as a variety of related projects and infrastructure:
* [Jetpack](https://jetpack.com/)
* Jetpack Backup
* Jetpack Boost
* Jetpack CRM
* Jetpack Protect
* Jetpack Search
* Jetpack Social
* Jetpack VideoPress
**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.**

View File

@ -3,10 +3,12 @@
"description": "A logo for Jetpack",
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {},
"require": {
"php": ">=7.0"
},
"require-dev": {
"yoast/phpunit-polyfills": "1.1.0",
"automattic/jetpack-changelogger": "^3.3.9"
"automattic/jetpack-changelogger": "^4.0.0"
},
"suggest": {
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
@ -33,7 +35,7 @@
"link-template": "https://github.com/Automattic/jetpack-logo/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "1.6.x-dev"
"dev-trunk": "2.0.x-dev"
}
}
}

View File

@ -5,6 +5,135 @@ 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).
## [4.6.0] - 2024-01-18
### Added
- Add hosting provider check. [#34864]
- Add Jetpack Manage banner. [#35078]
## [4.5.0] - 2024-01-18
### Changed
- Use blog ID instead of site slug in checkout URL. [#34976]
## [4.4.0] - 2024-01-15
### Changed
- add plan check to My Jetpack Akismet product card [#34905]
- Prevent new users from seeing JITMs [#34927]
- To avoid displaying the Welcome banner to every user, now we only display it to new users. [#34883]
## [4.3.0] - 2024-01-08
### Added
- Add a check to determine if a user is "new" to Jetpack. [#34821]
- Add a button that links to the connection screen to the Welcome Banner in My Jetpack. [#34858]
### Changed
- Add a product interstitial in My Jetpack for stats. [#34772]
- Added an image to Social interstitial. [#34814]
- Update Akismet card on My Jetpack to go to interstitial screen when there is no API key. [#34817]
## [4.2.1] - 2024-01-04
### Changed
- Updated package dependencies. [#34815] [#34816]
### Fixed
- My Jetpack: Jetpack footer links are now consistent with footers in rest of Jetpack screens. [#34787]
## [4.2.0] - 2024-01-02
### Fixed
- Show JP Creator as active when JP Complete is purchased. [#34806]
## [4.1.4] - 2023-12-20
### Changed
- Updated package dependencies. [#34694]
## [4.1.3] - 2023-12-11
### Changed
- Updated Jetpack AI interstitial to repeat the feature's list on all the tiers. [#34541]
## [4.1.2] - 2023-12-06
### Changed
- Updated package dependencies. [#34416]
### Fixed
- Creator Card: fix typo. [#34478]
## [4.1.1] - 2023-12-05
### Fixed
- My Jetpack: Fix outdated product cache issue when enabling tiers. [#34428]
## [4.1.0] - 2023-12-03
### Added
- Added Jetpack Creator to My Jetpack. [#34307]
- Added the welcome banner to My Jetpack. [#34384]
- Display a "Jetpack Manage" menu item to connected users. [#34353]
- Updated connection message to only display if the welcome banner has been dismissed. [#34420]
### Changed
- Updated package dependencies. [#34411] [#34427]
- Updated the API calls used for My Jetpack backup card. [#34197]
- Updated the CTAs in My Jetpack for more clarity and to avoid inconsistencies. [#34300]
### Fixed
- Dashboard: Prevented display of any notices from third-party services. [#34364]
- Fixed checkout error while selling the unlimited Jetpack AI plan. [#34339]
- Fixed Jetpack AI bi-yearly plan on product card. [#34276]
- Fixed product card menus. [#34285]
## [4.0.3] - 2023-11-24
### Changed
- Changed Jetpack AI insterstitial contact link to Jetpack Redirect. [#34252]
- Link Jetpack AI Contact Us button to support email on interstitial page. [#34240]
- Removed hardcoded tiers from Jetpack AI interstitial. [#34259]
- Trust next tier provided by the Jetpack AI feature endpoint. [#34239]
## [4.0.2] - 2023-11-21
### Changed
- Replace usage of strpos() with str_contains(). [#34137]
## [4.0.1] - 2023-11-21
### Added
- Marked Jetpack AI as upgradable in the interstitial page. [#34215]
## [4.0.0] - 2023-11-20
### Added
- Display an "Activity Log" menu item to connected users. [#34174]
- Added direct checkout support for products with quantity-based plans. [#34177]
- Added Jetpack AI prices by tier to the interstitial page. [#34196]
### Changed
- Replaced usage of strpos() with str_starts_with(). [#34135]
- Updated required PHP version to >= 7.0. [#34126]
- Removed condition from the backup undoable event call, this datapoint will be removed. [#33997]
## [3.12.2] - 2023-11-14
### Changed
- My Jetpack: Fix a bug causing PHP fatal errors when the Jetpack AI feature information is not available. [#34095]
- Updated package dependencies. [#34093]
## [3.12.1] - 2023-11-13
### Changed
- AI Assistant: Updated the text and image for the interstitial based on AI plan tiers. [#33981]
- AI Assistant: Removed the ToS notice from the interstitial page. [#34076]
## [3.12.0] - 2023-11-08
### Added
- Updated purchased state for VaultPress backup card on My Jetpack. [#33927]
## [3.11.1] - 2023-11-03
## [3.11.0] - 2023-10-30
### Added
- Add site data to unpurchased state of VaultPress Backup card to My Jetpack. [#33607]
## [3.10.0] - 2023-10-23
### Added
- Add jetpack-plans dependency. It will be use to restore the reverted change on #33410. [#33706]
### Changed
- Update checkout flow to connect "After" checkout vs before (if not connected). [#33257]
### Fixed
- Use Current_Plan to check/return from has_required_plan on VP product class. [#33708]
## [3.9.1] - 2023-10-19
### Changed
- Make has_required_plan return true (as it was before #33410) as a way to revert the change. [#33697]
@ -1069,6 +1198,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Created package
[4.6.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.5.0...4.6.0
[4.5.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.4.0...4.5.0
[4.4.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.3.0...4.4.0
[4.3.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.2.1...4.3.0
[4.2.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.2.0...4.2.1
[4.2.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.1.4...4.2.0
[4.1.4]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.1.3...4.1.4
[4.1.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.1.2...4.1.3
[4.1.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.1.1...4.1.2
[4.1.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.1.0...4.1.1
[4.1.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.0.3...4.1.0
[4.0.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.0.2...4.0.3
[4.0.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.0.1...4.0.2
[4.0.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.0.0...4.0.1
[4.0.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/3.12.2...4.0.0
[3.12.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/3.12.1...3.12.2
[3.12.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/3.12.0...3.12.1
[3.12.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/3.11.1...3.12.0
[3.11.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/3.11.0...3.11.1
[3.11.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/3.10.0...3.11.0
[3.10.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/3.9.1...3.10.0
[3.9.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/3.9.0...3.9.1
[3.9.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/3.8.2...3.9.0
[3.8.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/3.8.1...3.8.2

View File

@ -0,0 +1,10 @@
<svg width="105" height="105" viewBox="0 0 105 105" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.190476" y="0.19043" width="103.91" height="104" rx="12.1905" fill="#2C3338"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.1456 33.3356C32.1456 32.6707 32.6857 32.1306 33.3506 32.1306H60.9406C61.6055 32.1306 62.1456 32.6707 62.1456 33.3356V36.1306H64.1456V33.3356C64.1456 31.5661 62.7101 30.1306 60.9406 30.1306H33.3506C31.5812 30.1306 30.1456 31.5661 30.1456 33.3356V60.9256C30.1456 62.6951 31.5812 64.1306 33.3506 64.1306H36.1456V62.1306H33.3506C32.6857 62.1306 32.1456 61.5905 32.1456 60.9256V33.3356Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M37.1456 38.3356C37.1456 37.6701 37.6851 37.1306 38.3506 37.1306H65.9406C66.6061 37.1306 67.1456 37.6701 67.1456 38.3356V41.1306H69.1456V38.3356C69.1456 36.5655 67.7107 35.1306 65.9406 35.1306H38.3506C36.5806 35.1306 35.1456 36.5655 35.1456 38.3356V65.9256C35.1456 67.6957 36.5806 69.1306 38.3506 69.1306H41.1456V67.1306H38.3506C37.6851 67.1306 37.1456 66.5911 37.1456 65.9256V38.3356Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M43.3506 42.1306C42.6851 42.1306 42.1456 42.6701 42.1456 43.3356V70.9256C42.1456 71.5911 42.6851 72.1306 43.3506 72.1306H70.9406C71.6061 72.1306 72.1456 71.5911 72.1456 70.9256V43.3356C72.1456 42.6701 71.6061 42.1306 70.9406 42.1306H43.3506ZM40.1456 43.3356C40.1456 41.5655 41.5805 40.1306 43.3506 40.1306H70.9406C72.7107 40.1306 74.1456 41.5655 74.1456 43.3356V70.9256C74.1456 72.6957 72.7107 74.1306 70.9406 74.1306H43.3506C41.5805 74.1306 40.1456 72.6957 40.1456 70.9256V43.3356Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M66.7775 63.5483H47.1456V61.7073H66.7775V63.5483Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M47.1456 51.2786H66.7775V53.1195H47.1456V51.2786Z" fill="white"/>
<path d="M60.029 65.6962C61.7236 65.6962 63.0973 64.3224 63.0973 62.6279C63.0973 60.9333 61.7236 59.5596 60.029 59.5596C58.3344 59.5596 56.9607 60.9333 56.9607 62.6279C56.9607 64.3224 58.3344 65.6962 60.029 65.6962Z" fill="white"/>
<path d="M53.8941 55.2672C55.5887 55.2672 56.9624 53.8935 56.9624 52.1989C56.9624 50.5043 55.5887 49.1306 53.8941 49.1306C52.1996 49.1306 50.8258 50.5043 50.8258 52.1989C50.8258 53.8935 52.1996 55.2672 53.8941 55.2672Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1 +1 @@
<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-url'), 'version' => '86d713f826cd96de9db1');
<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-url'), 'version' => '8b33ddffd4b0e82ce03c');

View File

@ -4,20 +4,22 @@
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"automattic/jetpack-admin-ui": "^0.2.23",
"automattic/jetpack-assets": "^1.18.13",
"automattic/jetpack-connection": "^1.58.2",
"automattic/jetpack-jitm": "^2.5.1",
"automattic/jetpack-licensing": "^1.8.4",
"automattic/jetpack-plugins-installer": "^0.2.5",
"automattic/jetpack-redirect": "^1.7.27",
"automattic/jetpack-constants": "^1.6.23"
"php": ">=7.0",
"automattic/jetpack-admin-ui": "^0.3.1",
"automattic/jetpack-assets": "^2.0.4",
"automattic/jetpack-connection": "^2.2.0",
"automattic/jetpack-jitm": "^3.0.2",
"automattic/jetpack-licensing": "^2.0.1",
"automattic/jetpack-plugins-installer": "^0.3.1",
"automattic/jetpack-redirect": "^2.0.0",
"automattic/jetpack-constants": "^2.0.0",
"automattic/jetpack-plans": "^0.4.1"
},
"require-dev": {
"yoast/phpunit-polyfills": "1.1.0",
"automattic/jetpack-changelogger": "^3.3.11",
"automattic/jetpack-changelogger": "^4.0.5",
"automattic/wordbless": "@dev",
"automattic/jetpack-videopress": "^0.18.0"
"automattic/jetpack-videopress": "^0.22.2"
},
"suggest": {
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
@ -65,7 +67,7 @@
"link-template": "https://github.com/Automattic/jetpack-my-jetpack/compare/${old}...${new}"
},
"branch-alias": {
"dev-trunk": "3.9.x-dev"
"dev-trunk": "4.6.x-dev"
},
"version-constants": {
"::PACKAGE_VERSION": "src/class-initializer.php"

View File

@ -0,0 +1,57 @@
<?php
/**
* Manage the display of an "Activity Log" menu item.
*
* @package automattic/my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack;
use Automattic\Jetpack\Admin_UI\Admin_Menu;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Redirect;
/**
* Activity Log features in My Jetpack.
*/
class Activitylog {
/**
* Initialize the class and hooks needed.
*/
public static function init() {
add_action( 'admin_menu', array( self::class, 'add_submenu_jetpack' ) );
}
/**
* The page to be added to submenu
*
* @return void|null|string The resulting page's hook_suffix
*/
public static function add_submenu_jetpack() {
// Only proceed if the user is connected to WordPress.com.
if ( ! ( new Connection_Manager() )->is_user_connected() ) {
return;
}
// Do not display the menu on Multisite.
if ( is_multisite() ) {
return;
}
$args = array();
$blog_id = Connection_Manager::get_site_id( true );
if ( $blog_id ) {
$args = array( 'site' => $blog_id );
}
return Admin_Menu::add_menu(
__( 'Activity Log', 'jetpack-my-jetpack' ),
_x( 'Activity Log', 'product name shown in menu', 'jetpack-my-jetpack' ) . ' <span class="dashicons dashicons-external"></span>',
'manage_options',
esc_url( Redirect::get_url( 'cloud-activity-log-wp-menu', $args ) ),
null,
1
);
}
}

View File

@ -19,8 +19,10 @@ use Automattic\Jetpack\Licensing;
use Automattic\Jetpack\Modules;
use Automattic\Jetpack\Plugins_Installer;
use Automattic\Jetpack\Status;
use Automattic\Jetpack\Status\Host as Status_Host;
use Automattic\Jetpack\Terms_Of_Service;
use Automattic\Jetpack\Tracking;
use Jetpack;
/**
* The main Initializer class that registers the admin menu and eneuque the assets.
@ -32,13 +34,24 @@ class Initializer {
*
* @var string
*/
const PACKAGE_VERSION = '3.9.1';
const PACKAGE_VERSION = '4.6.0';
/**
* HTML container ID for the IDC screen on My Jetpack page.
*/
const IDC_CONTAINER_ID = 'my-jetpack-identity-crisis-container';
const JETPACK_PLUGIN_SLUGS = array(
'jetpack-backup',
'jetpack-boost',
'zerobscrm',
'jetpack',
'jetpack-protect',
'jetpack-social',
'jetpack-videopress',
'jetpack-search',
);
/**
* Initialize My Jetpack
*
@ -76,6 +89,12 @@ class Initializer {
// Sets up JITMS.
JITM::configure();
// Add "Activity Log" menu item.
Activitylog::init();
// Add "Jetpack Manage" menu item.
Jetpack_Manage::init();
/**
* Fires after the My Jetpack package is initialized
*
@ -171,8 +190,10 @@ class Initializer {
),
'plugins' => Plugins_Installer::get_plugins(),
'myJetpackUrl' => admin_url( 'admin.php?page=my-jetpack' ),
'myJetpackCheckoutUri' => 'admin.php?page=my-jetpack',
'topJetpackMenuItemUrl' => Admin_Menu::get_top_level_menu_item_url(),
'siteSuffix' => ( new Status() )->get_site_suffix(),
'blogID' => Connection_Manager::get_site_id( true ),
'myJetpackVersion' => self::PACKAGE_VERSION,
'myJetpackFlags' => self::get_my_jetpack_flags(),
'fileSystemWriteAccess' => self::has_file_system_write_access(),
@ -180,7 +201,16 @@ class Initializer {
'adminUrl' => esc_url( admin_url() ),
'IDCContainerID' => static::get_idc_container_id(),
'userIsAdmin' => current_user_can( 'manage_options' ),
'userIsNewToJetpack' => self::is_jetpack_user_new(),
'isStatsModuleActive' => $modules->is_active( 'stats' ),
'isUserFromKnownHost' => self::is_user_from_known_host(),
'welcomeBanner' => array(
'hasBeenDismissed' => \Jetpack_Options::get_option( 'dismissed_welcome_banner', false ),
),
'jetpackManage' => array(
'isEnabled' => Jetpack_Manage::could_use_jp_manage(),
'isAgencyAccount' => Jetpack_Manage::is_agency_account(),
),
)
);
@ -202,6 +232,72 @@ class Initializer {
}
}
/**
* Determine if the current user is "new" to Jetpack
* This is used to vary some messaging in My Jetpack
*
* On the front-end, purchases are also taken into account
*
* @return bool
*/
public static function is_jetpack_user_new() {
// is the user connected?
$connection = new Connection_Manager();
if ( $connection->is_user_connected() ) {
return false;
}
// TODO: add a data point for the last known connection/ disconnection time
// are any modules active?
$modules = new Modules();
$active_modules = $modules->get_active();
// if the Jetpack plugin is active, filter out the modules that are active by default
if ( class_exists( 'Jetpack' ) && ! empty( $active_modules ) ) {
$active_modules = array_diff( $active_modules, Jetpack::get_default_modules() );
}
if ( ! empty( $active_modules ) ) {
return false;
}
// check for other Jetpack plugins that are installed on the site (active or not)
// If there's more than one Jetpack plugin active, this user is not "new"
$plugin_slugs = array_keys( Plugins_Installer::get_plugins() );
$plugin_slugs = array_map(
static function ( $slug ) {
$parts = explode( '/', $slug );
if ( empty( $parts ) ) {
return '';
}
// Return the last segment of the filepath without the PHP extension
return str_replace( '.php', '', $parts[ count( $parts ) - 1 ] );
},
$plugin_slugs
);
$installed_jetpack_plugins = array_intersect( self::JETPACK_PLUGIN_SLUGS, $plugin_slugs );
if ( is_countable( $installed_jetpack_plugins ) && count( $installed_jetpack_plugins ) >= 2 ) {
return false;
}
// Does the site have any purchases?
$purchases = Wpcom_Products::get_site_current_purchases();
if ( ! empty( $purchases ) && ! is_wp_error( $purchases ) ) {
return false;
}
return true;
}
/**
* Determines whether the user has come from a host we can recognize.
*
* @return string
*/
public static function is_user_from_known_host() {
// Known (external) host is the one that has been determined and is not dotcom.
return ! in_array( ( new Status_Host() )->get_known_host_guess(), array( 'unknown', 'wpcom' ), true );
}
/**
* Build flags for My Jetpack UI
*
@ -246,6 +342,16 @@ class Initializer {
'permission_callback' => __CLASS__ . '::permissions_callback',
)
);
register_rest_route(
'my-jetpack/v1',
'site/dismiss-welcome-banner',
array(
'methods' => \WP_REST_Server::EDITABLE,
'callback' => __CLASS__ . '::dismiss_welcome_banner',
'permission_callback' => __CLASS__ . '::permissions_callback',
)
);
}
/**
@ -300,6 +406,16 @@ class Initializer {
return rest_ensure_response( $body, 200 );
}
/**
* Dismiss the welcome banner.
*
* @return \WP_REST_Response
*/
public static function dismiss_welcome_banner() {
\Jetpack_Options::update_option( 'dismissed_welcome_banner', true );
return rest_ensure_response( array( 'success' => true ), 200 );
}
/**
* Returns true if the site has file write access to the plugins folder, false otherwise.
*

View File

@ -0,0 +1,124 @@
<?php
/**
* Tools to manage things related to "Jetpack Manage"
* - Add Jetpack Manage menu item.
* - Check if user is an agency (used by the Jetpack Manage banner)
*
* @package automattic/my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack;
use Automattic\Jetpack\Admin_UI\Admin_Menu;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Redirect;
/**
* Jetpack Manage features in My Jetpack.
*/
class Jetpack_Manage {
/**
* Initialize the class and hooks needed.
*/
public static function init() {
add_action( 'admin_menu', array( self::class, 'add_submenu_jetpack' ) );
}
/**
* The page to be added to submenu
*
* @return void|null|string The resulting page's hook_suffix
*/
public static function add_submenu_jetpack() {
// Do not display the menu if the user has < 2 sites.
if ( ! self::could_use_jp_manage( 2 ) ) {
return;
}
$args = array();
$blog_id = Connection_Manager::get_site_id( true );
if ( $blog_id ) {
$args = array( 'site' => $blog_id );
}
return Admin_Menu::add_menu(
__( 'Jetpack Manage', 'jetpack-my-jetpack' ),
_x( 'Jetpack Manage', 'product name shown in menu', 'jetpack-my-jetpack' ) . ' <span class="dashicons dashicons-external"></span>',
'manage_options',
esc_url( Redirect::get_url( 'cloud-manage-dashboard-wp-menu', $args ) ),
null,
100
);
}
/**
* Check if the user has enough sites to be able to use Jetpack Manage.
*
* @param int $min_sites Minimum number of sites to be able to use Jetpack Manage.
*
* @return bool Return true if the user has enough sites to be able to use Jetpack Manage.
*/
public static function could_use_jp_manage( $min_sites = 2 ) {
// Only proceed if the user is connected to WordPress.com.
if ( ! ( new Connection_Manager() )->is_user_connected() ) {
return false;
}
// Do not display the menu if Jetpack plugin is not installed.
if ( ! class_exists( 'Jetpack' ) ) {
return false;
}
// Do not display the menu on Multisite.
if ( is_multisite() ) {
return false;
}
// Check if the user has the minimum number of sites.
$user_data = ( new Connection_Manager() )->get_connected_user_data( get_current_user_id() );
if ( ! isset( $user_data['site_count'] ) || $user_data['site_count'] < $min_sites ) {
return false;
}
return true;
}
/**
* Check if the user is a partner/agency.
*
* @return bool Return true if the user is a partner/agency, otherwise false.
*/
public static function is_agency_account() {
// Only proceed if the user is connected to WordPress.com.
if ( ! ( new Connection_Manager() )->is_user_connected() ) {
return false;
}
// Get the cached partner data.
$partner = get_transient( 'jetpack_partner_data' );
if ( $partner === false ) {
$wpcom_response = Client::wpcom_json_api_request_as_user( '/jetpack-partners' );
if ( 200 !== wp_remote_retrieve_response_code( $wpcom_response ) || is_wp_error( $wpcom_response ) ) {
return false;
}
$partner_data = json_decode( wp_remote_retrieve_body( $wpcom_response ) );
// The jetpack-partners endpoint will return only one partner data into an array, it uses Jetpack_Partner::find_by_owner.
if ( ! is_array( $partner_data ) || count( $partner_data ) !== 1 || ! is_object( $partner_data[0] ) ) {
return false;
}
$partner = $partner_data[0];
// Cache the partner data for 1 hour.
set_transient( 'jetpack_partner_data', $partner, HOUR_IN_SECONDS );
}
return $partner->partner_type === 'agency';
}
}

View File

@ -26,6 +26,7 @@ class Products {
'backup' => Products\Backup::class,
'boost' => Products\Boost::class,
'crm' => Products\Crm::class,
'creator' => Products\Creator::class,
'extras' => Products\Extras::class,
'jetpack-ai' => Products\Jetpack_Ai::class,
'scan' => Products\Scan::class,

View File

@ -18,12 +18,23 @@ class REST_Product_Data {
* Constructor.
*/
public function __construct() {
// Get backup undo event
register_rest_route(
'my-jetpack/v1',
'site/product-data',
'/site/backup/undo-event',
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => __CLASS__ . '::get_all_product_data',
'callback' => __CLASS__ . '::get_site_backup_undo_event',
'permission_callback' => __CLASS__ . '::permissions_callback',
)
);
register_rest_route(
'my-jetpack/v1',
'/site/backup/count-items',
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => __CLASS__ . '::count_things_that_can_be_backed_up',
'permission_callback' => __CLASS__ . '::permissions_callback',
)
);
@ -37,22 +48,100 @@ class REST_Product_Data {
}
/**
* Gets the product data for all products
* This will fetch the last rewindable event from the Activity Log and
* the last rewind_id prior to that.
*
* @return array|WP_Error
* @return array|WP_Error|null
*/
public static function get_all_product_data() {
$site_id = \Jetpack_Options::get_option( 'id' );
$wpcom_endpoint = sprintf( 'sites/%d/jetpack-product-data?locale=%2$s&force=wpcom', $site_id, get_user_locale() );
$api_version = '2';
$response = Client::wpcom_json_api_request_as_blog( $wpcom_endpoint, $api_version, array(), null, 'wpcom' );
$response_code = wp_remote_retrieve_response_code( $response );
$body = json_decode( wp_remote_retrieve_body( $response ) );
public static function get_site_backup_undo_event() {
$blog_id = \Jetpack_Options::get_option( 'id' );
if ( is_wp_error( $response ) || empty( $response['body'] ) || 200 !== $response_code ) {
return new WP_Error( 'site_products_data_fetch_failed', 'Site products data fetch failed', array( 'status' => $response_code ? $response_code : 400 ) );
$response = Client::wpcom_json_api_request_as_user(
'/sites/' . $blog_id . '/activity/rewindable?force=wpcom',
'v2',
array(),
null,
'wpcom'
);
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
return null;
}
return rest_ensure_response( $body, 200 );
$body = json_decode( $response['body'], true );
if ( ! isset( $body['current'] ) ) {
return null;
}
// Preparing the response structure
$undo_event = array(
'last_rewindable_event' => null,
'undo_backup_id' => null,
);
// List of events that will not be considered to be undo.
// Basically we should not `undo` a full backup event, but we could
// use them to undo any other action like plugin updates.
$last_event_exceptions = array(
'rewind__backup_only_complete_full',
'rewind__backup_only_complete_initial',
'rewind__backup_only_complete',
'rewind__backup_complete_full',
'rewind__backup_complete_initial',
'rewind__backup_complete',
);
// Looping through the events to find the last rewindable event and the last backup_id.
// The idea is to find the last rewindable event and then the last rewind_id before that.
$found_last_event = false;
foreach ( $body['current']['orderedItems'] as $event ) {
if ( $event['is_rewindable'] ) {
if ( ! $found_last_event && ! in_array( $event['name'], $last_event_exceptions, true ) ) {
$undo_event['last_rewindable_event'] = $event;
$found_last_event = true;
} elseif ( $found_last_event ) {
$undo_event['undo_backup_id'] = $event['rewind_id'];
break;
}
}
}
return rest_ensure_response( $undo_event, 200 );
}
/**
* This will collect a count of all the items that could be backed up
* This is used to show what backup could be doing if it is not enabled
*
* @return array
*/
public static function count_things_that_can_be_backed_up() {
$image_mime_type = 'image';
$video_mime_type = 'video';
$audio_mime_type = 'audio';
$data = array();
// Add all post types together to get the total post count
$data['total_post_count'] = array_sum( (array) wp_count_posts( 'post' ) );
// Add all page types together to get the total page count
$data['total_page_count'] = array_sum( (array) wp_count_posts( 'page' ) );
// Add all comments together to get the total comment count
$comments = (array) wp_count_comments();
$data['total_comment_count'] = $comments ? $comments['total_comments'] : 0;
// Add all image attachments together to get the total image count
$data['total_image_count'] = array_sum( (array) wp_count_attachments( $image_mime_type ) );
// Add all video attachments together to get the total video count
$data['total_video_count'] = array_sum( (array) wp_count_attachments( $video_mime_type ) );
// Add all audio attachments together to get the total audio count
$data['total_audio_count'] = array_sum( (array) wp_count_attachments( $audio_mime_type ) );
return rest_ensure_response( $data, 200 );
}
}

View File

@ -30,6 +30,8 @@ class Wpcom_Products {
*/
const CACHE_META_NAME = 'my-jetpack-cache';
const MY_JETPACK_PURCHASES_TRANSIENT_KEY = 'my-jetpack-purchases';
/**
* Fetches the list of products from WPCOM
*
@ -149,11 +151,12 @@ class Wpcom_Products {
* Get one product
*
* @param string $product_slug The product slug.
* @param bool $renew_cache A flag to force the cache to be renewed.
*
* @return ?Object The product details if found
*/
public static function get_product( $product_slug ) {
$products = self::get_products();
public static function get_product( $product_slug, $renew_cache = false ) {
$products = self::get_products( $renew_cache );
if ( ! empty( $products->$product_slug ) ) {
return $products->$product_slug;
}
@ -232,18 +235,21 @@ class Wpcom_Products {
/**
* Gets the site purchases from WPCOM.
*
* @todo Maybe add caching.
*
* @return Object|WP_Error
*/
public static function get_site_current_purchases() {
// TODO: Add a short-lived cache (less than a minute) to accommodate repeated invocation of this function.
static $purchases = null;
if ( $purchases !== null ) {
return $purchases;
}
// Check for a cached value before doing lookup
$stored_purchases = get_transient( self::MY_JETPACK_PURCHASES_TRANSIENT_KEY );
if ( $stored_purchases !== false ) {
return $stored_purchases;
}
$site_id = Jetpack_Options::get_option( 'id' );
$response = Client::wpcom_json_api_request_as_blog(
@ -259,6 +265,9 @@ class Wpcom_Products {
$body = wp_remote_retrieve_body( $response );
$purchases = json_decode( $body );
// Set short transient to help with repeated lookups on the same page load
set_transient( self::MY_JETPACK_PURCHASES_TRANSIENT_KEY, $purchases, 5 );
return $purchases;
}
}

View File

@ -92,6 +92,38 @@ class Anti_Spam extends Product {
);
}
/**
* Determine if the site has an Akismet plan by checking for an API key
*
* @return bool - whether an API key was found
*/
public static function has_required_plan() {
// Check if the site has an API key for Akismet
$akismet_api_key = apply_filters( 'akismet_get_api_key', defined( 'WPCOM_API_KEY' ) ? constant( 'WPCOM_API_KEY' ) : get_option( 'wordpress_api_key' ) );
$fallback = ! empty( $akismet_api_key );
// Check for existing plans
$purchases_data = Wpcom_Products::get_site_current_purchases();
if ( is_wp_error( $purchases_data ) ) {
return $fallback;
}
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
foreach ( $purchases_data as $purchase ) {
// Anti-spam is available as standalone bundle and as part of the Security and Complete plans.
if (
strpos( $purchase->product_slug, 'jetpack_anti_spam' ) !== false ||
str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ||
str_starts_with( $purchase->product_slug, 'jetpack_security' )
) {
return true;
}
}
}
return $fallback;
}
/**
* Get the product princing details
*

View File

@ -75,7 +75,11 @@ class Backup extends Hybrid_Product {
* @return string
*/
public static function get_description() {
return __( 'Save every change', 'jetpack-my-jetpack' );
if ( static::is_active() ) {
return __( 'Save every change', 'jetpack-my-jetpack' );
}
return __( 'Your site is not backed up', 'jetpack-my-jetpack' );
}
/**
@ -226,9 +230,9 @@ class Backup extends Hybrid_Product {
*/
public static function get_post_checkout_url() {
if ( static::is_jetpack_plugin_active() ) {
return admin_url( 'admin.php?page=jetpack#/recommendations' );
return 'admin.php?page=jetpack#/recommendations';
} elseif ( static::is_plugin_active() ) {
return admin_url( 'admin.php?page=jetpack-backup' );
return 'admin.php?page=jetpack-backup';
}
}
}

View File

@ -0,0 +1,358 @@
<?php
/**
* Creator product
*
* @package my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
/**
* Class responsible for handling the Creator product
*/
class Creator extends Product {
const FREE_TIER_SLUG = 'free';
const UPGRADED_TIER_SLUG = 'upgraded';
const UPGRADED_TIER_PRODUCT_SLUG = 'jetpack_creator_yearly';
/**
* The product slug
*
* @var string
*/
public static $slug = 'creator';
/**
* The slug of the plugin associated with this product - Creator functionalities are part of Jetpack's main plugin
*
* @var string
*/
public static $plugin_slug = self::JETPACK_PLUGIN_SLUG;
/**
* Get the plugin filename - ovewrite it and return Jetpack's
*
* @return ?string
*/
public static function get_plugin_filename() {
return self::JETPACK_PLUGIN_FILENAME;
}
/**
* Whether this product requires a user connection
*
* @var string
*/
public static $requires_user_connection = false;
/**
* Get the internationalized product name
*
* @return string
*/
public static function get_name() {
return __( 'Creator', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack Creator', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product description
*
* @return string
*/
public static function get_description() {
return __( 'Create, grow, and monetize your audience', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product long description
*
* @return string
*/
public static function get_long_description() {
return __( 'Create, grow, and monetize your audience with powerful tools for creators.', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized features list
*
* @return array Features list
*/
public static function get_features() {
return array(
__( 'Create content that stands out', 'jetpack-my-jetpack' ),
__( 'Grow your subscribers through our creator network and tools', 'jetpack-my-jetpack' ),
__( 'Monetize your online presence and earn from your website', 'jetpack-my-jetpack' ),
);
}
/**
* Get the product's available tiers
*
* @return string[] Slugs of the available tiers
*/
public static function get_tiers() {
return array(
self::UPGRADED_TIER_SLUG,
self::FREE_TIER_SLUG,
);
}
/**
* Get the internationalized comparison of free vs upgraded features
*
* @return array[] Protect features comparison
*/
public static function get_features_by_tier() {
return array(
array(
'name' => __( 'Import subscribers', 'jetpack-my-jetpack' ),
'info' => array(
'content' => __(
'Import a CSV file of your existing subscribers to be sent your Newsletter.',
'jetpack-my-jetpack'
),
),
'tiers' => array(
self::FREE_TIER_SLUG => array(
'included' => true,
'description' => __( '100 subscribers', 'jetpack-my-jetpack' ),
),
self::UPGRADED_TIER_SLUG => array(
'included' => true,
'description' => __( 'Unlimited subscribers', 'jetpack-my-jetpack' ),
),
),
),
array(
'name' => __( 'Transaction fees', 'jetpack-my-jetpack' ),
'info' => array(
'content' => __(
'<p>Fees are only collected when you accept payments.</p>
<p>Fees are based on the Jetpack plan you have and are calculated as a percentage of your revenue from 10% on the Free plan to 2% on the Creator plan (plus Stripe fees).</p>',
'jetpack-my-jetpack'
),
),
'tiers' => array(
self::FREE_TIER_SLUG => array(
'included' => true,
'description' => __( '10%', 'jetpack-my-jetpack' ),
),
self::UPGRADED_TIER_SLUG => array(
'included' => true,
'description' => __( '2%', 'jetpack-my-jetpack' ),
),
),
),
array(
'name' => __( 'Creator network', 'jetpack-my-jetpack' ),
'info' => array(
'content' => __(
'<p>The creator network is the network of websites either hosted with WordPress.com or self-hosted and connected with Jetpack.</p>
<p>Sites that are part of the creator network can gain exposure to new readers. Sites on the Creator plan have enhanced distribution to more areas of the Reader.</p>',
'jetpack-my-jetpack'
),
),
'tiers' => array(
self::FREE_TIER_SLUG => array( 'included' => true ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
array(
'name' => __( 'Jetpack Blocks', 'jetpack-my-jetpack' ),
'info' => array(
'content' => __(
'Jetpack has over 40 Gutenberg blocks to help you with your content creation, such as displaying your podcasts, showing different content to repeat visitors, creating contact forms and many more.',
'jetpack-my-jetpack'
),
),
'tiers' => array(
self::FREE_TIER_SLUG => array( 'included' => true ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
array(
'name' => __( 'Paid content gating', 'jetpack-my-jetpack' ),
'info' => array(
'content' => __(
'Lock your content behind a paid content block. To access the content, readers will need to pay a one-time fee or a recurring subscription.',
'jetpack-my-jetpack'
),
),
'tiers' => array(
self::FREE_TIER_SLUG => array( 'included' => true ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
array(
'name' => __( 'Paywall access', 'jetpack-my-jetpack' ),
'info' => array(
'content' => __(
'Add a Paywall to your content which lets your visitors read a section of your content before being asked to subscribe to continue reading.',
'jetpack-my-jetpack'
),
),
'tiers' => array(
self::FREE_TIER_SLUG => array( 'included' => true ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
array(
'name' => __( 'Newsletter', 'jetpack-my-jetpack' ),
'info' => array(
'content' => __(
'Start a Newsletter by sending your content as an email newsletter direct to your fans email inboxes.',
'jetpack-my-jetpack'
),
),
'tiers' => array(
self::FREE_TIER_SLUG => array( 'included' => true ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
array(
'name' => __( 'Pay with PayPal', 'jetpack-my-jetpack' ),
'info' => array(
'content' => __(
'Accept payment with PayPal for simple payments like eBooks, courses and more.',
'jetpack-my-jetpack'
),
),
'tiers' => array(
self::FREE_TIER_SLUG => array( 'included' => false ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
array(
'name' => __( 'WordAds', 'jetpack-my-jetpack' ),
'info' => array(
'content' => __(
'<p>WordAds adds advertisements to your website. Start earning from your website traffic.</p>
<p>Over 50 internet advertisers including Google AdSense & Adx, AppNexus, Amazon A9, AOL Marketplace, Yahoo, Criteo, and more bid to display ads in WordAds spots.</p>',
'jetpack-my-jetpack'
),
),
'tiers' => array(
self::FREE_TIER_SLUG => array( 'included' => false ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
array(
'name' => __( 'Dedicated email support', 'jetpack-my-jetpack' ),
'info' => array(
'content' => __(
'<p>Paid customers get dedicated email support from our world-class Happiness Engineers to help with any issue.</p>
<p>All other questions are handled by our team as quickly as we are able to go through the WordPress support forum.</p>',
'jetpack-my-jetpack'
),
),
'tiers' => array(
self::FREE_TIER_SLUG => array( 'included' => false ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
);
}
/**
* Get the product princing details
*
* @return array Pricing details
*/
public static function get_pricing_for_ui() {
return array(
'tiers' => array(
self::FREE_TIER_SLUG => array(
'available' => true,
'is_free' => true,
),
self::UPGRADED_TIER_SLUG => array_merge(
array(
'available' => true,
'wpcom_product_slug' => self::UPGRADED_TIER_PRODUCT_SLUG,
),
Wpcom_Products::get_product_pricing( self::UPGRADED_TIER_PRODUCT_SLUG )
),
),
);
}
/**
* Get the URL where the user manages the product
*
* @return ?string
*/
public static function get_manage_url() {
return admin_url( 'admin.php?page=jetpack#/settings?term=creator' );
}
/**
* Get the WPCOM product slug used to make the purchase
*
* @return ?string
*/
public static function get_wpcom_product_slug() {
return 'jetpack_creator_yearly';
}
/**
* Get the WPCOM product slug used to make the purchase
*
* @return ?string
*/
public static function get_wpcom_biyearly_product_slug() {
return 'jetpack_creator_bi_yearly';
}
/**
* Get the WPCOM monthly product slug used to make the purchase
*
* @return ?string
*/
public static function get_wpcom_monthly_product_slug() {
return 'jetpack_creator_monthly';
}
/**
* Checks whether the current plan (or purchases) of the site already supports the product
*
* @return boolean
*/
public static function has_required_plan() {
$purchases_data = Wpcom_Products::get_site_current_purchases();
if ( is_wp_error( $purchases_data ) ) {
return false;
}
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
foreach ( $purchases_data as $purchase ) {
// Creator is available as standalone bundle and as part of the Complete plan.
if ( strpos( $purchase->product_slug, 'jetpack_creator' ) !== false || str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) {
return true;
}
}
}
return false;
}
/**
* Checks whether the product can be upgraded - i.e. this shows the /#add-creator interstitial
*
* @return boolean
*/
public static function is_upgradable() {
$has_required_plan = self::has_required_plan();
return ! $has_required_plan;
}
}

View File

@ -7,6 +7,7 @@
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\My_Jetpack\Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
@ -74,6 +75,51 @@ class Jetpack_Ai extends Product {
return __( 'Jetpack AI', 'jetpack-my-jetpack' );
}
/**
* Get the current usage tier
*
* @return int
*/
public static function get_current_usage_tier() {
if ( ! self::is_site_connected() ) {
return 0;
}
$info = self::get_ai_assistant_feature();
// Bail early if it's not possible to fetch the feature data.
if ( is_wp_error( $info ) ) {
return null;
}
$current_tier = isset( $info['current-tier']['value'] ) ? $info['current-tier']['value'] : null;
return $current_tier;
}
/**
* Get the next usage tier
*
* @return int
*/
public static function get_next_usage_tier() {
if ( ! self::is_site_connected() ) {
return 100;
}
$info = self::get_ai_assistant_feature();
// Bail early if it's not possible to fetch the feature data.
if ( is_wp_error( $info ) ) {
return null;
}
// Trust the next tier provided by the feature data.
$next_tier = isset( $info['next-tier']['value'] ) ? $info['next-tier']['value'] : null;
return $next_tier;
}
/**
* Get the internationalized product description
*
@ -83,49 +129,164 @@ class Jetpack_Ai extends Product {
return __( 'Experimental tool to add AI to your editor', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized usage tier long description by tier
*
* @param int $tier The usage tier.
* @return string
*/
public static function get_long_description_by_usage_tier( $tier ) {
$long_descriptions = array(
1 => __( 'Jetpack AI Assistant brings the power of AI right into your WordPress editor, letting your content creation soar to new heights.', 'jetpack-my-jetpack' ),
100 => __( 'The most advanced AI technology Jetpack has to offer.', 'jetpack-my-jetpack' ),
);
$tiered_description = __( 'Upgrade and increase the amount of your available monthly requests to continue using the most advanced AI technology Jetpack has to offer.', 'jetpack-my-jetpack' );
return isset( $long_descriptions[ $tier ] ) ? $long_descriptions[ $tier ] : $tiered_description;
}
/**
* Get the internationalized product long description
*
* @return string
*/
public static function get_long_description() {
return __( 'Jetpack AI Assistant brings the power of AI right into your WordPress editor, letting your content creation soar to new heights.', 'jetpack-my-jetpack' );
$next_tier = self::get_next_usage_tier();
return self::get_long_description_by_usage_tier( $next_tier );
}
/**
* Get the internationalized usage tier features by tier
*
* @param int $tier The usage tier.
* @return string
*/
public static function get_features_by_usage_tier( $tier ) {
$features = array(
1 => array(
__( 'Artificial intelligence chatbot', 'jetpack-my-jetpack' ),
__( 'Generate text, tables, lists, and forms', 'jetpack-my-jetpack' ),
__( 'Refine the tone and content to your liking', 'jetpack-my-jetpack' ),
__( 'Get feedback about your post', 'jetpack-my-jetpack' ),
__( 'Seamless WordPress editor integration', 'jetpack-my-jetpack' ),
),
);
$tiered_features = array(
__( 'Prompt based content generation', 'jetpack-my-jetpack' ),
__( 'Generate text, tables, and lists', 'jetpack-my-jetpack' ),
__( 'Adaptive tone adjustment', 'jetpack-my-jetpack' ),
__( 'Superior spelling and grammar correction', 'jetpack-my-jetpack' ),
__( 'Title & summary generation', 'jetpack-my-jetpack' ),
__( 'Priority support', 'jetpack-my-jetpack' ),
/* translators: %d is the number of requests. */
sprintf( __( 'Up to %d requests per month', 'jetpack-my-jetpack' ), $tier ),
);
return isset( $features[ $tier ] ) ? $features[ $tier ] : $tiered_features;
}
/**
* Get the internationalized features list
*
* @return array CRM features list
* @return array Jetpack AI features list
*/
public static function get_features() {
return array(
__( 'Artificial intelligence chatbot', 'jetpack-my-jetpack' ),
__( 'Generate text, tables, lists, and forms', 'jetpack-my-jetpack' ),
__( 'Refine the tone and content to your liking', 'jetpack-my-jetpack' ),
__( 'Get feedback about your post', 'jetpack-my-jetpack' ),
__( 'Seamless WordPress editor Integration', 'jetpack-my-jetpack' ),
);
$next_tier = self::get_next_usage_tier();
return self::get_features_by_usage_tier( $next_tier );
}
/**
* Get the product princing details
* Get the product pricing details by tier
*
* @param int $tier The usage tier.
* @return array Pricing details
*/
public static function get_pricing_for_ui_by_usage_tier( $tier ) {
// Bail early if the site is not connected.
if ( ! self::is_site_connected() ) {
return array();
}
$product = Wpcom_Products::get_product( static::get_wpcom_product_slug() );
if ( empty( $product ) ) {
return array();
}
// get info about the feature.
$info = self::get_ai_assistant_feature();
// flag to indicate if the tiers are enabled, case the info is available.
$tier_plans_enabled = ( ! is_wp_error( $info ) && isset( $info['tier-plans-enabled'] ) ) ? boolval( $info['tier-plans-enabled'] ) : false;
/*
* when tiers are enabled and the price tier list is empty,
* we may need to renew the cache for the product data so
* we get the new price tier list.
*
* if the list is still empty after the fresh data, we will
* default to empty pricing (by returning an empty array).
*/
if ( empty( $product->price_tier_list ) && $tier_plans_enabled ) {
$product = Wpcom_Products::get_product( static::get_wpcom_product_slug(), true );
}
// get the base pricing for the unlimited plan, for compatibility
$base_pricing = Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() );
$price_tier_list = $product->price_tier_list;
$yearly_prices = array();
foreach ( $price_tier_list as $price_tier ) {
if ( isset( $price_tier->maximum_units ) && isset( $price_tier->maximum_price ) ) {
// The prices are in cents
$yearly_prices[ $price_tier->maximum_units ] = $price_tier->maximum_price / 100;
}
}
// add the base pricing to the list
$prices = array( 1 => $base_pricing );
foreach ( $yearly_prices as $units => $price ) {
$prices[ $units ] = array_merge(
$base_pricing,
array(
'full_price' => $price,
'discount_price' => $price,
'is_introductory_offer' => false,
'introductory_offer' => null,
)
);
}
return isset( $prices[ $tier ] ) ? $prices[ $tier ] : array();
}
/**
* Get the product pricing details
*
* @return array Pricing details
*/
public static function get_pricing_for_ui() {
$next_tier = self::get_next_usage_tier();
return array_merge(
array(
'available' => true,
'wpcom_product_slug' => static::get_wpcom_product_slug(),
),
Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() )
self::get_pricing_for_ui_by_usage_tier( $next_tier )
);
}
/**
* Get the WPCOM product slug used to make the purchase
*
* @return ?string
* @return string
*/
public static function get_wpcom_product_slug() {
return 'jetpack_ai_yearly';
@ -134,12 +295,21 @@ class Jetpack_Ai extends Product {
/**
* Get the WPCOM monthly product slug used to make the purchase
*
* @return ?string
* @return string
*/
public static function get_wpcom_monthly_product_slug() {
return 'jetpack_ai_monthly';
}
/**
* Get the WPCOM bi-yearly product slug used to make the purchase
*
* @return string
*/
public static function get_wpcom_bi_yearly_product_slug() {
return 'jetpack_ai_bi_yearly';
}
/**
* Checks whether the current plan (or purchases) of the site already supports the product
*
@ -152,10 +322,13 @@ class Jetpack_Ai extends Product {
}
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
foreach ( $purchases_data as $purchase ) {
if ( 0 === strpos( $purchase->product_slug, static::get_wpcom_product_slug() ) ) {
if ( str_starts_with( $purchase->product_slug, static::get_wpcom_product_slug() ) ) {
return true;
}
if ( 0 === strpos( $purchase->product_slug, static::get_wpcom_monthly_product_slug() ) ) {
if ( str_starts_with( $purchase->product_slug, static::get_wpcom_monthly_product_slug() ) ) {
return true;
}
if ( str_starts_with( $purchase->product_slug, static::get_wpcom_bi_yearly_product_slug() ) ) {
return true;
}
}
@ -163,6 +336,23 @@ class Jetpack_Ai extends Product {
return false;
}
/**
* Checks whether the product can be upgraded to a different product.
*
* @return boolean
*/
public static function is_upgradable() {
$has_required_plan = self::has_required_plan();
$current_tier = self::get_current_usage_tier();
// Mark as not upgradable if user is on unlimited tier or does not have any plan.
if ( ! $has_required_plan || null === $current_tier || 1 === $current_tier ) {
return false;
}
return true;
}
/**
* Get the URL where the user manages the product
*
@ -188,6 +378,11 @@ class Jetpack_Ai extends Product {
return array();
}
// Bail early if the site is not connected.
if ( ! self::is_site_connected() ) {
return array();
}
// Check if class exists. If not, try to require it once.
if ( ! class_exists( 'Jetpack_AI_Helper' ) ) {
$class_file_path = JETPACK__PLUGIN_DIR . '_inc/lib/class-jetpack-ai-helper.php';
@ -202,4 +397,13 @@ class Jetpack_Ai extends Product {
return \Jetpack_AI_Helper::get_ai_assistance_feature();
}
/**
* Checks whether the site is connected to WordPress.com.
*
* @return boolean
*/
private static function is_site_connected() {
return ( new Connection_Manager() )->is_connected();
}
}

View File

@ -220,7 +220,7 @@ class Scan extends Module_Product {
*/
public static function get_post_checkout_url() {
if ( static::is_jetpack_plugin_active() ) {
return admin_url( 'admin.php?page=jetpack#/recommendations' );
return 'admin.php?page=jetpack#/recommendations';
}
// If Jetpack is not active, it means that the user has another standalone plugin active

View File

@ -173,8 +173,8 @@ class Security extends Module_Product {
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
foreach ( $purchases_data as $purchase ) {
if (
0 === strpos( $purchase->product_slug, 'jetpack_security' ) ||
0 === strpos( $purchase->product_slug, 'jetpack_complete' )
str_starts_with( $purchase->product_slug, 'jetpack_security' ) ||
str_starts_with( $purchase->product_slug, 'jetpack_complete' )
) {
return true;
}

View File

@ -124,7 +124,28 @@ class Social extends Hybrid_Product {
* @return string
*/
public static function get_wpcom_product_slug() {
return 'jetpack_social';
return 'jetpack_social_basic_yearly';
}
/**
* Checks whether the current plan (or purchases) of the site already supports the product
*
* @return boolean
*/
public static function has_required_plan() {
$purchases_data = Wpcom_Products::get_site_current_purchases();
if ( is_wp_error( $purchases_data ) ) {
return false;
}
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
foreach ( $purchases_data as $purchase ) {
// Social is available as standalone bundle and as part of the Complete plan.
if ( strpos( $purchase->product_slug, 'jetpack_social' ) !== false || str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) {
return true;
}
}
}
return false;
}
/**

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