updated plugin Jetpack Protect version 2.0.0

This commit is contained in:
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,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;
}
/**

View File

@ -166,7 +166,7 @@ class Starter extends Module_Product {
}
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
foreach ( $purchases_data as $purchase ) {
if ( 0 === strpos( $purchase->product_slug, 'jetpack_starter' ) ) {
if ( str_starts_with( $purchase->product_slug, 'jetpack_starter' ) ) {
return true;
}
}

View File

@ -29,6 +29,20 @@ class Stats extends Module_Product {
*/
public static $module_name = 'stats';
/**
* The Plugin slug associated with stats
*
* @var string|null
*/
public static $plugin_slug = self::JETPACK_PLUGIN_SLUG;
/**
* The Plugin file associated with stats
*
* @var string|null
*/
public static $plugin_filename = self::JETPACK_PLUGIN_FILENAME;
/**
* Get the internationalized product name
*
@ -72,32 +86,29 @@ class Stats extends Module_Product {
*/
public static function get_features() {
return array(
__( 'Instant access to upcoming features', 'jetpack-my-jetpack' ),
__( 'Real-time data on visitors', 'jetpack-my-jetpack' ),
__( 'Traffic stats and trends for post and pages', 'jetpack-my-jetpack' ),
__( 'Detailed statistics about links leading to your site', 'jetpack-my-jetpack' ),
__( 'GDPR compliant', 'jetpack-my-jetpack' ),
__( 'Access to upcoming advanced features', 'jetpack-my-jetpack' ),
__( 'Priority support', 'jetpack-my-jetpack' ),
__( 'Commercial use', 'jetpack-my-jetpack' ),
);
}
/**
* Get the product princing details
* Get the product pricing details
* Only showing the pricing details for the commercial product
*
* @return array Pricing details
*/
public static function get_pricing_for_ui() {
return array_merge(
array(
'available' => true,
'wpcom_product_slug' => static::get_wpcom_product_slug(),
'wpcom_free_product_slug' => static::get_wpcom_free_product_slug(),
'wpcom_pwyw_product_slug' => static::get_wpcom_pwyw_product_slug(),
'available' => true,
'wpcom_product_slug' => static::get_wpcom_product_slug(),
),
// TODO: replace with `Wpcom_Products::get_product_pricing` once available.
// This is not yet used anywhere, so it's fine to leave it as is for now.
array(
'currency_code' => 'USD',
'full_price' => 10,
'discount_price' => 10,
'product_term' => 'month',
)
Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() )
);
}
@ -107,7 +118,7 @@ class Stats extends Module_Product {
* @return ?string
*/
public static function get_wpcom_product_slug() {
return 'jetpack_stats_monthly';
return 'jetpack_stats_yearly';
}
/**
@ -140,10 +151,10 @@ class Stats extends Module_Product {
}
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
foreach ( $purchases_data as $purchase ) {
if ( 0 === strpos( $purchase->product_slug, 'jetpack_stats' ) ) {
if ( str_starts_with( $purchase->product_slug, 'jetpack_stats' ) ) {
return true;
}
if ( 0 === strpos( $purchase->product_slug, 'jetpack_complete' ) ) {
if ( str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) {
return true;
}
}
@ -162,23 +173,36 @@ class Stats extends Module_Product {
if ( is_wp_error( $purchases_data ) ) {
return false;
}
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
foreach ( $purchases_data as $purchase ) {
if (
(
// Purchase is Jetpack Stats...
0 === strpos( $purchase->product_slug, 'jetpack_stats' ) &&
// but not Jetpack Stats Free...
false === strpos( $purchase->product_slug, 'free' )
) || 0 === strpos( $purchase->product_slug, 'jetpack_complete' )
) {
// Only Jetpack Stats paid plans should be eligible for this conditional.
// Sample product slugs: jetpack_stats_monthly
// For now, only the free and commercial tiered subs show as upgradable
$upgradeable_stats_purchases = array_filter(
$purchases_data,
static function ( $purchase ) {
// Free plan is upgradeable
if ( $purchase->product_slug === 'jetpack_stats_free_yearly' ) {
return true;
// Commercial plans are upgradeable if they have a tier
} elseif (
in_array(
$purchase->product_slug,
array( 'jetpack_stats_yearly', 'jetpack_stats_monthly', 'jetpack_stats_bi_yearly' ),
true
) &&
! empty( $purchase->current_price_tier_slug )
) {
return true;
}
return false;
}
}
);
return ! empty( $upgradeable_stats_purchases );
}
return true;
// If there are no plans found, don't consider the product as upgradeable
return false;
}
/**
@ -195,9 +219,18 @@ class Stats extends Module_Product {
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
foreach ( $purchases_data as $purchase ) {
if (
0 === strpos( $purchase->product_slug, static::get_wpcom_free_product_slug() )
str_starts_with( $purchase->product_slug, static::get_wpcom_free_product_slug() )
) {
return '&productType=personal';
} elseif (
in_array(
$purchase->product_slug,
array( 'jetpack_stats_yearly', 'jetpack_stats_monthly', 'jetpack_stats_bi_yearly' ),
true
) &&
! empty( $purchase->current_price_tier_slug )
) {
return '&productType=commercial';
}
}
}

View File

@ -7,6 +7,7 @@
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\Current_Plan;
use Automattic\Jetpack\My_Jetpack\Hybrid_Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
@ -163,7 +164,7 @@ class Videopress extends Hybrid_Product {
* @return boolean
*/
public static function has_required_plan() {
// TODO: import and perform a proper check with Current_Plan. See #33410.
return true;
// using second argument `true` to force fetching from wpcom
return Current_Plan::supports( 'videopress-1tb-storage', true );
}
}