This commit is contained in:
2026-07-02 15:54:39 -06:00
commit 9883323161
17470 changed files with 4470592 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
<?php return array('dependencies' => array('wp-api-fetch', 'wp-dom-ready', 'wp-polyfill', 'wp-url'), 'version' => '1c700443851eeb71f733');
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
@@ -0,0 +1,483 @@
<?php
/**
* Jetpack's JITM class.
*
* @package automattic/jetpack-jitm
*/
namespace Automattic\Jetpack\JITMS;
use Automattic\Jetpack\Assets;
use Automattic\Jetpack\Assets\Logo as Jetpack_Logo;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Status;
use Automattic\Jetpack\Status\Host;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
/**
* Jetpack just in time messaging through out the admin
*
* @since 1.1.0
* @since-jetpack 5.6.0
*/
class JITM {
const PACKAGE_VERSION = '4.3.44';
/**
* List of screen IDs where JITMs are allowed to display.
*
* @var string[]
*/
const APPROVED_SCREEN_IDS = array(
'jetpack',
'woo',
'shop',
'product',
);
/**
* The lazily-resolved JITM instance used by the hooks registered in configure().
*
* @var Post_Connection_JITM|Pre_Connection_JITM|null
*/
private static $configured_instance;
/**
* The configuration method that is called from the jetpack-config package.
*
* Registers the JITM hooks directly (rather than building a JITM instance up
* front) and binds the instance-dependent callbacks to a lazily-resolved
* instance, so the connection-specific JITM subclass and its dependencies are
* only loaded when one of these hooks actually fires instead of on every
* request. JITMs only render on admin screens and via the REST API.
*/
public static function configure() {
if ( did_action( 'jetpack_registered_jitms' ) ) {
// JITMs have already been registered.
return;
}
if ( ! self::are_jitms_enabled() ) {
// Do nothing.
return;
}
add_action( 'rest_api_init', array( __NAMESPACE__ . '\\Rest_Api_Endpoints', 'register_endpoints' ) );
add_action(
'current_screen',
static function ( $screen ) {
self::get_configured_instance()->prepare_jitms( $screen );
}
);
/**
* These are sync actions that we need to keep track of for jitms.
*/
add_filter(
'jetpack_sync_before_send_updated_option',
static function ( $params ) {
return self::get_configured_instance()->jetpack_track_last_sync_callback( $params );
},
99
);
/** This action is documented in projects/packages/jitm/src/class-jitm.php */
do_action( 'jetpack_registered_jitms' );
}
/**
* Pre/Post Connection JITM factory metod
*
* @return Post_Connection_JITM|Pre_Connection_JITM JITM instance.
*/
public static function get_instance() {
if ( self::is_connected() ) {
$jitm = new Post_Connection_JITM();
} else {
$jitm = new Pre_Connection_JITM();
}
return $jitm;
}
/**
* Lazily resolve and cache the connection-appropriate JITM instance used by
* the hooks registered in configure().
*
* @return Post_Connection_JITM|Pre_Connection_JITM JITM instance.
*/
private static function get_configured_instance() {
if ( null === self::$configured_instance ) {
self::$configured_instance = self::get_instance();
}
return self::$configured_instance;
}
/**
* Sets up JITM action callbacks if needed.
*/
public function register() {
if ( did_action( 'jetpack_registered_jitms' ) ) {
// JITMs have already been registered.
return;
}
if ( ! $this->jitms_enabled() ) {
// Do nothing.
return;
}
add_action( 'rest_api_init', array( __NAMESPACE__ . '\\Rest_Api_Endpoints', 'register_endpoints' ) );
add_action( 'current_screen', array( $this, 'prepare_jitms' ) );
/**
* These are sync actions that we need to keep track of for jitms.
*/
add_filter( 'jetpack_sync_before_send_updated_option', array( $this, 'jetpack_track_last_sync_callback' ), 99 );
/**
* Fires when the JITMs are registered. This action is used to ensure that
* JITMs are registered only once.
*
* @since 1.16.0
*/
do_action( 'jetpack_registered_jitms' );
}
/**
* Checks the jetpack_just_in_time_msgs filters and whether the site
* is offline to determine whether JITMs are enabled.
*
* @return bool True if JITMs are enabled, else false.
*/
public function jitms_enabled() {
return self::are_jitms_enabled();
}
/**
* Static variant of jitms_enabled() so the enabled state can be checked
* without building a JITM instance (used by configure()).
*
* @return bool True if JITMs are enabled, else false.
*/
private static function are_jitms_enabled() {
/**
* Filter to turn off all just in time messages
*
* @since 1.1.0
* @since-jetpack 3.7.0
* @since-jetpack 5.4.0 Correct docblock to reflect default arg value
*
* @param bool true Whether to show just in time messages.
*/
if ( ! apply_filters( 'jetpack_just_in_time_msgs', true ) ) {
return false;
}
// Folks cannot connect to WordPress.com and won't really be able to act on the pre-connection messages. So bail.
if ( ( new Status() )->is_offline_mode() ) {
return false;
}
return true;
}
/**
* Prepare actions according to screen and post type.
*
* @since 1.1.0
* @since-jetpack 3.8.2
*
* @uses Jetpack_Autoupdate::get_possible_failures()
*
* @param \WP_Screen $screen WP Core's screen object.
*/
public function prepare_jitms( $screen ) {
/**
* Filter to hide JITMs on certain screens.
*
* @since 1.14.0
*
* @param bool true Whether to show just in time messages.
* @param string $string->id The ID of the current screen.
*/
if ( apply_filters( 'jetpack_display_jitms_on_screen', true, $screen->id ) ) {
add_action( 'admin_enqueue_scripts', array( $this, 'jitm_enqueue_files' ) );
add_action( 'admin_notices', array( $this, 'ajax_message' ) );
add_action( 'edit_form_top', array( $this, 'ajax_message' ) );
}
}
/**
* Check if the current page is a Jetpack or WooCommerce admin page.
* Noting that this is a very basic check, and pages from other plugins may also match.
*
* @since 3.1.0
*
* @return bool True if the current page is a Jetpack or WooCommerce admin page, else false.
*/
public function is_a8c_admin_page() {
if ( ! function_exists( 'get_current_screen' ) ) {
return false;
}
$current_screen = get_current_screen();
// We never want to show JITMs on the block editor.
if (
method_exists( $current_screen, 'is_block_editor' )
&& $current_screen->is_block_editor()
) {
return false;
}
return (
$current_screen
&& $current_screen->id
&& (bool) preg_match(
'/' . implode( '|', self::APPROVED_SCREEN_IDS ) . '/',
$current_screen->id
)
);
}
/**
* Function to enqueue jitm css and js
*/
public function jitm_enqueue_files() {
// Only load those files on the Jetpack or Woo admin pages.
if ( ! $this->is_a8c_admin_page() ) {
return;
}
Assets::register_script(
'jetpack-jitm',
'../build/index.js',
__FILE__,
array(
'in_footer' => true,
'dependencies' => array(),
'enqueue' => true,
)
);
wp_localize_script(
'jetpack-jitm',
'jitm_config',
array(
'api_root' => esc_url_raw( rest_url() ),
'activate_module_text' => esc_html__( 'Activate', 'jetpack-jitm' ),
'activated_module_text' => esc_html__( 'Activated', 'jetpack-jitm' ),
'activating_module_text' => esc_html__( 'Activating', 'jetpack-jitm' ),
'settings_module_text' => esc_html__( 'Settings', 'jetpack-jitm' ),
'nonce' => wp_create_nonce( 'wp_rest' ),
)
);
}
/**
* Is the current page a block editor page?
*
* @since 1.1.0
* @since-jetpack 8.0.0
*
* @deprecated 3.1.0
*/
public function is_gutenberg_page() {
_deprecated_function( __METHOD__, '3.1.0' );
$current_screen = get_current_screen();
return ( method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor() );
}
/**
* Get's the current message path for display of a JITM
*
* @return string The message path
*/
public function get_message_path() {
$screen = get_current_screen();
return 'wp:' . $screen->id . ':' . current_filter();
}
/**
* Injects the dom to show a JITM inside of wp-admin.
*/
public function ajax_message() {
if ( ! is_admin() ) {
return;
}
// Only add this to specifically allowed pages.
if ( ! $this->is_a8c_admin_page() ) {
return;
}
$message_path = $this->get_message_path();
$query_string = _http_build_query( $_GET, '', ',' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$current_screen = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- Escaped below
?>
<div class="jetpack-jitm-message"
data-nonce="<?php echo esc_attr( wp_create_nonce( 'wp_rest' ) ); ?>"
data-ajax-nonce="<?php echo esc_attr( wp_create_nonce( 'wp_ajax_action' ) ); ?>"
data-message-path="<?php echo esc_attr( $message_path ); ?>"
data-query="<?php echo urlencode_deep( $query_string ); ?>"
data-redirect="<?php echo urlencode_deep( $current_screen ); ?>"
></div>
<?php
}
/**
* Generate the icon to display on the JITM.
*
* All icons supported in this method should be included in the array returned by
* JITM::get_supported_icons.
*
* @param string $content_icon Icon type name.
* @param bool $full_jp_logo_exists Is there a big JP logo already displayed on this screen.
*/
public function generate_icon( $content_icon, $full_jp_logo_exists ) {
$date_now = new \DateTime( 'now', wp_timezone() );
$feb_4_date = new \DateTime( '02-04-2025', wp_timezone() );
// Don't show the new Woo svg logo until after Feb 4th, 2025
$show_new_logo = $date_now >= $feb_4_date;
switch ( $content_icon ) {
case 'jetpack':
$jetpack_logo = new Jetpack_Logo();
$content_icon = '<div class="jp-emblem">' . ( ( $full_jp_logo_exists ) ? $jetpack_logo->get_jp_emblem() : $jetpack_logo->get_jp_emblem_larger() ) . '</div>';
break;
case 'woocommerce':
// After Feb 4th 2025, we can remove this date condition check ( & filter) and the old svg logo.
/**
* Filter to force display the new Woo logo in Woo JITM's, or not.
*
* @since 4.0.5
*
* @param bool $show_woo_logo Whether to show the new Woo logo or not.
*/
$content_icon = apply_filters( 'jetpack_jitm_use_new_woo_logo', $show_new_logo )
// New Woo logo
? '<div class="jp-emblem"><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 183.6 47.5" style="enable-background:new 0 0 183.6 47.5;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#873EFF;}
.st1{fill-rule:evenodd;clip-rule:evenodd;}
.st2{fill:#873EFF;}
.st3{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
.st4{fill:#FFFFFF;}
</style>
<g>
<path class="st0" d="M77.4,0c-4.3,0-7.1,1.4-9.6,6.1L56.4,27.6V8.5c0-5.7-2.7-8.5-7.7-8.5s-7.1,1.7-9.6,6.5L28.3,27.6V8.7
c0-6.1-2.5-8.7-8.6-8.7H7.3C2.6,0,0,2.2,0,6.2s2.5,6.4,7.1,6.4h5.1v24.1c0,6.8,4.6,10.8,11.2,10.8s9.6-2.6,12.9-8.7l7.2-13.5v11.4
c0,6.7,4.4,10.8,11.1,10.8s9.2-2.3,13-8.7l16.6-28C87.8,4.7,85.3,0,77.3,0C77.3,0,77.3,0,77.4,0z"/>
<path class="st0" d="M108.6,0C95,0,84.7,10.1,84.7,23.8s10.4,23.7,23.9,23.7s23.8-10.1,23.9-23.7C132.5,10.1,122.1,0,108.6,0z
M108.6,32.9c-5.1,0-8.6-3.8-8.6-9.1s3.5-9.2,8.6-9.2s8.6,3.9,8.6,9.2S113.8,32.9,108.6,32.9z"/>
<path class="st0" d="M159.7,0c-13.5,0-23.9,10.1-23.9,23.8s10.4,23.7,23.9,23.7s23.9-10.1,23.9-23.7S173.2,0,159.7,0z M159.7,32.9
c-5.2,0-8.5-3.8-8.5-9.1s3.4-9.2,8.5-9.2s8.6,3.9,8.6,9.2S164.9,32.9,159.7,32.9z"/>
</g>
</svg></div>'
// Old Woo logo
: '<div class="jp-emblem"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 168 100" xml:space="preserve" enable-background="new 0 0 168 100" width="50" height="30"><style type="text/css">
.st0{clip-path:url(#SVGID_2_);enable-background:new ;}
.st1{clip-path:url(#SVGID_4_);}
.st2{clip-path:url(#SVGID_6_);}
.st3{clip-path:url(#SVGID_8_);fill:#7F54B3;}
.st4{clip-path:url(#SVGID_10_);fill:#FFFFFE;}
.st5{clip-path:url(#SVGID_12_);fill:#FFFFFE;}
.st6{clip-path:url(#SVGID_14_);fill:#FFFFFE;}
</style><g><defs><polygon id="SVGID_1_" points="83.8 100 0 100 0 0.3 83.8 0.3 167.6 0.3 167.6 100 "/></defs><clipPath id="SVGID_2_"><use xlink:href="#SVGID_1_" overflow="visible"/></clipPath><g class="st0"><g><defs><rect id="SVGID_3_" width="168" height="100"/></defs><clipPath id="SVGID_4_"><use xlink:href="#SVGID_3_" overflow="visible"/></clipPath><g class="st1"><defs><path id="SVGID_5_" d="M15.6 0.3H152c8.6 0 15.6 7 15.6 15.6v52c0 8.6-7 15.6-15.6 15.6h-48.9l6.7 16.4L80.2 83.6H15.6C7 83.6 0 76.6 0 67.9v-52C0 7.3 7 0.3 15.6 0.3"/></defs><clipPath id="SVGID_6_"><use xlink:href="#SVGID_5_" overflow="visible"/></clipPath><g class="st2"><defs><rect id="SVGID_7_" width="168" height="100"/></defs><clipPath id="SVGID_8_"><use xlink:href="#SVGID_7_" overflow="visible"/></clipPath><rect x="-10" y="-9.7" class="st3" width="187.6" height="119.7"/></g></g></g></g></g><g><defs><path id="SVGID_9_" d="M8.4 14.5c1-1.3 2.4-2 4.3-2.1 3.5-0.2 5.5 1.4 6 4.9 2.1 14.3 4.4 26.4 6.9 36.4l15-28.6c1.4-2.6 3.1-3.9 5.2-4.1 3-0.2 4.9 1.7 5.6 5.7 1.7 9.1 3.9 16.9 6.5 23.4 1.8-17.4 4.8-30 9-37.7 1-1.9 2.5-2.9 4.5-3 1.6-0.1 3 0.3 4.3 1.4 1.3 1 2 2.3 2.1 3.9 0.1 1.2-0.1 2.3-0.7 3.3 -2.7 5-4.9 13.2-6.6 24.7 -1.7 11.1-2.3 19.8-1.9 26.1 0.1 1.7-0.1 3.2-0.8 4.5 -0.8 1.5-2 2.4-3.7 2.5 -1.8 0.1-3.6-0.7-5.4-2.5C52.4 66.7 47.4 57 43.7 44.1c-4.4 8.8-7.7 15.3-9.9 19.7 -4 7.7-7.5 11.7-10.3 11.9 -1.9 0.1-3.5-1.4-4.8-4.7 -3.5-9-7.3-26.3-11.3-52C7.1 17.3 7.5 15.8 8.4 14.5"/></defs><clipPath id="SVGID_10_"><use xlink:href="#SVGID_9_" overflow="visible"/></clipPath><rect x="-2.7" y="-0.6" class="st4" width="90.6" height="86.4"/></g><g><defs><path id="SVGID_11_" d="M155.6 25.2c-2.5-4.3-6.1-6.9-11-7.9 -1.3-0.3-2.5-0.4-3.7-0.4 -6.6 0-11.9 3.4-16.1 10.2 -3.6 5.8-5.3 12.3-5.3 19.3 0 5.3 1.1 9.8 3.3 13.6 2.5 4.3 6.1 6.9 11 7.9 1.3 0.3 2.5 0.4 3.7 0.4 6.6 0 12-3.4 16.1-10.2 3.6-5.9 5.3-12.4 5.3-19.4C159 33.4 157.9 28.9 155.6 25.2zM147 44.2c-0.9 4.5-2.7 7.9-5.2 10.1 -2 1.8-3.9 2.5-5.5 2.2 -1.7-0.3-3-1.8-4-4.4 -0.8-2.1-1.2-4.2-1.2-6.2 0-1.7 0.2-3.4 0.5-5 0.6-2.8 1.8-5.5 3.6-8.1 2.3-3.3 4.7-4.8 7.1-4.2 1.7 0.3 3 1.8 4 4.4 0.8 2.1 1.2 4.2 1.2 6.2C147.5 40.9 147.3 42.6 147 44.2z"/></defs><clipPath id="SVGID_12_"><use xlink:href="#SVGID_11_" overflow="visible"/></clipPath><rect x="109.6" y="6.9" class="st5" width="59.4" height="71.4"/></g><g><defs><path id="SVGID_13_" d="M112.7 25.2c-2.5-4.3-6.1-6.9-11-7.9 -1.3-0.3-2.5-0.4-3.7-0.4 -6.6 0-11.9 3.4-16.1 10.2 -3.5 5.8-5.3 12.3-5.3 19.3 0 5.3 1.1 9.8 3.3 13.6 2.5 4.3 6.1 6.9 11 7.9 1.3 0.3 2.5 0.4 3.7 0.4 6.6 0 12-3.4 16.1-10.2 3.5-5.9 5.3-12.4 5.3-19.4C116 33.4 114.9 28.9 112.7 25.2zM104.1 44.2c-0.9 4.5-2.7 7.9-5.2 10.1 -2 1.8-3.9 2.5-5.5 2.2 -1.7-0.3-3-1.8-4-4.4 -0.8-2.1-1.2-4.2-1.2-6.2 0-1.7 0.2-3.4 0.5-5 0.6-2.8 1.8-5.5 3.6-8.1 2.3-3.3 4.7-4.8 7.1-4.2 1.7 0.3 3 1.8 4 4.4 0.8 2.1 1.2 4.2 1.2 6.2C104.6 40.9 104.4 42.6 104.1 44.2z"/></defs><clipPath id="SVGID_14_"><use xlink:href="#SVGID_13_" overflow="visible"/></clipPath><rect x="66.7" y="6.9" class="st6" width="59.4" height="71.4"/></g></svg></div>';
break;
default:
$content_icon = '';
break;
}
return $content_icon;
}
/**
* Returns an array containing the supported icons for JITMs.
*
* The list includes an empty string, which is used when no icon should be displayed.
*
* @return array The list of supported icons.
*/
public function get_supported_icons() {
return array(
'jetpack',
'woocommerce',
'',
);
}
/**
* Stores dismiss data into an option
*
* @param string $key Dismiss key.
*/
public function save_dismiss( $key ) {
$hide_jitm = \Jetpack_Options::get_option( 'hide_jitm' );
if ( ! is_array( $hide_jitm ) ) {
$hide_jitm = array();
}
if ( ! isset( $hide_jitm[ $key ] ) || ! is_array( $hide_jitm[ $key ] ) ) {
$hide_jitm[ $key ] = array(
'last_dismissal' => 0,
'number' => 0,
);
}
$hide_jitm[ $key ] = array(
'last_dismissal' => time(),
'number' => $hide_jitm[ $key ]['number'] + 1,
);
\Jetpack_Options::update_option( 'hide_jitm', $hide_jitm );
}
/**
* Sets the 'jetpack_last_plugin_sync' transient when the active_plugins option is synced.
*
* @param array $params The action parameters.
*
* @return array Returns the action parameters unchanged.
*/
public function jetpack_track_last_sync_callback( $params ) {
/**
* This filter is documented in the Automattic\Jetpack\JITMS\Post_Connection_JITM class.
*/
if ( ! apply_filters( 'jetpack_just_in_time_msg_cache', true ) ) {
return $params;
}
if ( is_array( $params ) && isset( $params[0] ) ) {
$option = $params[0];
if ( 'active_plugins' === $option ) {
// Use the cache if we can, but not terribly important if it gets evicted.
set_transient( 'jetpack_last_plugin_sync', time(), HOUR_IN_SECONDS );
}
}
return $params;
}
/**
* Check if the current site is connected.
* On WordPress.com Simple, it is always connected.
*
* @return bool true if the site is connected, false otherwise.
*/
private static function is_connected() {
if ( ( new Host() )->is_wpcom_simple() ) {
return true;
}
$connection = new Connection_Manager();
return $connection->is_connected();
}
}
@@ -0,0 +1,434 @@
<?php
/**
* Jetpack's Post-Connection JITM class.
*
* @package automattic/jetpack-jitm
*/
namespace Automattic\Jetpack\JITMS;
use Automattic\Jetpack\A8c_Mc_Stats;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager;
use Automattic\Jetpack\Device_Detection;
use Automattic\Jetpack\Partner;
use Automattic\Jetpack\Redirect;
use Automattic\Jetpack\Tracking;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
/**
* Jetpack just in time messaging through out the admin
*
* @since 1.1.0
*
* @since-jetpack 5.6.0
*/
class Post_Connection_JITM extends JITM {
/**
* Tracking object.
*
* @var \Automattic\Jetpack\Tracking
*
* @access private
*/
public $tracking;
/**
* JITM constructor.
*/
public function __construct() {
$this->tracking = new Tracking();
}
/**
* A special filter for WooCommerce, to set a message based on local state.
*
* @param object $content The current message.
*
* @return object The new message.
*/
public static function jitm_woocommerce_services_msg( $content ) {
if ( ! function_exists( 'wc_get_base_location' ) ) {
return $content;
}
$base_location = wc_get_base_location();
switch ( $base_location['country'] ) {
case 'US':
$content->message = esc_html__( 'New free service: Show USPS shipping rates on your store! Added bonus: print shipping labels without leaving WooCommerce.', 'jetpack-jitm' );
break;
case 'CA':
$content->message = esc_html__( 'New free service: Show Canada Post shipping rates on your store!', 'jetpack-jitm' );
break;
default:
$content->message = '';
}
return $content;
}
/**
* A special filter for WooCommerce Call To Action button
*
* @return string The new CTA
*/
public static function jitm_jetpack_woo_services_install() {
return wp_nonce_url(
add_query_arg(
array(
'wc-services-action' => 'install',
),
admin_url( 'admin.php?page=wc-settings' )
),
'wc-services-install'
);
}
/**
* A special filter for WooCommerce Call To Action button.
*
* @return string The new CTA
*/
public static function jitm_jetpack_woo_services_activate() {
return wp_nonce_url(
add_query_arg(
array(
'wc-services-action' => 'activate',
),
admin_url( 'admin.php?page=wc-settings' )
),
'wc-services-install'
);
}
/**
* A special filter used in the CTA of a JITM offering to install the Creative Mail plugin.
*
* @return string The new CTA
*/
public static function jitm_jetpack_creative_mail_install() {
return wp_nonce_url(
add_query_arg(
array(
'creative-mail-action' => 'install',
),
admin_url( 'admin.php?page=jetpack-forms-admin' )
),
'creative-mail-install'
);
}
/**
* A special filter used in the CTA of a JITM offering to activate the Creative Mail plugin.
*
* @return string The new CTA
*/
public static function jitm_jetpack_creative_mail_activate() {
return wp_nonce_url(
add_query_arg(
array(
'creative-mail-action' => 'activate',
),
admin_url( 'admin.php?page=jetpack-forms-admin' )
),
'creative-mail-install'
);
}
/**
* A special filter used in the CTA of a JITM offering to install the Jetpack Backup plugin.
*
* @return string The new CTA
*/
public static function jitm_jetpack_backup_install() {
return wp_nonce_url(
add_query_arg(
array(
'jetpack-backup-action' => 'install',
),
admin_url( 'admin.php?page=jetpack' )
),
'jetpack-backup-install'
);
}
/**
* A special filter used in the CTA of a JITM offering to activate the Jetpack Backup plugin.
*
* @return string The new CTA
*/
public static function jitm_jetpack_backup_activate() {
return wp_nonce_url(
add_query_arg(
array(
'jetpack-backup-action' => 'activate',
),
admin_url( 'admin.php?page=jetpack' )
),
'jetpack-backup-install'
);
}
/**
* A special filter used in the CTA of a JITM offering to install the Jetpack Boost plugin.
*
* @return string The new CTA
*/
public static function jitm_jetpack_boost_install() {
return wp_nonce_url(
add_query_arg(
array(
'jetpack-boost-action' => 'install',
),
admin_url( 'admin.php?page=jetpack' )
),
'jetpack-boost-install'
);
}
/**
* A special filter used in the CTA of a JITM offering to activate the Jetpack Boost plugin.
*
* @return string The new CTA
*/
public static function jitm_jetpack_boost_activate() {
return wp_nonce_url(
add_query_arg(
array(
'jetpack-boost-action' => 'activate',
),
admin_url( 'admin.php?page=jetpack' )
),
'jetpack-boost-install'
);
}
/**
* Dismisses a JITM feature class so that it will no longer be shown.
*
* @param string $id The id of the JITM that was dismissed.
* @param string $feature_class The feature class of the JITM that was dismissed.
*
* @return bool Always true.
*/
public function dismiss( $id, $feature_class ) {
$this->tracking->record_user_event(
'jitm_dismiss_client',
array(
'jitm_id' => $id,
'feature_class' => $feature_class,
)
);
$this->save_dismiss( $feature_class );
return true;
}
/**
* Asks the wpcom API for the current message to display keyed on query string and message path.
*
* For sites running on the Dotcom Simple codebase, the network request is bypassed
* via Client::wpcom_json_api_request_as_blog allowing for the JITM\Engine to be called
* directly.
*
* @param string $message_path The message path to ask for.
* @param array $query Query parameters as an associative array.
* @param bool $full_jp_logo_exists If there is a full Jetpack logo already on the page.
*
* @return array The JITM's to show, or an empty array if there is nothing to show
*/
public function get_messages( $message_path, $query, $full_jp_logo_exists ) {
// WooCommerce Services.
add_filter( 'jitm_woocommerce_services_msg', array( $this, 'jitm_woocommerce_services_msg' ) );
add_filter( 'jitm_jetpack_woo_services_install', array( $this, 'jitm_jetpack_woo_services_install' ) );
add_filter( 'jitm_jetpack_woo_services_activate', array( $this, 'jitm_jetpack_woo_services_activate' ) );
// Creative Mail.
add_filter( 'jitm_jetpack_creative_mail_install', array( $this, 'jitm_jetpack_creative_mail_install' ) );
add_filter( 'jitm_jetpack_creative_mail_activate', array( $this, 'jitm_jetpack_creative_mail_activate' ) );
// Jetpack Backup.
add_filter( 'jitm_jetpack_backup_install', array( $this, 'jitm_jetpack_backup_install' ) );
add_filter( 'jitm_jetpack_backup_activate', array( $this, 'jitm_jetpack_backup_activate' ) );
// Jetpack Boost.
add_filter( 'jitm_jetpack_boost_install', array( $this, 'jitm_jetpack_boost_install' ) );
add_filter( 'jitm_jetpack_boost_activate', array( $this, 'jitm_jetpack_boost_activate' ) );
$user = wp_get_current_user();
// Unauthenticated or invalid requests just bail.
if ( ! $user ) {
return array();
}
$user_roles = implode( ',', $user->roles );
$site_id = \Jetpack_Options::get_option( 'id' );
// Build our jitm request.
$path = add_query_arg(
array(
'external_user_id' => urlencode_deep( $user->ID ),
'user_roles' => urlencode_deep( $user_roles ),
'query_string' => urlencode_deep( build_query( $query ) ),
'mobile_browser' => Device_Detection::is_smartphone() ? 1 : 0,
'_locale' => get_user_locale(),
),
sprintf( '/sites/%d/jitm/%s', $site_id, $message_path )
);
$cache_key = 'jetpack_jitm_' . substr( md5( $path ), 0, 31 );
// Attempt to get from cache.
$envelopes = get_transient( $cache_key );
// If something is in the cache and it was put in the cache after the last sync we care about, use it.
$use_cache = false;
/**
* Filter to turn off jitm caching
*
* @since 1.1.0
* @since-jetpack 5.4.0
*
* @param bool true Whether to cache just in time messages
*/
if ( apply_filters( 'jetpack_just_in_time_msg_cache', true ) ) {
$use_cache = true;
}
if ( $use_cache ) {
$last_sync = (int) get_transient( 'jetpack_last_plugin_sync' );
// The sync timestamp indicates when a plugin change was last sent to Jetpack, however,
// it's stored in a transient and doesn't stay forever. Therefore, an admin who last changed
// a plugin a month ago is likely to have $last_sync=0.
//
// If the sync timestamp is missing (value0):use the cache.
// If the timestamp exists and is older than the cached envelope:use the cache.
// If the timestamp exists and is newer:bypass and refresh.
// (This case means the JITM was created before the last plugin activate/deactivate and is invalid).
$from_cache = $envelopes && ( 0 === $last_sync || $last_sync < $envelopes['last_response_time'] );
} else {
$from_cache = false;
}
// Otherwise, ask again.
if ( ! $from_cache ) {
$wpcom_response = Client::wpcom_json_api_request_as_blog(
$path,
'2',
array(
'user_id' => $user->ID,
'user_roles' => implode( ',', $user->roles ),
),
null,
'wpcom'
);
// silently fail...might be helpful to track it?
if ( is_wp_error( $wpcom_response ) ) {
return array();
}
$envelopes = json_decode( $wpcom_response['body'] );
if ( ! is_array( $envelopes ) ) {
return array();
}
$expiration = isset( $envelopes[0] ) ? $envelopes[0]->ttl : 300;
// Do not cache if expiration is 0 or we're not using the cache.
if ( 0 !== $expiration && $use_cache ) {
$envelopes['last_response_time'] = time();
set_transient( $cache_key, $envelopes, $expiration );
}
}
$hidden_jitms = \Jetpack_Options::get_option( 'hide_jitm' );
unset( $envelopes['last_response_time'] );
/**
* Allow adding your own custom JITMs after a set of JITMs has been received.
*
* @since 1.1.0
* @since-jetpack 6.9.0
* @since-jetpack 8.3.0 - Added Message path.
*
* @param array $envelopes array of existing JITMs.
* @param string $message_path The message path to ask for.
*/
$envelopes = apply_filters( 'jetpack_jitm_received_envelopes', $envelopes, $message_path );
foreach ( $envelopes as $idx => &$envelope ) {
$dismissed_feature = isset( $hidden_jitms[ $envelope->feature_class ] ) && is_array( $hidden_jitms[ $envelope->feature_class ] ) ? $hidden_jitms[ $envelope->feature_class ] : null;
// If the this feature class has been dismissed and the request has not passed the ttl, skip it as it's been dismissed.
if ( is_array( $dismissed_feature ) && ( time() - $dismissed_feature['last_dismissal'] < $envelope->expires || $dismissed_feature['number'] >= $envelope->max_dismissal ) ) {
unset( $envelopes[ $idx ] );
continue;
}
$this->tracking->record_user_event(
'jitm_view_client',
array(
'jitm_id' => $envelope->id,
'jitm_message_path' => $message_path,
)
);
$url_params = array(
'u' => $user->ID,
);
// Get affiliate code and add it to the array of URL parameters.
$aff = Partner::init()->get_partner_code( Partner::AFFILIATE_CODE );
if ( '' !== $aff ) {
$url_params['aff'] = $aff;
}
// Check if the current user has connected their WP.com account
// and if not add this information to the the array of URL parameters.
if ( ! ( new Manager() )->is_user_connected( $user->ID ) ) {
$url_params['query'] = 'unlinked=1';
}
$envelope->url = esc_url( Redirect::get_url( "jitm-$envelope->id", $url_params ) );
$stats = new A8c_Mc_Stats();
$envelope->jitm_stats_url = $stats->build_stats_url( array( 'x_jetpack-jitm' => $envelope->id ) );
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
// $CTA is not valid per PHPCS, but it is part of the return from WordPress.com, so allowing.
if ( $envelope->CTA->hook ) {
$envelope->url = apply_filters( 'jitm_' . $envelope->CTA->hook, $envelope->url );
unset( $envelope->CTA->hook );
}
// phpcs:enable
if ( isset( $envelope->content->hook ) ) {
$envelope->content = apply_filters( 'jitm_' . $envelope->content->hook, $envelope->content );
unset( $envelope->content->hook );
}
// No point in showing an empty message.
if ( empty( $envelope->content->message ) ) {
unset( $envelopes[ $idx ] );
continue;
}
$envelope->content->icon = $this->generate_icon( $envelope->content->icon, $full_jp_logo_exists );
$envelope->message_path = esc_attr( $message_path );
$stats->add( 'jitm', $envelope->id . '-viewed' );
$stats->do_server_side_stats();
}
return $envelopes;
}
}
@@ -0,0 +1,177 @@
<?php
/**
* Jetpack's Pre-Connection JITM class.
*
* @package automattic/jetpack-jitm
*/
namespace Automattic\Jetpack\JITMS;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
/**
* Jetpack pre-connection just in time messaging through out the admin.
*/
class Pre_Connection_JITM extends JITM {
/**
* Filters and formats the messages for the client-side JS renderer
*
* @param string $message_path Current message path.
*
* @return array Formatted messages.
*/
private function filter_messages( $message_path ) {
/**
* Allows filtering of the pre-connection JITMs.
*
* This filter allows plugins to add pre-connection JITMs that will be
* displayed by the JITM package.
*
* @since 1.14.1
*
* @param array An array of pre-connection messages.
*/
$messages = apply_filters( 'jetpack_pre_connection_jitms', array() );
$messages = $this->validate_messages( $messages );
$formatted_messages = array();
foreach ( $messages as $message ) {
if ( ! preg_match( $message['message_path'], $message_path ) ) {
continue;
}
$obj = new \stdClass();
$obj->CTA = array( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
'message' => $message['button_caption'],
'newWindow' => false,
);
$obj->url = $message['button_link'];
$obj->id = $message['id'];
$obj->is_dismissible = true;
$obj->content = array(
'message' => $message['message'],
'description' => $message['description'],
'list' => array(),
'icon' => $this->get_message_icon( $message ),
);
$formatted_messages[] = $obj;
}
return $formatted_messages;
}
/**
* Validates that each of the messages contains all of the required keys:
* - id
* - message_path
* - message
* - description
* - button_link
* - button_caption
*
* @param array $messages An array of JITM messages.
*
* @return array An array of JITM messages that contain all of the required keys.
*/
private function validate_messages( $messages ) {
if ( ! is_array( $messages ) ) {
return array();
}
$expected_keys = array_flip( array( 'id', 'message_path', 'message', 'description', 'button_link', 'button_caption' ) );
foreach ( $messages as $index => $message ) {
if ( count( array_intersect_key( $expected_keys, $message ) ) !== count( $expected_keys ) ) {
// Remove any messages that are missing expected keys.
unset( $messages[ $index ] );
}
}
return $messages;
}
/**
* Get the icon for the message.
*
* The message may contain an 'icon' key. If the value of the 'icon' key matches a supported icon (or empty string), the value is used.
* If the message does not contain an icon key or if the value does not match a supported icon, the Jetpack icon is used by default.
*
* @param array $message A pre-connection JITM.
*
* @return string The icon to use in the JITM.
*/
private function get_message_icon( $message ) {
// Default to the Jetpack icon.
$icon = 'jetpack';
if ( ! isset( $message['icon'] ) ) {
return $icon;
}
$supported_icons = $this->get_supported_icons();
if ( in_array( $message['icon'], $supported_icons, true ) ) {
// Only use the message icon if it's a supported icon or an empty string (for no icon).
$icon = $message['icon'];
}
return $icon;
}
/**
* Retrieve the current message to display keyed on query string and message path
*
* @param string $message_path The message path to ask for.
* @param array $query Query parameters as an associative array. Unused in this subclass.
* @param bool $full_jp_logo_exists If there is a full Jetpack logo already on the page.
*
* @return array The JITMs to show, or an empty array if there is nothing to show
*/
public function get_messages( $message_path, $query, $full_jp_logo_exists ) {
// Ensure only admins see pre-connection JITMs since only they can connect to WordPress.com
// and enable modules.
if ( ! current_user_can( 'install_plugins' ) ) {
return array();
}
$messages = $this->filter_messages( $message_path );
if ( empty( $messages ) ) {
return array();
}
$hidden_jitms = \Jetpack_Options::get_option( 'hide_jitm' );
foreach ( $messages as $idx => &$envelope ) {
$dismissed_feature = isset( $hidden_jitms[ 'pre-connection-' . $envelope->id ] ) &&
is_array( $hidden_jitms[ 'pre-connection-' . $envelope->id ] ) ? $hidden_jitms[ 'pre-connection-' . $envelope->id ] : null;
if ( is_array( $dismissed_feature ) ) {
unset( $messages[ $idx ] );
continue;
}
$envelope->content['icon'] = $this->generate_icon( $envelope->content['icon'], $full_jp_logo_exists );
}
return $messages;
}
/**
* Dismisses a JITM ID so that it will no longer be shown.
*
* @param string $id The id of the JITM that was dismissed.
*
* @return bool Always true
*/
public function dismiss( $id ) {
$this->save_dismiss( 'pre-connection-' . $id );
return true;
}
}
@@ -0,0 +1,103 @@
<?php
/**
* JITM's REST API Endpoints
*
* @package automattic/jetpack-jitm
*/
namespace Automattic\Jetpack\JITMS;
use Automattic\Jetpack\Connection\REST_Connector;
use WP_Error;
use WP_REST_Request;
use WP_REST_Server;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
/**
* Register the JITM's REST API Endpoints and their callbacks.
*/
class Rest_Api_Endpoints {
/**
* Declare the JITM's REST API endpoints.
*/
public static function register_endpoints() {
register_rest_route(
'jetpack/v4',
'/jitm',
array(
'methods' => WP_REST_Server::READABLE,
'callback' => __CLASS__ . '::get_jitm_message',
'permission_callback' => '__return_true',
)
);
register_rest_route(
'jetpack/v4',
'/jitm',
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => __CLASS__ . '::delete_jitm_message',
'permission_callback' => __CLASS__ . '::delete_jitm_message_permission_callback',
)
);
}
/**
* Asks for a jitm, unless they've been disabled, in which case it returns an empty array
*
* @param WP_REST_Request $request The request object.
*
* @return array An array of jitms
*/
public static function get_jitm_message( $request ) {
$jitm = JITM::get_instance();
if ( ! $jitm->jitms_enabled() ) {
return array();
}
$query_string = $request['query'] ?? '';
$query_array = array();
if ( ! empty( $query_string ) ) {
parse_str( $query_string, $query_array );
$query_array = urldecode_deep( $query_array );
}
return $jitm->get_messages( $request['message_path'], $query_array, 'true' === $request['full_jp_logo_exists'] );
}
/**
* Dismisses a jitm.
*
* @param WP_REST_Request $request The request object.
*
* @return bool Always True
*/
public static function delete_jitm_message( $request ) {
$jitm = JITM::get_instance();
if ( ! $jitm->jitms_enabled() ) {
return true;
}
return $jitm->dismiss( $request['id'], $request['feature_class'] );
}
/**
* Verify that the user can dismiss JITM messages.
*
* @return bool|WP_Error True if user is able to dismiss JITM messages.
*/
public static function delete_jitm_message_permission_callback() {
if ( current_user_can( 'read' ) ) {
return true;
}
return new WP_Error( 'invalid_user_permission_jetpack_delete_jitm_message', REST_Connector::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB