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('jetpack-script-data', 'jetpack-shared-stores', 'react', 'react-jsx-runtime', 'wp-components', 'wp-compose', 'wp-data', 'wp-dom-ready', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-plugins', 'wp-polyfill', 'wp-primitives', 'wp-url'), 'version' => 'b907ec4aa61463228272');
@@ -0,0 +1 @@
.block-editor-block-list__layout .block-editor-block-list__block.has-warning.is-interactive>*{pointer-events:auto;user-select:auto}.block-editor-block-list__layout .block-editor-block-list__block.has-warning.is-interactive:after{content:none}.blaze-panel .blaze-panel-outbound-link__external_icon{fill:currentColor;height:1.4em;margin-left:.1em;vertical-align:middle;width:1.4em}.blaze-panel .components-panel__body-toggle svg{margin:0 0 0 .5em}.blaze-panel .post-publish-panel__postpublish-buttons .components-button{padding-bottom:.4em;padding-top:.5em}.blaze-panel p{margin-top:0}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
.block-editor-block-list__layout .block-editor-block-list__block.has-warning.is-interactive>*{pointer-events:auto;user-select:auto}.block-editor-block-list__layout .block-editor-block-list__block.has-warning.is-interactive:after{content:none}.blaze-panel .blaze-panel-outbound-link__external_icon{fill:currentColor;height:1.4em;margin-right:.1em;vertical-align:middle;width:1.4em}.blaze-panel .components-panel__body-toggle svg{margin:0 .5em 0 0}.blaze-panel .post-publish-panel__postpublish-buttons .components-button{padding-bottom:.4em;padding-top:.5em}.blaze-panel p{margin-top:0}
@@ -0,0 +1,558 @@
<?php
/**
* Attract high-quality traffic to your site.
*
* @package automattic/jetpack-blaze
*/
namespace Automattic\Jetpack;
use Automattic\Jetpack\Blaze\Dashboard as Blaze_Dashboard;
use Automattic\Jetpack\Blaze\Dashboard_REST_Controller as Blaze_Dashboard_REST_Controller;
use Automattic\Jetpack\Blaze\REST_Controller;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
use Automattic\Jetpack\Connection\Manager as Jetpack_Connection;
use Automattic\Jetpack\Status as Jetpack_Status;
use Automattic\Jetpack\Status\Host;
use Automattic\Jetpack\Sync\Settings as Sync_Settings;
use WP_Post;
/**
* Class for promoting posts.
*/
class Blaze {
/**
* Script handle for the JS file we enqueue in the post editor.
*
* @var string
*/
const SCRIPT_HANDLE = 'jetpack-promote-editor';
/**
* Transient prefix for active campaign status checks.
*
* @var string
*/
const ACTIVE_CAMPAIGNS_STATUS_TRANSIENT_PREFIX = 'jetpack_blaze_active_campaigns_status_';
/**
* Transient TTL for active campaign status checks that find campaigns.
*
* @var int
*/
const ACTIVE_CAMPAIGNS_STATUS_TRANSIENT_TTL = HOUR_IN_SECONDS;
/**
* Transient TTL for active campaign status checks that cannot determine campaign status.
*
* @var int
*/
const UNKNOWN_ACTIVE_CAMPAIGNS_STATUS_TRANSIENT_TTL = 5 * MINUTE_IN_SECONDS;
/**
* Minimum campaign statuses that should trigger a warning.
*
* @var string[]
*/
const ACTIVE_CAMPAIGN_STATUSES = array( 'active' );
/**
* Path of the JS file we enqueue in the post editor.
*
* @var string
*/
public static $script_path = '../build/editor.js';
/**
* Initializer.
* Used to configure the blaze package, eg when called via the Config package.
*
* @return void
*/
public static function init() {
// On the edit screen, add a row action to promote the post.
add_action( 'load-edit.php', array( __CLASS__, 'add_post_links_actions' ) );
// After the quick-edit screen is processed, ensure the blaze row action is still present
if ( 'edit.php' === $GLOBALS['pagenow'] ||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification is not needed here, we're not saving anything.
( 'admin-ajax.php' === $GLOBALS['pagenow'] && ! empty( $_POST['post_view'] ) && 'list' === $_POST['post_view'] && ! empty( $_POST['action'] ) && 'inline-save' === $_POST['action'] ) ) {
self::add_post_links_actions();
}
// In the post editor, add a post-publish panel to allow promoting the post.
add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'enqueue_block_editor_assets' ) );
// Add a Blaze Menu.
add_action( 'admin_menu', array( __CLASS__, 'enable_blaze_menu' ), 999 );
// Add Blaze dashboard app REST API endpoints.
add_action( 'rest_api_init', array( Blaze_Dashboard_REST_Controller::class, 'register' ) );
// Add general Blaze REST API endpoints.
add_action( 'rest_api_init', array( REST_Controller::class, 'register' ) );
}
/**
* Add links under each published post in the wp-admin post list.
*
* @return void
*/
public static function add_post_links_actions() {
if ( self::should_initialize()['can_init'] ) {
add_filter( 'post_row_actions', array( __CLASS__, 'jetpack_blaze_row_action' ), 10, 2 );
add_filter( 'page_row_actions', array( __CLASS__, 'jetpack_blaze_row_action' ), 10, 2 );
}
}
/**
* Is the wp-admin Dashboard enabled?
* That dashboard is not available or necessary on WordPress.com sites when the nav redesign is disabled.
*
* @return bool
*/
public static function is_dashboard_enabled() {
$is_dashboard_enabled = true;
// On WordPress.com sites, the dashboard is not needed if the nav redesign is not enabled.
if ( get_option( 'wpcom_admin_interface' ) !== 'wp-admin' && ( new Host() )->is_wpcom_platform() ) {
$is_dashboard_enabled = false;
}
/**
* Enable a wp-admin dashboard for Blaze campaign management.
*
* @since 0.7.0
*
* @param bool $should_enable Should the dashboard be enabled?
*/
return apply_filters( 'jetpack_blaze_dashboard_enable', $is_dashboard_enabled );
}
/**
* Enable the Blaze menu.
*
* @return void
*/
public static function enable_blaze_menu() {
if ( ! self::should_initialize()['can_init'] ) {
return;
}
$blaze_dashboard = new Blaze_Dashboard();
if ( self::is_dashboard_enabled() ) {
$page_suffix = add_submenu_page(
'tools.php',
esc_attr__( 'Advertising', 'jetpack-blaze' ),
__( 'Advertising', 'jetpack-blaze' ),
'manage_options',
'advertising',
array( $blaze_dashboard, 'render' ),
1
);
add_action( 'load-' . $page_suffix, array( $blaze_dashboard, 'admin_init' ) );
} elseif ( ( new Host() )->is_wpcom_platform() ) {
$domain = ( new Jetpack_Status() )->get_site_suffix();
$page_suffix = add_submenu_page(
'tools.php',
esc_attr__( 'Advertising', 'jetpack-blaze' ),
__( 'Advertising', 'jetpack-blaze' ),
'manage_options',
'https://wordpress.com/advertising/' . $domain,
null, // @phan-suppress-current-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. https://core.trac.wordpress.org/ticket/52539
1
);
add_action( 'load-' . $page_suffix, array( $blaze_dashboard, 'admin_init' ) );
}
}
/**
* Check the WordPress.com REST API
* to ensure that the site supports the Blaze feature.
*
* - If the site is on WordPress.com Simple, we do not query the API.
* - Results are cached for a day after getting response from API.
* - If the API returns an error, we cache the result for an hour.
*
* @param int $blog_id The blog ID to check.
*
* @return bool
*/
public static function site_supports_blaze( $blog_id ) {
$transient_name = 'jetpack_blaze_site_supports_blaze_' . $blog_id;
/*
* On WordPress.com, we don't need to make an API request,
* we can query directly.
*/
if ( defined( 'IS_WPCOM' ) && IS_WPCOM && function_exists( 'blaze_is_site_eligible' ) ) {
return blaze_is_site_eligible( $blog_id );
}
$cached_result = get_transient( $transient_name );
if ( false !== $cached_result ) {
if ( is_array( $cached_result ) ) {
return $cached_result['approved'];
}
return (bool) $cached_result;
}
// Make the API request.
$url = sprintf( '/sites/%d/blaze/status', $blog_id );
$response = Client::wpcom_json_api_request_as_blog(
$url,
'2',
array( 'method' => 'GET' ),
null,
'wpcom'
);
// If there was an error or malformed response, bail and save response for an hour.
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
set_transient( $transient_name, array( 'approved' => false ), HOUR_IN_SECONDS );
return false;
}
// Decode the results.
$result = json_decode( wp_remote_retrieve_body( $response ), true );
// Bail if there were no results returned.
if ( ! is_array( $result ) || ! isset( $result['approved'] ) ) {
return false;
}
// Cache the result for 24 hours.
set_transient( $transient_name, array( 'approved' => (bool) $result['approved'] ), DAY_IN_SECONDS );
return (bool) $result['approved'];
}
/**
* Get the active campaign status for a site.
*
* @param int $blog_id The blog ID to check.
*
* @return array Active campaign status.
*/
public static function get_active_campaigns_status( $blog_id ) {
$blog_id = absint( $blog_id );
if ( empty( $blog_id ) ) {
return self::get_unknown_active_campaigns_status();
}
$transient_name = self::ACTIVE_CAMPAIGNS_STATUS_TRANSIENT_PREFIX . $blog_id;
$cached_result = get_transient( $transient_name );
if ( false !== $cached_result ) {
return $cached_result;
}
/**
* Filter the campaign statuses that should trigger the active campaign warning.
*
* @since 0.27.23
*
* @param string[] $statuses Campaign statuses to check.
* @param int $blog_id The blog ID being checked.
*/
$statuses = apply_filters( 'jetpack_blaze_active_campaign_statuses', self::ACTIVE_CAMPAIGN_STATUSES, $blog_id );
$statuses = array_filter( array_map( 'sanitize_key', (array) $statuses ) );
if ( empty( $statuses ) ) {
$statuses = self::ACTIVE_CAMPAIGN_STATUSES;
}
$url = add_query_arg(
array(
'status' => implode( ',', $statuses ),
'limit' => 1,
),
sprintf( '/sites/%d/wordads/dsp/api/v1/search/campaigns/site/%d', $blog_id, $blog_id )
);
$response = Client::wpcom_json_api_request_as_user(
$url,
'2',
array( 'method' => 'GET' ),
null,
'wpcom'
);
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
return self::get_unknown_active_campaigns_status( $transient_name );
}
$result = json_decode( wp_remote_retrieve_body( $response ), true );
if ( ! is_array( $result ) || ! isset( $result['campaigns'] ) || ! is_array( $result['campaigns'] ) ) {
return self::get_unknown_active_campaigns_status( $transient_name );
}
$has_active_campaigns = ! empty( $result['campaigns'] );
$status = array(
'has_active_campaigns' => $has_active_campaigns,
'status' => $has_active_campaigns ? 'active' : 'none',
);
if ( $has_active_campaigns ) {
set_transient( $transient_name, $status, self::ACTIVE_CAMPAIGNS_STATUS_TRANSIENT_TTL );
}
return $status;
}
/**
* Get the unknown active campaign status response.
*
* @param string|null $transient_name Optional transient name used to cache the unknown status.
* @return array Unknown active campaign status.
*/
private static function get_unknown_active_campaigns_status( $transient_name = null ) {
$status = array(
'has_active_campaigns' => false,
'status' => 'unknown',
);
if ( $transient_name ) {
set_transient( $transient_name, $status, self::UNKNOWN_ACTIVE_CAMPAIGNS_STATUS_TRANSIENT_TTL );
}
return $status;
}
/**
* Determines if criteria is met to enable Blaze features.
* Keep in mind that this makes remote requests, so we want to avoid calling it when unnecessary, like in the frontend.
*
* @return array
*/
public static function should_initialize() {
$is_wpcom = defined( 'IS_WPCOM' ) && IS_WPCOM;
$connection = new Jetpack_Connection();
$site_id = Jetpack_Connection::get_site_id();
// Only admins should be able to Blaze posts on a site.
if ( ! current_user_can( 'manage_options' ) ) {
return array(
'can_init' => false,
'reason' => 'user_not_admin',
);
}
// Allow short-circuiting the Blaze initialization via a filter.
if ( has_filter( 'jetpack_blaze_enabled' ) ) {
/**
* Filter to disable all Blaze functionality.
*
* @since 0.3.0
*
* @param bool $should_initialize Whether Blaze should be enabled. Default to true.
*/
$should_init = apply_filters( 'jetpack_blaze_enabled', true );
return array(
'can_init' => $should_init,
'reason' => $should_init ? null : 'initialization_disabled',
);
}
// On self-hosted sites, we must do some additional checks.
if ( ! $is_wpcom ) {
/*
* These features currently only work on WordPress.com,
* so the site must be connected to WordPress.com, and the user as well for things to work.
*/
if (
is_wp_error( $site_id )
) {
return array(
'can_init' => false,
'reason' => 'wp_error',
);
}
if ( ! $connection->is_connected() ) {
return array(
'can_init' => false,
'reason' => 'site_not_connected',
);
}
if ( ! $connection->is_user_connected() ) {
return array(
'can_init' => false,
'reason' => 'user_not_connected',
);
}
// The whole thing is powered by Sync!
if ( ! Sync_Settings::is_sync_enabled() ) {
return array(
'can_init' => false,
'reason' => 'sync_disabled',
);
}
}
// Check if the site supports Blaze.
if ( is_numeric( $site_id ) && ! self::site_supports_blaze( $site_id ) ) {
return array(
'can_init' => false,
'reason' => 'site_not_eligible',
);
}
// Final fallback.
return array(
'can_init' => true,
'reason' => null,
);
}
/**
* Get URL to create a Blaze campaign for a specific post.
*
* This can return 2 different types of URL:
* - Calypso Links
* - wp-admin Links if access to the wp-admin Blaze Dashboard is enabled.
*
* @param int|string $post_id Post ID.
*
* @return array An array with the link, and whether this is a Calypso or a wp-admin link.
*/
public static function get_campaign_management_url( $post_id ) {
if ( self::is_dashboard_enabled() ) {
$admin_url = admin_url( 'tools.php?page=advertising' );
$hostname = wp_parse_url( get_site_url(), PHP_URL_HOST );
$blaze_url = sprintf(
'%1$s#!/advertising/posts/promote/post-%2$s/%3$s',
$admin_url,
esc_attr( $post_id ),
$hostname
);
return array(
'link' => $blaze_url,
'external' => false,
);
}
// Default Calypso link.
$blaze_url = Redirect::get_url(
'jetpack-blaze',
array(
'query' => 'blazepress-widget=post-' . esc_attr( $post_id ),
)
);
return array(
'link' => $blaze_url,
'external' => true,
);
}
/**
* Adds the Promote link to the posts list row action.
*
* @param array $post_actions The current array of post actions.
* @param WP_Post $post The current post in the post list table.
*
* @return array
*/
public static function jetpack_blaze_row_action( $post_actions, $post ) {
/**
* Allow third-party plugins to disable Blaze row actions.
*
* @since 0.16.0
*
* @param bool $are_quick_links_enabled Should Blaze row actions be enabled.
* @param WP_Post $post The current post in the post list table.
*/
$are_quick_links_enabled = apply_filters( 'jetpack_blaze_post_row_actions_enable', true, $post );
// Bail if we are not looking at one of the supported post types (post, page, or product).
if (
! $are_quick_links_enabled
|| ! in_array( $post->post_type, array( 'post', 'page', 'product' ), true )
) {
return $post_actions;
}
// Bail if the post is not published.
if ( $post->post_status !== 'publish' ) {
return $post_actions;
}
// Bail if the post has a password.
if ( '' !== $post->post_password ) {
return $post_actions;
}
$blaze_url = self::get_campaign_management_url( $post->ID );
$text = __( 'Promote with Blaze', 'jetpack-blaze' );
$title = get_the_title( $post );
$label = sprintf(
/* translators: post title */
__( 'Blaze &#8220;%s&#8221; to Tumblr and WordPress.com audiences.', 'jetpack-blaze' ),
$title
);
$post_actions['blaze'] = sprintf(
'<a href="%1$s" title="%2$s" aria-label="%2$s" %4$s>%3$s</a>',
esc_url( $blaze_url['link'] ),
esc_attr( $label ),
esc_html( $text ),
( true === $blaze_url['external'] ? 'target="_blank" rel="noopener noreferrer"' : '' )
);
return $post_actions;
}
/**
* Enqueue block editor assets.
*/
public static function enqueue_block_editor_assets() {
/*
* We do not want (nor need) Blaze in the site editor, or the widget editor, or the classic editor.
* We only want it in the post editor.
* Enqueueing the script in those editors would cause a fatal error.
* See #20357 for more info.
*/
if ( ! function_exists( 'get_current_screen' ) ) { // When Gutenberg is loaded in the frontend.
return;
}
$current_screen = get_current_screen();
if (
empty( $current_screen )
|| $current_screen->base !== 'post'
|| ! $current_screen->is_block_editor()
) {
return;
}
// Bail if criteria is not met to enable Blaze features.
if ( ! self::should_initialize()['can_init'] ) {
return;
}
Assets::register_script(
self::SCRIPT_HANDLE,
self::$script_path,
__FILE__,
array(
'enqueue' => true,
'in_footer' => true,
'textdomain' => 'jetpack-blaze',
)
);
// Adds Connection package initial state.
Connection_Initial_State::render_script( self::SCRIPT_HANDLE );
// Pass additional data to our script.
wp_localize_script(
self::SCRIPT_HANDLE,
'blazeInitialState',
array(
'blazeUrlTemplate' => self::get_campaign_management_url( '__POST_ID__' ),
)
);
}
}
@@ -0,0 +1,215 @@
<?php
/**
* Blaze dashboard Initial State
*
* @package automattic/jetpack-blaze
*/
namespace Automattic\Jetpack\Blaze;
use Automattic\Jetpack\Connection\Manager;
use Automattic\Jetpack\Constants;
use Automattic\Jetpack\Current_Plan;
use Automattic\Jetpack\Status\Host;
use Jetpack_Options;
/**
* Class Dashboard_Config_Data
*/
class Dashboard_Config_Data {
/**
* Blaze dashboard admin page. Default is tools.php.
*
* @var string
*/
private $admin_page;
/**
* Blaze dashboard menu slug. Default is 'advertising'.
*
* @var string
*/
private $menu_slug;
/**
* Dashboard config constructor.
*
* @param string $admin_page Dashboard admin page. Default is tools.php.
* @param string $menu_slug Dashboard menu slug. Default is 'advertising'.
*/
public function __construct( $admin_page = 'tools.php', $menu_slug = 'advertising' ) {
$this->admin_page = $admin_page;
$this->menu_slug = $menu_slug;
}
/**
* Set configData to window.configData.
*
* @param array $config_data The config data.
*/
public function get_js_config_data( $config_data = null ) {
return 'window.configData = ' . wp_json_encode(
$config_data === null ? $this->get_data() : $config_data,
JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP
) . ';';
}
/**
* Return the config for the app.
*/
public function get_data() {
$blog_id = Jetpack_Options::get_option( 'id' );
$host = new Host();
$empty_object = json_decode( '{}' );
$user = $this->get_connected_user_identity();
$data = array(
'admin_page_base' => $this->get_admin_path(),
'api_root' => esc_url_raw( rest_url() ),
'blog_id' => $blog_id,
'enable_all_sections' => false,
'env_id' => 'production',
'google_analytics_key' => 'UA-10673494-15',
'hostname' => wp_parse_url( get_site_url(), PHP_URL_HOST ),
'i18n_default_locale_slug' => 'en',
'mc_analytics_enabled' => false,
'meta' => array(),
'nonce' => wp_create_nonce( 'wp_rest' ),
'site_name' => \get_bloginfo( 'name' ),
'sections' => array(),
// Features are inlined in Calypso Blaze app (wp-calypso/apps/blaze-dashboard)
'features' => array(
'is_running_in_jetpack_site' => ! $host->is_wpcom_simple(),
),
'initial_state' => array(
'currentUser' => array(
'id' => $user['ID'],
'user' => $user,
'capabilities' => array(
"$blog_id" => $this->get_current_user_capabilities(),
),
),
'sites' => array(
'items' => array(
"$blog_id" => array(
'ID' => $blog_id,
'URL' => site_url(),
'jetpack' => ! $host->is_wpcom_simple(),
'visible' => true,
'capabilities' => $empty_object,
'products' => array(),
'plan' => $empty_object, // we need this empty object, otherwise the front end would crash on insight page.
'options' => array(
'admin_url' => admin_url(),
'gmt_offset' => $this->get_gmt_offset(),
'is_wpcom_atomic' => $host->is_woa_site(),
'is_wpcom_simple' => $host->is_wpcom_simple(),
'jetpack_version' => Constants::get_constant( 'JETPACK__VERSION' ),
),
),
),
'features' => array( "$blog_id" => array( 'data' => $this->get_plan_features() ) ),
),
),
);
/**
* Filter to allow modification of the Blaze dashboard config data.
*
* @param bool $data Blaze dashboard config data.
*
* @since 0.21.0
*/
return apply_filters( 'jetpack_blaze_dashboard_config_data', $data );
}
/**
* Gets the WordPress.com user's identity, if connected.
*
* @return array|bool
*/
protected function get_connected_user_identity() {
$user_data = ( new Manager() )->get_connected_user_data();
if ( ! $user_data ) {
return array(
'ID' => 1000,
'username' => 'no-user',
'localeSlug' => $this->get_locale(),
'site_count' => 1,
);
}
return array(
'ID' => $user_data['ID'],
'username' => $user_data['login'],
'email' => $user_data['email'],
'localeSlug' => $this->get_locale(),
'site_count' => 1,
);
}
/**
* Get the current site GMT Offset.
*
* @return float The current site GMT Offset by hours.
*/
protected function get_gmt_offset() {
return (float) get_option( 'gmt_offset' );
}
/**
* Page base for the Calypso admin page.
*/
protected function get_admin_path() {
if ( ! isset( $_SERVER['PHP_SELF'] ) || ! isset( $_SERVER['QUERY_STRING'] ) ) {
$admin_path = $this->admin_page . '?page=' . $this->menu_slug;
$parsed = wp_parse_url( admin_url( $admin_path ) );
return $parsed['path'] . '?' . $parsed['query'];
}
// We do this because page.js requires the exactly page base to be set otherwise it will not work properly.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
return wp_unslash( $_SERVER['PHP_SELF'] ) . '?' . wp_unslash( $_SERVER['QUERY_STRING'] );
}
/**
* Get the user's locale acceptable by Calypso.
*/
protected function get_locale() {
/**
* In WP, locales are formatted as LANGUAGE_REGION, for example `en`, `en_US`, `es_AR`,
* but Calypso expects language-region, e.g. `en-us`, `en`, `es-ar`. So we need to convert
* them to lower case and replace the underscore with a dash.
*/
$locale = strtolower( get_user_locale() );
$locale = str_replace( '_', '-', $locale );
return $locale;
}
/**
* Get the features of the current plan.
*/
protected function get_plan_features() {
$plan = Current_Plan::get();
if ( empty( $plan['features'] ) ) {
return array();
}
return $plan['features'];
}
/**
* Get the capabilities of the current user.
*
* @return array An array of capabilities.
*/
protected function get_current_user_capabilities() {
$user = wp_get_current_user();
if ( ! $user || is_wp_error( $user ) ) {
return array();
}
return $user->allcaps;
}
}
@@ -0,0 +1,228 @@
<?php
/**
* A class that adds a Blaze dashboard to wp-admin.
*
* @since 0.7.0
*
* @package automattic/jetpack-blaze
*/
namespace Automattic\Jetpack\Blaze;
use Automattic\Jetpack\Assets;
/**
* Responsible for adding a Blaze dashboard menu to wp-admin.
* The screen content itself is rendered remotely.
*/
class Dashboard {
/**
* Package version.
*
* @var string
*/
const PACKAGE_VERSION = '0.27.25';
/**
* List of dependencies needed to render the dashboard in wp-admin.
*
* @var array
*/
const JS_DEPENDENCIES = array( 'lodash', 'react', 'react-dom', 'wp-components', 'wp-compose', 'wp-i18n', 'wp-is-shallow-equal', 'wp-primitives', 'wp-url', 'moment' );
/**
* URL where we load the Blaze dashboard remotely.
*
* @var string
*/
const CDN_URL = 'https://widgets.wp.com/blaze-dashboard/%s/%s';
/**
* Script handle for the JS file we enqueue in the dashboard page.
*
* @var string
*/
const SCRIPT_HANDLE = 'jetpack-blaze-dashboard';
/**
* We bump the asset version when the Jetpack back end is not compatible anymore.
*
* @var string
*/
const BLAZEDASH_VERSION = 'v1';
/**
* Cache key for the cache buster.
*
* @var string
*/
const BLAZEDASH_CACHE_BUSTER_CACHE_KEY = 'jetpack_blaze_admin_asset_cache_buster';
/**
* Blaze dashboard admin page. Default is tools.php.
*
* @var string
*/
private $admin_page;
/**
* Blaze dashboard menu slug. Default is 'advertising'.
*
* @var string
*/
private $menu_slug;
/**
* Blaze dashboard css prefix. Default is 'jp-blaze'.
*
* @var string
*/
private $css_prefix;
/**
* Dashboard constructor.
*
* @param string $admin_page Dashboard admin page. Default is tools.php.
* @param string $menu_slug Dashboard menu slug. Default is 'advertising'.
* @param string $css_prefix Dashboard css prefix. Default is 'jp-blaze'.
*/
public function __construct( $admin_page = 'tools.php', $menu_slug = 'advertising', $css_prefix = 'jp-blaze' ) {
$this->admin_page = $admin_page;
$this->menu_slug = $menu_slug;
$this->css_prefix = $css_prefix;
}
/**
* Override render funtion
*
* @return void
*/
public function render() {
?>
<div id="wpcom" class="<?php echo esc_attr( $this->css_prefix ); ?>-dashboard" style="min-height: calc(100vh - 100px);">
<div class="hide-if-js"><?php esc_html_e( 'Your Jetpack Blaze dashboard requires JavaScript to function properly.', 'jetpack-blaze' ); ?></div>
<div class="hide-if-no-js" style="height: 100%">
<img
class="<?php echo esc_attr( $this->css_prefix ); ?>-dashboard-loading-spinner"
width="32"
height="32"
style="position: absolute; left: 50%; top: 50%;"
alt=<?php echo esc_attr( __( 'Loading', 'jetpack-blaze' ) ); ?>
src="//en.wordpress.com/i/loading/loading-64.gif"
/>
</div>
</div>
<script>
jQuery(document).ready(function($) {
// Load SVG sprite.
$.get("https://widgets.wp.com/blaze-dashboard/common/gridicons-506499ddac13811fee8e.svg", function(data) {
var div = document.createElement("div");
div.innerHTML = new XMLSerializer().serializeToString(data.documentElement);
div.style = 'display: none';
document.body.insertBefore(div, document.body.childNodes[0]);
});
// we intercept on all anchor tags and change it to hashbang style.
$("#wpcom").on('click', 'a', function (e) {
const link = e && e.currentTarget && e.currentTarget.attributes && e.currentTarget.attributes.href && e.currentTarget.attributes.href.value;
if (link && link.startsWith( '/<?php echo esc_attr( $this->menu_slug ); ?>' ) ) {
location.hash = `#!${link}`;
return false;
}
});
});
</script>
<?php
}
/**
* Initialize the admin resources.
*/
public function admin_init() {
add_action( 'admin_enqueue_scripts', array( $this, 'load_admin_scripts' ) );
}
/**
* Load the admin scripts.
*
* @param string $hook The current admin page.
*/
public function load_admin_scripts( $hook ) {
if ( ! str_ends_with( $hook, 'page_' . $this->menu_slug ) ) {
return;
}
$asset_handle = self::SCRIPT_HANDLE;
$asset_name = 'build.min';
$dashboard_config = new Dashboard_Config_Data( $this->admin_page, $this->menu_slug );
$config_data = $dashboard_config->get_data();
if ( file_exists( __DIR__ . "/../dist/{$asset_name}.js" ) ) {
// Load local assets for the convenience of development.
Assets::register_script(
$asset_handle,
"../dist/{$asset_name}.js",
__FILE__,
array(
'enqueue' => true,
'in_footer' => true,
'textdomain' => 'jetpack-blaze',
)
);
} else {
$css_url = $asset_name . ( is_rtl() ? '.rtl' : '' ) . '.css';
$css_handle = $asset_handle . '-style';
wp_enqueue_script(
$asset_handle,
sprintf( self::CDN_URL, self::BLAZEDASH_VERSION, "{$asset_name}.js" ),
self::JS_DEPENDENCIES,
$this->get_cdn_asset_cache_buster(),
true
);
wp_enqueue_style(
$css_handle,
sprintf( self::CDN_URL, self::BLAZEDASH_VERSION, $css_url ),
array(),
$this->get_cdn_asset_cache_buster()
);
}
wp_add_inline_script(
$asset_handle,
$dashboard_config->get_js_config_data( $config_data ),
'before'
);
}
/**
* Returns cache buster string for assets.
* Development mode doesn't need this, as it's handled by `Assets` class.
*/
protected function get_cdn_asset_cache_buster() {
// Use cached cache buster in production.
$remote_asset_version = get_transient( self::BLAZEDASH_CACHE_BUSTER_CACHE_KEY );
if ( ! empty( $remote_asset_version ) ) {
return $remote_asset_version;
}
// If no cached cache buster, we fetch it from CDN and set to transient.
$response = wp_remote_get( sprintf( self::CDN_URL, self::BLAZEDASH_VERSION, 'build_meta.json' ), array( 'timeout' => 5 ) );
if ( is_wp_error( $response ) ) {
// fallback to the package version.
return self::PACKAGE_VERSION;
}
$build_meta = json_decode( wp_remote_retrieve_body( $response ), true );
if ( ! empty( $build_meta['cache_buster'] ) ) {
// Cache the cache buster for 15 mins.
set_transient( self::BLAZEDASH_CACHE_BUSTER_CACHE_KEY, $build_meta['cache_buster'], 15 * MINUTE_IN_SECONDS );
return $build_meta['cache_buster'];
}
// fallback to the package version.
return self::PACKAGE_VERSION;
}
}
@@ -0,0 +1,185 @@
<?php
/**
* The Blaze Rest Controller class.
* Registers the REST routes for Blaze Dashboard.
*
* @package automattic/jetpack-blaze
*/
namespace Automattic\Jetpack\Blaze;
use Automattic\Jetpack\Blaze;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Status\Host;
use WP_Error;
use WP_REST_Server;
/**
* Registers general REST routes for Blaze.
*/
class REST_Controller {
/**
* Namespace for the REST API.
*
* @var string
*/
public static $namespace = 'jetpack/v4/blaze';
/**
* Connection manager object.
*
* @var \Automattic\Jetpack\Connection\Manager
*/
private $connection;
/**
* Creates the REST_Controller object.
*
* @param \Automattic\Jetpack\Connection\Manager $connection The connection manager object.
*/
public function __construct( $connection = null ) {
$this->connection = $connection ?? new Connection_Manager();
}
/**
* Registers the REST routes on the `rest_api_init` hook.
*
* Instantiated here, rather than eagerly, so the controller class only loads
* on requests that reach `rest_api_init`. Static so the callback can be
* unregistered.
*
* @access public
*/
public static function register() {
( new self() )->register_rest_routes();
}
/**
* Registers the REST routes.
*
* @access public
*/
public function register_rest_routes() {
$site_id = $this->get_site_id();
if ( ! is_wp_error( $site_id ) ) {
register_rest_route(
static::$namespace,
'eligibility',
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'blaze_eligibility' ),
'permission_callback' => array( $this, 'can_user_view_blaze_settings' ),
)
);
register_rest_route(
static::$namespace,
'dashboard',
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'is_dashboard_enabled' ),
'permission_callback' => array( $this, 'can_user_view_blaze_settings' ),
)
);
}
register_rest_route(
static::$namespace,
'active-campaigns',
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_active_campaigns' ),
'permission_callback' => array( $this, 'can_user_view_blaze_settings' ),
)
);
}
/**
* Only administrators can access the API.
*
* @return bool|WP_Error True if a blog token was used to sign the request, WP_Error otherwise.
*/
public function can_user_view_blaze_settings() {
if (
$this->is_user_connected()
&& current_user_can( 'manage_options' )
) {
return true;
}
return $this->get_forbidden_error();
}
/**
* Get the eligibility for Blaze.
*
* @return bool
*/
public function blaze_eligibility() {
$site_id = $this->get_site_id();
if ( is_wp_error( $site_id ) ) {
return false;
}
return (bool) Blaze::site_supports_blaze( $site_id );
}
/**
* Check if the dashboard is enabled.
*
* @return bool
*/
public function is_dashboard_enabled() {
return (bool) Blaze::is_dashboard_enabled();
}
/**
* Get active Blaze campaign status for the site.
*
* @return array
*/
public function get_active_campaigns() {
$site_id = $this->get_site_id();
if ( is_wp_error( $site_id ) ) {
return Blaze::get_active_campaigns_status( 0 );
}
return Blaze::get_active_campaigns_status( $site_id );
}
/**
* Check if the current user is connected.
* On WordPress.com Simple, it is always connected.
*
* @return true
*/
private function is_user_connected() {
if ( ( new Host() )->is_wpcom_simple() ) {
return true;
}
return $this->connection->is_connected() && $this->connection->is_user_connected();
}
/**
* Return a WP_Error object with a forbidden error.
*/
protected function get_forbidden_error() {
$error_msg = esc_html__(
'You are not allowed to perform this action.',
'jetpack-blaze'
);
return new WP_Error( 'rest_forbidden', $error_msg, array( 'status' => rest_authorization_required_code() ) );
}
/**
* Get the site ID.
*
* @return int|WP_Error
*/
private function get_site_id() {
return Connection_Manager::get_site_id();
}
}