summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnthony G. Basile <blueness@gentoo.org>2019-01-01 22:18:11 -0500
committerAnthony G. Basile <blueness@gentoo.org>2019-01-01 22:18:11 -0500
commit018bd442ec1e04ba78a6628763414eb60b359398 (patch)
tree448cde462397af33e5a964ba5d0803b73c65040e /plugins/jetpack/_inc/lib
parentUpdate easy-table 1.8 (diff)
downloadblogs-gentoo-018bd442ec1e04ba78a6628763414eb60b359398.tar.gz
blogs-gentoo-018bd442ec1e04ba78a6628763414eb60b359398.tar.bz2
blogs-gentoo-018bd442ec1e04ba78a6628763414eb60b359398.zip
Update jetpack 6.8.1
Signed-off-by: Anthony G. Basile <blueness@gentoo.org>
Diffstat (limited to 'plugins/jetpack/_inc/lib')
-rw-r--r--plugins/jetpack/_inc/lib/admin-pages/class.jetpack-admin-page.php144
-rw-r--r--plugins/jetpack/_inc/lib/admin-pages/class.jetpack-react-page.php79
-rw-r--r--plugins/jetpack/_inc/lib/admin-pages/class.jetpack-settings-page.php39
-rw-r--r--plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php431
-rw-r--r--plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php204
-rw-r--r--plugins/jetpack/_inc/lib/class.media-summary.php10
-rw-r--r--plugins/jetpack/_inc/lib/class.media.php1
-rw-r--r--plugins/jetpack/_inc/lib/core-api/class-wpcom-rest-field-controller.php330
-rw-r--r--plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php10
-rw-r--r--plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php75
-rw-r--r--plugins/jetpack/_inc/lib/core-api/load-wpcom-endpoints.php40
-rw-r--r--plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/hello.php22
-rw-r--r--plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php117
-rw-r--r--plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connections.php186
-rw-r--r--plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-services.php159
-rw-r--r--plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/sites-posts-featured-media-url.php37
-rw-r--r--plugins/jetpack/_inc/lib/core-api/wpcom-fields/post-fields-publicize-connections.php337
-rw-r--r--plugins/jetpack/_inc/lib/tonesque.php2
18 files changed, 2140 insertions, 83 deletions
diff --git a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-admin-page.php b/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-admin-page.php
index ba2fefe8..d352aa56 100644
--- a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-admin-page.php
+++ b/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-admin-page.php
@@ -92,7 +92,17 @@ abstract class Jetpack_Admin_Page {
return;
}
- $this->page_render();
+ // Check if we are looking at the main dashboard
+ if (
+ isset( $_GET['page'] ) &&
+ 'jetpack' === $_GET['page'] &&
+ empty( $_GET['configure'] )
+ )
+ {
+ $this->page_render();
+ return;
+ }
+ Jetpack_Admin_Page::wrap_ui( array( $this, 'page_render' ) );
}
function admin_help() {
@@ -104,14 +114,6 @@ abstract class Jetpack_Admin_Page {
$this->jetpack->admin_page_load();
}
- function admin_page_top() {
- include_once( JETPACK__PLUGIN_DIR . '_inc/header.php' );
- }
-
- function admin_page_bottom() {
- include_once( JETPACK__PLUGIN_DIR . '_inc/footer.php' );
- }
-
// Add page specific scripts and jetpack stats for all menu pages
function admin_scripts() {
$this->page_admin_scripts(); // Delegate to inheriting class
@@ -219,4 +221,128 @@ abstract class Jetpack_Admin_Page {
'deactivate' => $to_deactivate
);
}
+
+ static function load_wrapper_styles( ) {
+ $rtl = is_rtl() ? '.rtl' : '';
+ wp_enqueue_style( 'dops-css', plugins_url( "_inc/build/admin.dops-style{$rtl}.css", JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION );
+ wp_enqueue_style( 'components-css', plugins_url( "_inc/build/style.min{$rtl}.css", JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION );
+ $custom_css = '
+ #wpcontent {
+ padding-left: 0 !important;
+ }
+ #wpbody-content {
+ background-color: #f3f6f8;
+ }
+
+ #jp-plugin-container .wrap {
+ margin: 0 auto;
+ max-width:45rem;
+ padding: 0 1.5rem;
+ }
+ #jp-plugin-container.is-wide .wrap {
+ max-width: 1040px;
+ }
+ .wp-admin #dolly {
+ float: none;
+ position: relative;
+ right: 0;
+ left: 0;
+ top: 0;
+ padding: .625rem;
+ text-align: right;
+ background: #fff;
+ font-size: .75rem;
+ font-style: italic;
+ color: #87a6bc;
+ border-bottom: 1px #e9eff3 solid;
+ }
+ ';
+ wp_add_inline_style( 'dops-css', $custom_css );
+ }
+
+ static function wrap_ui( $callback, $args = array() ) {
+ $defaults = array(
+ 'is-wide' => false,
+ );
+ $args = wp_parse_args( $args, $defaults );
+ $jetpack_admin_url = admin_url( 'admin.php?page=jetpack' );
+
+ ?>
+ <div id="jp-plugin-container" class="<?php if ( $args['is-wide'] ) { echo "is-wide"; } ?>">
+
+ <div class="jp-masthead">
+ <div class="jp-masthead__inside-container">
+ <div class="jp-masthead__logo-container">
+ <a class="jp-masthead__logo-link" href="<?php echo esc_url( $jetpack_admin_url ); ?>">
+ <svg class="jetpack-logo__masthead" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" height="32" viewBox="0 0 118 32"><path fill="#00BE28" d="M16,0C7.2,0,0,7.2,0,16s7.2,16,16,16s16-7.2,16-16S24.8,0,16,0z M15,19H7l8-16V19z M17,29V13h8L17,29z"></path><path d="M41.3,26.6c-0.5-0.7-0.9-1.4-1.3-2.1c2.3-1.4,3-2.5,3-4.6V8h-3V6h6v13.4C46,22.8,45,24.8,41.3,26.6z"></path><path d="M65,18.4c0,1.1,0.8,1.3,1.4,1.3c0.5,0,2-0.2,2.6-0.4v2.1c-0.9,0.3-2.5,0.5-3.7,0.5c-1.5,0-3.2-0.5-3.2-3.1V12H60v-2h2.1V7.1 H65V10h4v2h-4V18.4z"></path><path d="M71,10h3v1.3c1.1-0.8,1.9-1.3,3.3-1.3c2.5,0,4.5,1.8,4.5,5.6s-2.2,6.3-5.8,6.3c-0.9,0-1.3-0.1-2-0.3V28h-3V10z M76.5,12.3 c-0.8,0-1.6,0.4-2.5,1.2v5.9c0.6,0.1,0.9,0.2,1.8,0.2c2,0,3.2-1.3,3.2-3.9C79,13.4,78.1,12.3,76.5,12.3z"></path><path d="M93,22h-3v-1.5c-0.9,0.7-1.9,1.5-3.5,1.5c-1.5,0-3.1-1.1-3.1-3.2c0-2.9,2.5-3.4,4.2-3.7l2.4-0.3v-0.3c0-1.5-0.5-2.3-2-2.3 c-0.7,0-2.3,0.5-3.7,1.1L84,11c1.2-0.4,3-1,4.4-1c2.7,0,4.6,1.4,4.6,4.7L93,22z M90,16.4l-2.2,0.4c-0.7,0.1-1.4,0.5-1.4,1.6 c0,0.9,0.5,1.4,1.3,1.4s1.5-0.5,2.3-1V16.4z"></path><path d="M104.5,21.3c-1.1,0.4-2.2,0.6-3.5,0.6c-4.2,0-5.9-2.4-5.9-5.9c0-3.7,2.3-6,6.1-6c1.4,0,2.3,0.2,3.2,0.5V13 c-0.8-0.3-2-0.6-3.2-0.6c-1.7,0-3.2,0.9-3.2,3.6c0,2.9,1.5,3.8,3.3,3.8c0.9,0,1.9-0.2,3.2-0.7V21.3z"></path><path d="M110,15.2c0.2-0.3,0.2-0.8,3.8-5.2h3.7l-4.6,5.7l5,6.3h-3.7l-4.2-5.8V22h-3V6h3V15.2z"></path><path d="M58.5,21.3c-1.5,0.5-2.7,0.6-4.2,0.6c-3.6,0-5.8-1.8-5.8-6c0-3.1,1.9-5.9,5.5-5.9s4.9,2.5,4.9,4.9c0,0.8,0,1.5-0.1,2h-7.3 c0.1,2.5,1.5,2.8,3.6,2.8c1.1,0,2.2-0.3,3.4-0.7C58.5,19,58.5,21.3,58.5,21.3z M56,15c0-1.4-0.5-2.9-2-2.9c-1.4,0-2.3,1.3-2.4,2.9 C51.6,15,56,15,56,15z"></path></svg>
+ </a>
+ </div>
+ <div class="jp-masthead__nav">
+ <?php if ( is_network_admin() ) {
+ $current_screen = get_current_screen();
+
+ $highlight_current_sites = ( 'toplevel_page_jetpack-network' === $current_screen->id ? 'is-primary' : '' );
+ $highlight_current_settings = ( 'jetpack_page_jetpack-settings-network' === $current_screen->id ? 'is-primary' : '' );
+ ?>
+ <span class="dops-button-group">
+ <?php
+ if ( current_user_can( 'jetpack_network_sites_page' ) ) {
+ ?><a href="<?php echo esc_url( network_admin_url( 'admin.php?page=jetpack' ) ); ?>" type="button" class="<?php echo esc_attr( $highlight_current_sites ); ?> dops-button is-compact" title="<?php esc_html_e( "Manage your network's Jetpack Sites.", 'jetpack' ); ?>"><?php echo esc_html_x( 'Sites', 'Navigation item', 'jetpack' ); ?></a><?php
+ } if ( current_user_can( 'jetpack_network_settings_page' ) ) {
+ ?><a href="<?php echo esc_url( network_admin_url( 'admin.php?page=jetpack-settings' ) ); ?>" type="button" class="<?php echo esc_attr( $highlight_current_settings ); ?> dops-button is-compact" title="<?php esc_html_e( "Manage your network's Jetpack Sites.", 'jetpack' ); ?>"><?php echo esc_html_x( 'Network Settings', 'Navigation item', 'jetpack' ); ?></a><?php
+ } ?>
+ </span>
+ <?php } else { ?>
+ <span class="dops-button-group">
+ <a href="<?php echo esc_url( $jetpack_admin_url ); ?>" type="button" class="dops-button is-compact"><?php esc_html_e( 'Dashboard', 'jetpack' ); ?></a><?php
+ if ( current_user_can( 'jetpack_manage_modules' ) ) {
+ ?><a href="<?php echo esc_url( $jetpack_admin_url . '#/settings' ); ?>" type="button" class="dops-button is-compact"><?php esc_html_e( 'Settings', 'jetpack' ); ?></a><?php
+ } ?>
+ </span>
+ <?php } ?>
+ </div>
+ </div>
+ </div>
+ <div class="wrap"><div id="jp-admin-notices" aria-live="polite"></div></div>
+ <!-- START OF CALLBACK -->
+ <?php
+ ob_start();
+ call_user_func( $callback );
+ $callback_ui = ob_get_contents();
+ ob_end_clean();
+ echo $callback_ui;
+ ?>
+ <!-- END OF CALLBACK -->
+ <div class="jp-footer">
+ <div class="jp-footer__a8c-attr-container"><a href="https://automattic.com" target="_blank" rel="noopener noreferrer"><svg role="img" class="jp-footer__a8c-attr" x="0" y="0" viewBox="0 0 935 38.2" enable-background="new 0 0 935 38.2" aria-labelledby="a8c-svg-title"><title id="a8c-svg-title">An Automattic Airline</title><path d="M317.1 38.2c-12.6 0-20.7-9.1-20.7-18.5v-1.2c0-9.6 8.2-18.5 20.7-18.5 12.6 0 20.8 8.9 20.8 18.5v1.2C337.9 29.1 329.7 38.2 317.1 38.2zM331.2 18.6c0-6.9-5-13-14.1-13s-14 6.1-14 13v0.9c0 6.9 5 13.1 14 13.1s14.1-6.2 14.1-13.1V18.6zM175 36.8l-4.7-8.8h-20.9l-4.5 8.8h-7L157 1.3h5.5L182 36.8H175zM159.7 8.2L152 23.1h15.7L159.7 8.2zM212.4 38.2c-12.7 0-18.7-6.9-18.7-16.2V1.3h6.6v20.9c0 6.6 4.3 10.5 12.5 10.5 8.4 0 11.9-3.9 11.9-10.5V1.3h6.7V22C231.4 30.8 225.8 38.2 212.4 38.2zM268.6 6.8v30h-6.7v-30h-15.5V1.3h37.7v5.5H268.6zM397.3 36.8V8.7l-1.8 3.1 -14.9 25h-3.3l-14.7-25 -1.8-3.1v28.1h-6.5V1.3h9.2l14 24.4 1.7 3 1.7-3 13.9-24.4h9.1v35.5H397.3zM454.4 36.8l-4.7-8.8h-20.9l-4.5 8.8h-7l19.2-35.5h5.5l19.5 35.5H454.4zM439.1 8.2l-7.7 14.9h15.7L439.1 8.2zM488.4 6.8v30h-6.7v-30h-15.5V1.3h37.7v5.5H488.4zM537.3 6.8v30h-6.7v-30h-15.5V1.3h37.7v5.5H537.3zM569.3 36.8V4.6c2.7 0 3.7-1.4 3.7-3.4h2.8v35.5L569.3 36.8 569.3 36.8zM628 11.3c-3.2-2.9-7.9-5.7-14.2-5.7 -9.5 0-14.8 6.5-14.8 13.3v0.7c0 6.7 5.4 13 15.3 13 5.9 0 10.8-2.8 13.9-5.7l4 4.2c-3.9 3.8-10.5 7.1-18.3 7.1 -13.4 0-21.6-8.7-21.6-18.3v-1.2c0-9.6 8.9-18.7 21.9-18.7 7.5 0 14.3 3.1 18 7.1L628 11.3zM321.5 12.4c1.2 0.8 1.5 2.4 0.8 3.6l-6.1 9.4c-0.8 1.2-2.4 1.6-3.6 0.8l0 0c-1.2-0.8-1.5-2.4-0.8-3.6l6.1-9.4C318.7 11.9 320.3 11.6 321.5 12.4L321.5 12.4z"></path><path d="M37.5 36.7l-4.7-8.9H11.7l-4.6 8.9H0L19.4 0.8H25l19.7 35.9H37.5zM22 7.8l-7.8 15.1h15.9L22 7.8zM82.8 36.7l-23.3-24 -2.3-2.5v26.6h-6.7v-36H57l22.6 24 2.3 2.6V0.8h6.7v35.9H82.8z"></path><path d="M719.9 37l-4.8-8.9H694l-4.6 8.9h-7.1l19.5-36h5.6l19.8 36H719.9zM704.4 8l-7.8 15.1h15.9L704.4 8zM733 37V1h6.8v36H733zM781 37c-1.8 0-2.6-2.5-2.9-5.8l-0.2-3.7c-0.2-3.6-1.7-5.1-8.4-5.1h-12.8V37H750V1h19.6c10.8 0 15.7 4.3 15.7 9.9 0 3.9-2 7.7-9 9 7 0.5 8.5 3.7 8.6 7.9l0.1 3c0.1 2.5 0.5 4.3 2.2 6.1V37H781zM778.5 11.8c0-2.6-2.1-5.1-7.9-5.1h-13.8v10.8h14.4c5 0 7.3-2.4 7.3-5.2V11.8zM794.8 37V1h6.8v30.4h28.2V37H794.8zM836.7 37V1h6.8v36H836.7zM886.2 37l-23.4-24.1 -2.3-2.5V37h-6.8V1h6.5l22.7 24.1 2.3 2.6V1h6.8v36H886.2zM902.3 37V1H935v5.6h-26v9.2h20v5.5h-20v10.1h26V37H902.3z"></path></svg></a></div>
+ <ul class="jp-footer__links">
+ <li class="jp-footer__link-item">
+ <a href="https://jetpack.com" target="_blank" rel="noopener noreferrer" class="jp-footer__link" title="<?php esc_html_e( 'Jetpack version', 'jetpack' ); ?>">Jetpack <?php echo JETPACK__VERSION; ?></a>
+ </li>
+ <li class="jp-footer__link-item">
+ <a href="https://wordpress.com/tos/" target="_blank" rel="noopener noreferrer" title="<?php esc_html__( 'WordPress.com Terms of Service', 'jetpack' ); ?>" class="jp-footer__link"><?php echo esc_html_x( 'Terms', 'Navigation item', 'jetpack' ); ?></a>
+ </li>
+ <li class="jp-footer__link-item">
+ <a href="<?php echo esc_url( $jetpack_admin_url . '#/privacy' ); ?>" rel="noopener noreferrer" title="<?php esc_html_e( "Automattic's Privacy Policy", 'jetpack' ); ?>" class="jp-footer__link"><?php echo esc_html_x( 'Privacy', 'Navigation item', 'jetpack' ); ?></a>
+ </li>
+ <?php if ( is_multisite() && current_user_can( 'jetpack_network_sites_page' ) ) { ?>
+ <li class="jp-footer__link-item">
+ <a href="<?php echo esc_url( network_admin_url( 'admin.php?page=jetpack' ) ); ?>" title="<?php esc_html_e( "Manage your network's Jetpack Sites.", 'jetpack' ); ?>" class="jp-footer__link"><?php echo esc_html_x( 'Network Sites', 'Navigation item', 'jetpack' ); ?></a>
+ </li>
+ <?php } ?>
+ <?php if ( is_multisite() && current_user_can( 'jetpack_network_settings_page' ) ) { ?>
+ <li class="jp-footer__link-item">
+ <a href="<?php echo esc_url( network_admin_url( 'admin.php?page=jetpack-settings' ) ); ?>" title="<?php esc_html_e( "Manage your network's Jetpack Sites.", 'jetpack' ); ?>" class="jp-footer__link"><?php echo esc_html_x( 'Network Settings', 'Navigation item', 'jetpack' ); ?></a>
+ </li>
+ <?php } ?>
+ <?php if ( current_user_can( 'manage_options' ) ) { ?>
+ <li class="jp-footer__link-item">
+ <a href="<?php echo esc_url( admin_url() . 'admin.php?page=jetpack-debugger' ); ?>" title="<?php esc_html_e( "Test your site's compatibility with Jetpack.", 'jetpack' ); ?>" class="jp-footer__link"><?php echo esc_html_x( 'Debug', 'Navigation item', 'jetpack' ); ?></a>
+ </li>
+ <?php } ?>
+ </ul>
+ </div>
+ </div>
+<?php return;
+ }
}
diff --git a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-react-page.php b/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-react-page.php
index 57a81a19..45f7d1e5 100644
--- a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-react-page.php
+++ b/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-react-page.php
@@ -9,10 +9,8 @@ class Jetpack_React_Page extends Jetpack_Admin_Page {
protected $is_redirecting = false;
function get_page_hook() {
- $title = _x( 'Jetpack', 'The menu item label', 'jetpack' );
-
// Add the main admin Jetpack menu
- return add_menu_page( 'Jetpack', $title, 'jetpack_admin_page', 'jetpack', array( $this, 'render' ), 'div' );
+ return add_menu_page( 'Jetpack', 'Jetpack', 'jetpack_admin_page', 'jetpack', array( $this, 'render' ), 'div' );
}
function add_page_actions( $hook ) {
@@ -109,7 +107,6 @@ class Jetpack_React_Page extends Jetpack_Admin_Page {
function render_nojs_configurable( $module_name ) {
$module_name = preg_replace( '/[^\da-z\-]+/', '', $_GET['configure'] );
- include_once( JETPACK__PLUGIN_DIR . '_inc/header.php' );
echo '<div class="wrap configure-module">';
if ( Jetpack::is_module( $module_name ) && current_user_can( 'jetpack_configure_modules' ) ) {
@@ -167,41 +164,27 @@ class Jetpack_React_Page extends Jetpack_Admin_Page {
}
function additional_styles() {
- $rtl = is_rtl() ? '.rtl' : '';
-
- wp_enqueue_style( 'dops-css', plugins_url( "_inc/build/admin.dops-style$rtl.css", JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION );
- wp_enqueue_style( 'components-css', plugins_url( "_inc/build/style.min$rtl.css", JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION );
+ Jetpack_Admin_Page::load_wrapper_styles();
}
function page_admin_scripts() {
- if ( $this->is_redirecting ) {
+ if ( $this->is_redirecting || isset( $_GET['configure'] ) ) {
return; // No need for scripts on a fallback page
}
- $is_dev_mode = Jetpack::is_development_mode();
-
// Enqueue jp.js and localize it
wp_enqueue_script( 'react-plugin', plugins_url( '_inc/build/admin.js', JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION, true );
- if ( ! $is_dev_mode && Jetpack::is_active() ) {
+ if ( ! Jetpack::is_development_mode() && Jetpack::is_active() ) {
// Required for Analytics
wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
}
- // Collecting roles that can view site stats.
- $stats_roles = array();
- $enabled_roles = function_exists( 'stats_get_option' ) ? stats_get_option( 'roles' ) : array( 'administrator' );
-
- if ( ! function_exists( 'get_editable_roles' ) ) {
- require_once ABSPATH . 'wp-admin/includes/user.php';
- }
- foreach ( get_editable_roles() as $slug => $role ) {
- $stats_roles[ $slug ] = array(
- 'name' => translate_user_role( $role['name'] ),
- 'canView' => is_array( $enabled_roles ) ? in_array( $slug, $enabled_roles, true ) : false,
- );
- }
+ // Add objects to be passed to the initial state of the app
+ wp_localize_script( 'react-plugin', 'Initial_State', $this->get_initial_state() );
+ }
+ function get_initial_state() {
// Load API endpoint base classes and endpoints for getting the module list fed into the JS Admin Page
require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php';
require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php';
@@ -217,11 +200,19 @@ class Jetpack_React_Page extends Jetpack_Admin_Page {
$modules[ $slug ]['long_description'] = html_entity_decode( $data['long_description'] );
}
- // Get last post, to build the link to Customizer in the Related Posts module.
- $last_post = get_posts( array( 'posts_per_page' => 1 ) );
- $last_post = isset( $last_post[0] ) && $last_post[0] instanceof WP_Post
- ? get_permalink( $last_post[0]->ID )
- : get_home_url();
+ // Collecting roles that can view site stats.
+ $stats_roles = array();
+ $enabled_roles = function_exists( 'stats_get_option' ) ? stats_get_option( 'roles' ) : array( 'administrator' );
+
+ if ( ! function_exists( 'get_editable_roles' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/user.php';
+ }
+ foreach ( get_editable_roles() as $slug => $role ) {
+ $stats_roles[ $slug ] = array(
+ 'name' => translate_user_role( $role['name'] ),
+ 'canView' => is_array( $enabled_roles ) ? in_array( $slug, $enabled_roles, true ) : false,
+ );
+ }
// Get information about current theme.
$current_theme = wp_get_theme();
@@ -234,8 +225,13 @@ class Jetpack_React_Page extends Jetpack_Admin_Page {
}
}
- // Add objects to be passed to the initial state of the app
- wp_localize_script( 'react-plugin', 'Initial_State', array(
+ // Get last post, to build the link to Customizer in the Related Posts module.
+ $last_post = get_posts( array( 'posts_per_page' => 1 ) );
+ $last_post = isset( $last_post[0] ) && $last_post[0] instanceof WP_Post
+ ? get_permalink( $last_post[0]->ID )
+ : get_home_url();
+
+ return array(
'WP_API_root' => esc_url_raw( rest_url() ),
'WP_API_nonce' => wp_create_nonce( 'wp_rest' ),
'pluginBaseUrl' => plugins_url( '', JETPACK__PLUGIN_FILE ),
@@ -243,18 +239,20 @@ class Jetpack_React_Page extends Jetpack_Admin_Page {
'isActive' => Jetpack::is_active(),
'isStaging' => Jetpack::is_staging_site(),
'devMode' => array(
- 'isActive' => $is_dev_mode,
+ 'isActive' => Jetpack::is_development_mode(),
'constant' => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG,
'url' => site_url() && false === strpos( site_url(), '.' ),
'filter' => apply_filters( 'jetpack_development_mode', false ),
),
'isPublic' => '1' == get_option( 'blog_public' ),
'isInIdentityCrisis' => Jetpack::validate_sync_error_idc_option(),
+ 'sandboxDomain' => JETPACK__SANDBOX_DOMAIN,
),
'connectUrl' => Jetpack::init()->build_connect_url( true, false, false ),
'dismissedNotices' => $this->get_dismissed_jetpack_notices(),
'isDevVersion' => Jetpack::is_development_version(),
'currentVersion' => JETPACK__VERSION,
+ 'is_gutenberg_available' => Jetpack_Gutenberg::is_gutenberg_available(),
'getModules' => $modules,
'showJumpstart' => jetpack_show_jumpstart(),
'rawUrl' => Jetpack::build_raw_urls( get_home_url() ),
@@ -289,6 +287,7 @@ class Jetpack_React_Page extends Jetpack_Admin_Page {
'showPromotions' => apply_filters( 'jetpack_show_promotions', true ),
'isAtomicSite' => jetpack_is_atomic_site(),
'plan' => Jetpack::get_active_plan(),
+ 'showBackups' => Jetpack::show_backups_ui(),
),
'themeData' => array(
'name' => $current_theme->get( 'Name' ),
@@ -298,7 +297,7 @@ class Jetpack_React_Page extends Jetpack_Admin_Page {
),
),
'locale' => Jetpack::get_i18n_data_json(),
- 'localeSlug' => join( '-', explode( '_', jetpack_get_user_locale() ) ),
+ 'localeSlug' => join( '-', explode( '_', get_user_locale() ) ),
'jetpackStateNotices' => array(
'messageCode' => Jetpack::state( 'message' ),
'errorCode' => Jetpack::state( 'error' ),
@@ -307,7 +306,17 @@ class Jetpack_React_Page extends Jetpack_Admin_Page {
'tracksUserData' => Jetpack_Tracks_Client::get_connected_user_tracks_identity(),
'currentIp' => function_exists( 'jetpack_protect_get_ip' ) ? jetpack_protect_get_ip() : false,
'lastPostUrl' => esc_url( $last_post ),
- ) );
+ 'externalServicesConnectUrls' => $this->get_external_services_connect_urls()
+ );
+ }
+
+ function get_external_services_connect_urls() {
+ $connect_urls = array();
+ jetpack_require_lib( 'class.jetpack-keyring-service-helper' );
+ foreach ( Jetpack_Keyring_Service_Helper::$SERVICES as $service_name => $service_info ) {
+ $connect_urls[ $service_name ] = Jetpack_Keyring_Service_Helper::connect_url( $service_name, $service_info[ 'for' ] );
+ }
+ return $connect_urls;
}
/**
diff --git a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-settings-page.php b/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-settings-page.php
index 3934a27d..13854cc7 100644
--- a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-settings-page.php
+++ b/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-settings-page.php
@@ -12,7 +12,14 @@ class Jetpack_Settings_Page extends Jetpack_Admin_Page {
// Adds the Settings sub menu
function get_page_hook() {
- return add_submenu_page( null, __( 'Jetpack Settings', 'jetpack' ), __( 'Settings', 'jetpack' ), 'jetpack_manage_modules', 'jetpack_modules', array( $this, 'render' ) );
+ return add_submenu_page(
+ null,
+ __( 'Jetpack Settings', 'jetpack' ),
+ __( 'Settings', 'jetpack' ),
+ 'jetpack_manage_modules',
+ 'jetpack_modules',
+ array( $this, 'render' )
+ );
}
// Renders the module list table where you can use bulk action or row
@@ -20,17 +27,6 @@ class Jetpack_Settings_Page extends Jetpack_Admin_Page {
function page_render() {
$list_table = new Jetpack_Modules_List_Table;
- $static_html = @file_get_contents( JETPACK__PLUGIN_DIR . '_inc/build/static.html' );
-
- // If static.html isn't there, there's nothing else we can do.
- if ( false === $static_html ) {
- echo '<p>';
- esc_html_e( 'Error fetching static.html. Try running: ', 'jetpack' );
- echo '<code>yarn distclean && yarn build</code>';
- echo '</p>';
- return;
- }
-
// We have static.html so let's continue trying to fetch the others
$noscript_notice = @file_get_contents( JETPACK__PLUGIN_DIR . '_inc/build/static-noscript-notice.html' );
$version_notice = $rest_api_notice = @file_get_contents( JETPACK__PLUGIN_DIR . '_inc/build/static-version-notice.html' );
@@ -80,10 +76,6 @@ class Jetpack_Settings_Page extends Jetpack_Admin_Page {
$ie_notice
);
- ob_start();
-
- $this->admin_page_top();
-
if ( $this->is_wp_version_too_old() ) {
echo $version_notice;
}
@@ -152,17 +144,6 @@ class Jetpack_Settings_Page extends Jetpack_Admin_Page {
</div><!-- /.content -->
<?php
- $this->admin_page_bottom();
-
- $page_content = ob_get_contents();
- ob_end_clean();
-
- echo str_replace(
- '<div class="jp-loading-placeholder"><span class="dashicons dashicons-wordpress-alt"></span></div>',
- $page_content,
- $static_html
- );
-
JetpackTracking::record_user_event( 'wpa_page_view', array( 'path' => 'old_settings' ) );
}
@@ -172,9 +153,7 @@ class Jetpack_Settings_Page extends Jetpack_Admin_Page {
* @since 4.3.0
*/
function additional_styles() {
- $rtl = is_rtl() ? '.rtl' : '';
- wp_enqueue_style( 'dops-css', plugins_url( "_inc/build/admin.dops-style$rtl.css", JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION );
- wp_enqueue_style( 'components-css', plugins_url( "_inc/build/style.min$rtl.css", JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION );
+ Jetpack_Admin_Page::load_wrapper_styles();
}
// Javascript logic specific to the list table
diff --git a/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php b/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php
index 70f8ed62..6af6a1ef 100644
--- a/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php
+++ b/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php
@@ -17,6 +17,9 @@ require_once ABSPATH . '/wp-includes/class-wp-error.php';
// Register endpoints when WP REST API is initialized.
add_action( 'rest_api_init', array( 'Jetpack_Core_Json_Api_Endpoints', 'register_endpoints' ) );
+// Load API endpoints that are synced with WP.com
+// Each of these is a class that will register its own routes on 'rest_api_init'.
+require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/load-wpcom-endpoints.php';
/**
* Class Jetpack_Core_Json_Api_Endpoints
@@ -102,6 +105,13 @@ class Jetpack_Core_Json_Api_Endpoints {
'callback' => __CLASS__ . '::jetpack_connection_status',
) );
+ // Test current connection status of Jetpack
+ register_rest_route( 'jetpack/v4', '/connection/test', array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => __CLASS__ . '::jetpack_connection_test',
+ 'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
+ ) );
+
register_rest_route( 'jetpack/v4', '/rewind', array(
'methods' => WP_REST_Server::READABLE,
'callback' => __CLASS__ . '::get_rewind_data',
@@ -174,6 +184,13 @@ class Jetpack_Core_Json_Api_Endpoints {
'permission_callback' => array( $site_endpoint , 'can_request' ),
) );
+ // Get related posts of a certain site post
+ register_rest_route( 'jetpack/v4', '/site/posts/related', array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $site_endpoint, 'get_related_posts' ),
+ 'permission_callback' => array( $site_endpoint , 'can_request' ),
+ ) );
+
// Confirm that a site in identity crisis should be in staging mode
register_rest_route( 'jetpack/v4', '/identity-crisis/confirm-safe-mode', array(
'methods' => WP_REST_Server::EDITABLE,
@@ -378,6 +395,63 @@ class Jetpack_Core_Json_Api_Endpoints {
'callback' => array( $widget_endpoint, 'process' ),
'permission_callback' => array( $widget_endpoint, 'can_request' ),
) );
+
+ // Site Verify: check if the site is verified, and a get verification token if not
+ register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => __CLASS__ . '::is_site_verified_and_token',
+ 'permission_callback' => __CLASS__ . '::update_settings_permission_check',
+ ) );
+
+ register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)/(?<keyring_id>[0-9]+)', array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => __CLASS__ . '::is_site_verified_and_token',
+ 'permission_callback' => __CLASS__ . '::update_settings_permission_check',
+ ) );
+
+ // Site Verify: tell a service to verify the site
+ register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', array(
+ 'methods' => WP_REST_Server::EDITABLE,
+ 'callback' => __CLASS__ . '::verify_site',
+ 'permission_callback' => __CLASS__ . '::update_settings_permission_check',
+ 'args' => array(
+ 'keyring_id' => array(
+ 'required' => true,
+ 'type' => 'integer',
+ 'validate_callback' => __CLASS__ . '::validate_posint',
+ ),
+ )
+ ) );
+
+ // Get and set API keys.
+ // Note: permission_callback intentionally omitted from the GET method.
+ // Map block requires open access to API keys on the front end.
+ register_rest_route(
+ 'jetpack/v4',
+ '/service-api-keys/(?P<service>[a-z\-_]+)',
+ array(
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => __CLASS__ . '::get_service_api_key',
+ ),
+ array(
+ 'methods' => WP_REST_Server::EDITABLE,
+ 'callback' => __CLASS__ . '::update_service_api_key',
+ 'permission_callback' => __CLASS__ . '::edit_others_posts_check',
+ 'args' => array(
+ 'service_api_key' => array(
+ 'required' => true,
+ 'type' => 'text',
+ ),
+ ),
+ ),
+ array(
+ 'methods' => WP_REST_Server::DELETABLE,
+ 'callback' => __CLASS__ . '::delete_service_api_key',
+ 'permission_callback' => __CLASS__ . '::edit_others_posts_check',
+ ),
+ )
+ );
}
public static function get_plans( $request ) {
@@ -461,6 +535,112 @@ class Jetpack_Core_Json_Api_Endpoints {
return $result;
}
+
+ /**
+ * Checks if this site has been verified using a service - only 'google' supported at present - and a specfic
+ * keyring to use to get the token if it is not
+ *
+ * Returns 'verified' = true/false, and a token if 'verified' is false and site is ready for verification
+ *
+ * @since 6.6.0
+ *
+ * @param WP_REST_Request $request The request sent to the WP REST API.
+ *
+ * @return array|wp-error
+ */
+ public static function is_site_verified_and_token( $request ) {
+ /**
+ * Return an error if the site uses a Maintenance / Coming Soon plugin
+ * and if the plugin is configured to make the site private.
+ *
+ * We currently handle the following plugins:
+ * - https://github.com/mojoness/mojo-marketplace-wp-plugin (used by bluehost)
+ * - https://wordpress.org/plugins/mojo-under-construction
+ * - https://wordpress.org/plugins/under-construction-page
+ * - https://wordpress.org/plugins/ultimate-under-construction
+ * - https://wordpress.org/plugins/coming-soon
+ *
+ * You can handle this in your own plugin thanks to the `jetpack_is_under_construction_plugin` filter.
+ * If the filter returns true, we will consider the site as under construction.
+ */
+ $mm_coming_soon = get_option( 'mm_coming_soon', null );
+ $under_construction_activation_status = get_option( 'underConstructionActivationStatus', null );
+ $ucp_options = get_option( 'ucp_options', array() );
+ $uuc_settings = get_option( 'uuc_settings', array() );
+ $csp4 = get_option( 'seed_csp4_settings_content', array() );
+ if (
+ ( Jetpack::is_plugin_active( 'mojo-marketplace-wp-plugin/mojo-marketplace.php' ) && 'true' === $mm_coming_soon )
+ || Jetpack::is_plugin_active( 'mojo-under-construction/mojo-contruction.php' ) && 1 == $under_construction_activation_status // WPCS: loose comparison ok.
+ || ( Jetpack::is_plugin_active( 'under-construction-page/under-construction.php' ) && isset( $ucp_options['status'] ) && 1 == $ucp_options['status'] ) // WPCS: loose comparison ok.
+ || ( Jetpack::is_plugin_active( 'ultimate-under-construction/ultimate-under-construction.php' ) && isset( $uuc_settings['enable'] ) && 1 == $uuc_settings['enable'] ) // WPCS: loose comparison ok.
+ || ( Jetpack::is_plugin_active( 'coming-soon/coming-soon.php' ) && isset( $csp4['status'] ) && ( 1 == $csp4['status'] || 2 == $csp4['status'] ) ) // WPCS: loose comparison ok.
+ /**
+ * Allow plugins to mark a site as "under construction".
+ *
+ * @since 6.7.0
+ *
+ * @param false bool Is the site under construction? Default to false.
+ */
+ || true === apply_filters( 'jetpack_is_under_construction_plugin', false )
+ ) {
+ return new WP_Error( 'forbidden', __( 'Site is under construction and cannot be verified', 'jetpack' ) );
+ }
+
+ Jetpack::load_xml_rpc_client();
+ $xml = new Jetpack_IXR_Client( array(
+ 'user_id' => get_current_user_id(),
+ ) );
+
+ $args = array(
+ 'user_id' => get_current_user_id(),
+ 'service' => $request[ 'service' ],
+ );
+
+ if ( isset( $request[ 'keyring_id' ] ) ) {
+ $args[ 'keyring_id' ] = $request[ 'keyring_id' ];
+ }
+
+ $xml->query( 'jetpack.isSiteVerified', $args );
+
+ if ( $xml->isError() ) {
+ return new WP_Error( 'error_checking_if_site_verified_google', sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
+ } else {
+ return $xml->getResponse();
+ }
+ }
+
+
+
+ public static function verify_site( $request ) {
+ Jetpack::load_xml_rpc_client();
+ $xml = new Jetpack_IXR_Client( array(
+ 'user_id' => get_current_user_id(),
+ ) );
+
+ $params = $request->get_json_params();
+
+ $xml->query( 'jetpack.verifySite', array(
+ 'user_id' => get_current_user_id(),
+ 'service' => $request[ 'service' ],
+ 'keyring_id' => $params[ 'keyring_id' ],
+ )
+ );
+
+ if ( $xml->isError() ) {
+ return new WP_Error( 'error_verifying_site_google', sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
+ } else {
+ $response = $xml->getResponse();
+
+ if ( ! empty( $response['errors'] ) ) {
+ $error = new WP_Error;
+ $error->errors = $response['errors'];
+ return $error;
+ }
+
+ return $response;
+ }
+ }
+
/**
* Handles verification that a site is registered
*
@@ -688,6 +868,19 @@ class Jetpack_Core_Json_Api_Endpoints {
}
/**
+ * Verify that user can edit other's posts (Editors and Administrators).
+ *
+ * @return bool Whether user has the capability 'edit_others_posts'.
+ */
+ public static function edit_others_posts_check() {
+ if ( current_user_can( 'edit_others_posts' ) ) {
+ return true;
+ }
+
+ return new WP_Error( 'invalid_user_permission_edit_others_posts', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
+ }
+
+ /**
* Contextual HTTP error code for authorization failure.
*
* Taken from rest_authorization_required_code() in WP-API plugin until is added to core.
@@ -722,6 +915,43 @@ class Jetpack_Core_Json_Api_Endpoints {
);
}
+ /**
+ * Test connection status for this Jetpack site. It uses the /jetpack-blogs/%d/test-connection wpcom endpoint.
+ *
+ * @since 6.8.0
+ *
+ * @return array|WP_Error WP_Error returned if connection test does not succeed.
+ */
+ public static function jetpack_connection_test() {
+ $response = Jetpack_Client::wpcom_json_api_request_as_blog(
+ sprintf( '/jetpack-blogs/%d/test-connection', Jetpack_Options::get_option( 'id' ) ),
+ Jetpack_Client::WPCOM_JSON_API_VERSION
+ );
+
+ if ( is_wp_error( $response ) ) {
+ /* translators: %1$s is the error code, %2$s is the error message */
+ return new WP_Error( 'connection_test_failed', sprintf( __( 'Connection test failed (#%1$s: %2$s)', 'jetpack' ), $response->get_error_code(), $response->get_error_message() ), array( 'status' => $response->get_error_code() ) );
+ }
+
+ $body = wp_remote_retrieve_body( $response );
+ if ( ! $body ) {
+ return new WP_Error( 'connection_test_failed', __( 'Connection test failed (empty response body)', 'jetpack' ), array( 'status' => $response->get_error_code() ) );
+ }
+
+ $result = json_decode( $body );
+ $is_connected = (bool) $result->connected;
+ $message = $result->message;
+
+ if ( $is_connected ) {
+ return rest_ensure_response( array(
+ 'code' => 'success',
+ 'message' => $message,
+ ) );
+ } else {
+ return new WP_Error( 'connection_test_failed', $message, array( 'status' => $response->get_error_code() ) );
+ }
+ }
+
public static function rewind_data() {
$site_id = Jetpack_Options::get_option( 'id' );
@@ -881,11 +1111,22 @@ class Jetpack_Core_Json_Api_Endpoints {
);
}
+ // Update the master user in Jetpack
$updated = Jetpack_Options::update_option( 'master_user', $new_owner_id );
- if ( $updated ) {
+
+ // Notify WPCOM about the master user change
+ Jetpack::load_xml_rpc_client();
+ $xml = new Jetpack_IXR_Client( array(
+ 'user_id' => get_current_user_id(),
+ ) );
+ $xml->query( 'jetpack.switchBlogOwner', array(
+ 'new_blog_owner' => $new_owner_id,
+ ) );
+
+ if ( $updated && ! $xml->isError() ) {
return rest_ensure_response(
array(
- 'code' => 'success'
+ 'code' => 'success',
)
);
}
@@ -1916,6 +2157,14 @@ class Jetpack_Core_Json_Api_Endpoints {
'validate_callback' => __CLASS__ . '::validate_boolean',
'jp_group' => 'wordads',
),
+ 'wordads_custom_adstxt' => array(
+ 'description' => esc_html__( 'Custom ads.txt entries', 'jetpack' ),
+ 'type' => 'string',
+ 'default' => '',
+ 'validate_callback' => __CLASS__ . '::validate_string',
+ 'sanitize_callback' => 'sanitize_textarea_field',
+ 'jp_group' => 'wordads',
+ ),
// Google Analytics
'google_analytics_tracking_id' => array(
@@ -2253,7 +2502,7 @@ class Jetpack_Core_Json_Api_Endpoints {
* @return bool|WP_Error
*/
public static function validate_verification_service( $value = '', $request, $param ) {
- if ( ! empty( $value ) && ! ( is_string( $value ) && ( preg_match( '/^[a-z0-9_-]+$/i', $value ) || preg_match( '#^<meta name="([a-z0-9_\-.:]+)?" content="([a-z0-9_-]+)?" />$#i', $value ) ) ) ) {
+ if ( ! empty( $value ) && ! ( is_string( $value ) && ( preg_match( '/^[a-z0-9_-]+$/i', $value ) || jetpack_verification_get_code( $value ) !== false ) ) ) {
return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an alphanumeric string or a verification tag.', 'jetpack' ), $param ) );
}
return true;
@@ -2831,6 +3080,182 @@ class Jetpack_Core_Json_Api_Endpoints {
return array();
}
+
+ /**
+ * Get third party plugin API keys.
+ *
+ * @param WP_REST_Request $request {
+ * Array of parameters received by request.
+ *
+ * @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
+ * }
+ */
+ public static function get_service_api_key( $request ) {
+ $service = self::validate_service_api_service( $request['service'] );
+ if ( ! $service ) {
+ return self::service_api_invalid_service_response();
+ }
+ $option = self::key_for_api_service( $service );
+ $message = esc_html__( 'API key retrieved successfully.', 'jetpack' );
+ return array(
+ 'code' => 'success',
+ 'service' => $service,
+ 'service_api_key' => Jetpack_Options::get_option( $option, '' ),
+ 'message' => $message,
+ );
+ }
+
+ /**
+ * Update third party plugin API keys.
+ *
+ * @param WP_REST_Request $request {
+ * Array of parameters received by request.
+ *
+ * @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
+ * }
+ */
+ public static function update_service_api_key( $request ) {
+ $service = self::validate_service_api_service( $request['service'] );
+ if ( ! $service ) {
+ return self::service_api_invalid_service_response();
+ }
+ $params = $request->get_json_params();
+ $service_api_key = trim( $params['service_api_key'] );
+ $option = self::key_for_api_service( $service );
+ $validation = self::validate_service_api_key( $service_api_key, $service );
+ if ( ! $validation['status'] ) {
+ return new WP_Error( 'invalid_key', esc_html__( 'Invalid API Key', 'jetpack' ), array( 'status' => 404 ) );
+ }
+ $message = esc_html__( 'API key updated successfully.', 'jetpack' );
+ Jetpack_Options::update_option( $option, $service_api_key );
+ return array(
+ 'code' => 'success',
+ 'service' => $service,
+ 'service_api_key' => Jetpack_Options::get_option( $option, '' ),
+ 'message' => $message,
+ );
+ }
+
+ /**
+ * Delete a third party plugin API key.
+ *
+ * @param WP_REST_Request $request {
+ * Array of parameters received by request.
+ *
+ * @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
+ * }
+ */
+ public static function delete_service_api_key( $request ) {
+ $service = self::validate_service_api_service( $request['service'] );
+ if ( ! $service ) {
+ return self::service_api_invalid_service_response();
+ }
+ $option = self::key_for_api_service( $service );
+ Jetpack_Options::delete_option( $option );
+ $message = esc_html__( 'API key deleted successfully.', 'jetpack' );
+ return array(
+ 'code' => 'success',
+ 'service' => $service,
+ 'service_api_key' => Jetpack_Options::get_option( $option, '' ),
+ 'message' => $message,
+ );
+ }
+
+ /**
+ * Validate the service provided in /service-api-keys/ endpoints.
+ * To add a service to these endpoints, add the service name to $valid_services
+ * and add '{service name}_api_key' to the non-compact return array in get_option_names(),
+ * in class-jetpack-options.php
+ *
+ * @param string $service The service the API key is for.
+ * @return string Returns the service name if valid, null if invalid.
+ */
+ public static function validate_service_api_service( $service = null ) {
+ $valid_services = array(
+ 'mapbox',
+ );
+ return in_array( $service, $valid_services, true ) ? $service : null;
+ }
+
+ /**
+ * Error response for invalid service API key requests with an invalid service.
+ */
+ public static function service_api_invalid_service_response() {
+ return new WP_Error(
+ 'invalid_service',
+ esc_html__( 'Invalid Service', 'jetpack' ),
+ array( 'status' => 404 )
+ );
+ }
+
+ /**
+ * Validate API Key
+ *
+ * @param string $key The API key to be validated.
+ * @param string $service The service the API key is for.
+ */
+ public static function validate_service_api_key( $key = null, $service = null ) {
+ $validation = false;
+ switch ( $service ) {
+ case 'mapbox':
+ $validation = self::validate_service_api_key_mapbox( $key );
+ break;
+ }
+ return $validation;
+ }
+
+ /**
+ * Validate Mapbox API key
+ * Based loosely on https://github.com/mapbox/geocoding-example/blob/master/php/MapboxTest.php
+ *
+ * @param string $key The API key to be validated.
+ */
+ public static function validate_service_api_key_mapbox( $key ) {
+ $status = true;
+ $msg = null;
+ $mapbox_url = sprintf(
+ 'https://api.mapbox.com?%s',
+ $key
+ );
+ $mapbox_response = wp_safe_remote_get( esc_url_raw( $mapbox_url ) );
+ $mapbox_body = wp_remote_retrieve_body( $mapbox_response );
+ if ( '{"api":"mapbox"}' !== $mapbox_body ) {
+ $status = false;
+ $msg = esc_html__( 'Can\'t connect to Mapbox', 'jetpack' );
+ return array(
+ 'status' => $status,
+ 'error_message' => $msg,
+ );
+ }
+ $mapbox_geocode_url = esc_url_raw(
+ sprintf(
+ 'https://api.mapbox.com/geocoding/v5/mapbox.places/%s.json?access_token=%s',
+ '1+broadway+new+york+ny+usa',
+ $key
+ )
+ );
+ $mapbox_geocode_response = wp_safe_remote_get( esc_url_raw( $mapbox_geocode_url ) );
+ $mapbox_geocode_body = wp_remote_retrieve_body( $mapbox_geocode_response );
+ $mapbox_geocode_json = json_decode( $mapbox_geocode_body );
+ if ( isset( $mapbox_geocode_json->message ) && ! isset( $mapbox_geocode_json->query ) ) {
+ $status = false;
+ $msg = $mapbox_geocode_json->message;
+ }
+ return array(
+ 'status' => $status,
+ 'error_message' => $msg,
+ );
+ }
+
+ /**
+ * Create site option key for service
+ *
+ * @param string $service The service to create key for.
+ */
+ private static function key_for_api_service( $service ) {
+ return $service . '_api_key';
+ }
+
/**
* Checks if the queried plugin is active.
*
diff --git a/plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php b/plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php
new file mode 100644
index 00000000..c8005ea1
--- /dev/null
+++ b/plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php
@@ -0,0 +1,204 @@
+<?php
+
+class Jetpack_Keyring_Service_Helper {
+ /**
+ * @var Jetpack_Keyring_Service_Helper
+ **/
+ private static $instance = null;
+
+ static function init() {
+ if ( is_null( self::$instance ) ) {
+ self::$instance = new Jetpack_Keyring_Service_Helper;
+ }
+
+ return self::$instance;
+ }
+
+ public static $SERVICES = array(
+ 'facebook' => array(
+ 'for' => 'publicize'
+ ),
+ 'twitter' => array(
+ 'for' => 'publicize'
+ ),
+ 'linkedin' => array(
+ 'for' => 'publicize'
+ ),
+ 'tumblr' => array(
+ 'for' => 'publicize'
+ ),
+ 'path' => array(
+ 'for' => 'publicize'
+ ),
+ 'google_plus' => array(
+ 'for' => 'publicize'
+ ),
+ 'google_site_verification' => array(
+ 'for' => 'other'
+ )
+ );
+
+ private function __construct() {
+ add_action( 'load-settings_page_sharing', array( __CLASS__, 'admin_page_load' ), 9 );
+ }
+
+ function get_services( $filter = 'all' ) {
+ $services = array(
+
+ );
+
+ if ( 'all' == $filter ) {
+ return $services;
+ } else {
+ $connected_services = array();
+ foreach ( $services as $service => $empty ) {
+ $connections = $this->get_connections( $service );
+ if ( $connections ) {
+ $connected_services[ $service ] = $connections;
+ }
+ }
+ return $connected_services;
+ }
+ }
+
+ /**
+ * Gets a URL to the public-api actions. Works like WP's admin_url
+ *
+ * @param string $service Shortname of a specific service.
+ *
+ * @return URL to specific public-api process
+ */
+ // on WordPress.com this is/calls Keyring::admin_url
+ static function api_url( $service = false, $params = array() ) {
+ /**
+ * Filters the API URL used to interact with WordPress.com.
+ *
+ * @since 2.0.0
+ *
+ * @param string https://public-api.wordpress.com/connect/?jetpack=publicize Default Publicize API URL.
+ */
+ $url = apply_filters( 'publicize_api_url', 'https://public-api.wordpress.com/connect/?jetpack=publicize' );
+
+ if ( $service ) {
+ $url = add_query_arg( array( 'service' => $service ), $url );
+ }
+
+ if ( count( $params ) ) {
+ $url = add_query_arg( $params, $url );
+ }
+
+ return $url;
+ }
+
+ static function connect_url( $service_name, $for ) {
+ return add_query_arg( array(
+ 'action' => 'request',
+ 'service' => $service_name,
+ 'kr_nonce' => wp_create_nonce( 'keyring-request' ),
+ 'nonce' => wp_create_nonce( "keyring-request-$service_name" ),
+ 'for' => $for,
+ ), menu_page_url( 'sharing', false ) );
+ }
+
+ static function refresh_url( $service_name, $for ) {
+ return add_query_arg( array(
+ 'action' => 'request',
+ 'service' => $service_name,
+ 'kr_nonce' => wp_create_nonce( 'keyring-request' ),
+ 'refresh' => 1,
+ 'for' => $for,
+ 'nonce' => wp_create_nonce( "keyring-request-$service_name" ),
+ ), admin_url( 'options-general.php?page=sharing' ) );
+ }
+
+ static function disconnect_url( $service_name, $id ) {
+ return add_query_arg( array(
+ 'action' => 'delete',
+ 'service' => $service_name,
+ 'id' => $id,
+ 'kr_nonce' => wp_create_nonce( 'keyring-request' ),
+ 'nonce' => wp_create_nonce( "keyring-request-$service_name" ),
+ ), menu_page_url( 'sharing', false ) );
+ }
+
+ static function admin_page_load() {
+ if ( isset( $_GET['action'] ) ) {
+ if ( isset( $_GET['service'] ) ) {
+ $service_name = $_GET['service'];
+ }
+
+ switch ( $_GET['action'] ) {
+
+ case 'request':
+ check_admin_referer( 'keyring-request', 'kr_nonce' );
+ check_admin_referer( "keyring-request-$service_name", 'nonce' );
+
+ $verification = Jetpack::generate_secrets( 'publicize' );
+ if ( ! $verification ) {
+ $url = Jetpack::admin_url( 'jetpack#/settings' );
+ wp_die( sprintf( __( "Jetpack is not connected. Please connect Jetpack by visiting <a href='%s'>Settings</a>.", 'jetpack' ), $url ) );
+
+ }
+ $stats_options = get_option( 'stats_options' );
+ $wpcom_blog_id = Jetpack_Options::get_option( 'id' );
+ $wpcom_blog_id = ! empty( $wpcom_blog_id ) ? $wpcom_blog_id : $stats_options['blog_id'];
+
+ $user = wp_get_current_user();
+ $redirect = Jetpack_Keyring_Service_Helper::api_url( $service_name, urlencode_deep( array(
+ 'action' => 'request',
+ 'redirect_uri' => add_query_arg( array( 'action' => 'done' ), menu_page_url( 'sharing', false ) ),
+ 'for' => 'publicize',
+ // required flag that says this connection is intended for publicize
+ 'siteurl' => site_url(),
+ 'state' => $user->ID,
+ 'blog_id' => $wpcom_blog_id,
+ 'secret_1' => $verification['secret_1'],
+ 'secret_2' => $verification['secret_2'],
+ 'eol' => $verification['exp'],
+ ) ) );
+ wp_redirect( $redirect );
+ exit;
+ break;
+
+ case 'completed':
+ Jetpack::load_xml_rpc_client();
+ $xml = new Jetpack_IXR_Client();
+ $xml->query( 'jetpack.fetchPublicizeConnections' );
+
+ if ( ! $xml->isError() ) {
+ $response = $xml->getResponse();
+ Jetpack_Options::update_option( 'publicize_connections', $response );
+ }
+
+ break;
+
+ case 'delete':
+ $id = $_GET['id'];
+
+ check_admin_referer( 'keyring-request', 'kr_nonce' );
+ check_admin_referer( "keyring-request-$service_name", 'nonce' );
+
+ Jetpack_Keyring_Service_Helper::disconnect( $service_name, $id );
+
+ do_action( 'connection_disconnected', $service_name );
+ break;
+ }
+ }
+ }
+
+ /**
+ * Remove a Publicize connection
+ */
+ static function disconnect( $service_name, $connection_id, $_blog_id = false, $_user_id = false, $force_delete = false ) {
+ Jetpack::load_xml_rpc_client();
+ $xml = new Jetpack_IXR_Client();
+ $xml->query( 'jetpack.deletePublicizeConnection', $connection_id );
+
+ if ( ! $xml->isError() ) {
+ Jetpack_Options::update_option( 'publicize_connections', $xml->getResponse() );
+ } else {
+ return false;
+ }
+ }
+
+}
diff --git a/plugins/jetpack/_inc/lib/class.media-summary.php b/plugins/jetpack/_inc/lib/class.media-summary.php
index cf1bc050..1cce160b 100644
--- a/plugins/jetpack/_inc/lib/class.media-summary.php
+++ b/plugins/jetpack/_inc/lib/class.media-summary.php
@@ -30,7 +30,11 @@ class Jetpack_Media_Summary {
}
if ( ! class_exists( 'Jetpack_Media_Meta_Extractor' ) ) {
- jetpack_require_lib( 'class.media-extractor' );
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ jetpack_require_lib( 'class.wpcom-media-meta-extractor' );
+ } else {
+ jetpack_require_lib( 'class.media-extractor' );
+ }
}
$post = get_post( $post_id );
@@ -74,7 +78,7 @@ class Jetpack_Media_Summary {
if ( 0 == $return['count']['video'] ) {
// If there is no id on the video, then let's just skip this
if ( ! isset ( $data['id'][0] ) ) {
- continue;
+ break;
}
$guid = $data['id'][0];
@@ -84,7 +88,7 @@ class Jetpack_Media_Summary {
if ( $video_info instanceof stdClass ) {
// Continue early if we can't find a Video slug.
if ( empty( $video_info->files->std->mp4 ) ) {
- continue;
+ break;
}
$url = sprintf(
diff --git a/plugins/jetpack/_inc/lib/class.media.php b/plugins/jetpack/_inc/lib/class.media.php
index cce98f8b..e48c4aad 100644
--- a/plugins/jetpack/_inc/lib/class.media.php
+++ b/plugins/jetpack/_inc/lib/class.media.php
@@ -111,6 +111,7 @@ class Jetpack_Media {
*/
protected static function is_file_supported_for_sideloading( $file ) {
if ( class_exists( 'finfo' ) ) { // php 5.3+
+ // phpcs:ignore PHPCompatibility.PHP.NewClasses.finfoFound
$finfo = new finfo( FILEINFO_MIME );
$mime = explode( '; ', $finfo->file( $file ) );
$type = $mime[0];
diff --git a/plugins/jetpack/_inc/lib/core-api/class-wpcom-rest-field-controller.php b/plugins/jetpack/_inc/lib/core-api/class-wpcom-rest-field-controller.php
new file mode 100644
index 00000000..e599b275
--- /dev/null
+++ b/plugins/jetpack/_inc/lib/core-api/class-wpcom-rest-field-controller.php
@@ -0,0 +1,330 @@
+<?php
+
+// @todo - nicer API for array values?
+
+/**
+ * `WP_REST_Controller` is basically a wrapper for `register_rest_route()`
+ * `WPCOM_REST_API_V2_Field_Controller` is a mostly-analogous wrapper for `register_rest_field()`
+ */
+abstract class WPCOM_REST_API_V2_Field_Controller {
+ /**
+ * @var string|string[] $object_type The REST Object Type(s) to which the field should be added.
+ */
+ protected $object_type;
+
+ /**
+ * @var string $field_name The name of the REST API field to add.
+ */
+ protected $field_name;
+
+ public function __construct() {
+ if ( ! $this->object_type ) {
+ /* translators: %s: object_type */
+ _doing_it_wrong( 'WPCOM_REST_API_V2_Field_Controller::$object_type', sprintf( __( "Property '%s' must be overridden.", 'jetpack' ), 'object_type' ), 'Jetpack 6.8' );
+ return;
+ }
+
+ if ( ! $this->field_name ) {
+ /* translators: %s: field_name */
+ _doing_it_wrong( 'WPCOM_REST_API_V2_Field_Controller::$field_name', sprintf( __( "Property '%s' must be overridden.", 'jetpack' ), 'field_name' ), 'Jetpack 6.8' );
+ return;
+ }
+
+ add_action( 'rest_api_init', array( $this, 'register_fields' ) );
+ }
+
+ /**
+ * Registers the field with the appropriate schema and callbacks.
+ */
+ public function register_fields() {
+ foreach ( (array) $this->object_type as $object_type ) {
+ register_rest_field(
+ $object_type,
+ $this->field_name,
+ array(
+ 'get_callback' => array( $this, 'get_for_response' ),
+ 'update_callback' => array( $this, 'update_from_request' ),
+ 'schema' => $this->get_schema(),
+ )
+ );
+ }
+ }
+
+ /**
+ * Ensures the response matches the schema and request context.
+ *
+ * @param mixed $value
+ * @param WP_REST_Request $request
+ * @return mixed
+ */
+ private function prepare_for_response( $value, $request ) {
+ $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
+ $schema = $this->get_schema();
+
+ $is_valid = rest_validate_value_from_schema( $value, $schema, $this->field_name );
+ if ( is_wp_error( $is_valid ) ) {
+ return $is_valid;
+ }
+
+ return $this->filter_response_by_context( $value, $schema, $context );
+ }
+
+ /**
+ * Returns the schema's default value
+ *
+ * If there is no default, returns the type's falsey value.
+ *
+ * @param array $schema
+ * @return mixed
+ */
+ final public function get_default_value( $schema ) {
+ if ( isset( $schema['default'] ) ) {
+ return $schema['default'];
+ }
+
+ // If you have something more complicated, use $schema['default'];
+ switch ( isset( $schema['type'] ) ? $schema['type'] : 'null' ) {
+ case 'string':
+ return '';
+ case 'integer':
+ case 'number':
+ return 0;
+ case 'object':
+ return (object) array();
+ case 'array':
+ return array();
+ case 'boolean':
+ return false;
+ case 'null':
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * The field's wrapped getter. Does permission checks and output preparation.
+ *
+ * This cannot be extended: implement `->get()` instead.
+ *
+ * @param mixed $object_data Probably an array. Whatever the endpoint returns.
+ * @param string $field_name Should always match `->field_name`
+ * @param WP_REST_Request $request
+ * @param string $object_type Should always match `->object_type`
+ * @return mixed
+ */
+ final public function get_for_response( $object_data, $field_name, $request, $object_type ) {
+ $permission_check = $this->get_permission_check( $object_data, $request );
+
+ if ( ! $permission_check ) {
+ /* translators: %s: get_permission_check() */
+ _doing_it_wrong( 'WPCOM_REST_API_V2_Field_Controller::get_permission_check', sprintf( __( "Method '%s' must return either true or WP_Error.", 'jetpack' ), 'get_permission_check' ), 'Jetpack 6.8' );
+ return $this->get_default_value( $this->get_schema() );
+ }
+
+ if ( is_wp_error( $permission_check ) ) {
+ return $this->get_default_value( $this->get_schema() );
+ }
+
+ $value = $this->get( $object_data, $request );
+
+ return $this->prepare_for_response( $value, $request );
+ }
+
+ /**
+ * The field's wrapped setter. Does permission checks.
+ *
+ * This cannot be extended: implement `->update()` instead.
+ *
+ * @param mixed $value The new value for the field.
+ * @param mixed $object_data Probably a WordPress object (e.g., WP_Post)
+ * @param string $field_name Should always match `->field_name`
+ * @param WP_REST_Request $request
+ * @param string $object_type Should always match `->object_type`
+ * @return void|WP_Error
+ */
+ final public function update_from_request( $value, $object_data, $field_name, $request, $object_type ) {
+ $permission_check = $this->update_permission_check( $value, $object_data, $request );
+
+ if ( ! $permission_check ) {
+ /* translators: %s: update_permission_check() */
+ _doing_it_wrong( 'WPCOM_REST_API_V2_Field_Controller::update_permission_check', sprintf( __( "Method '%s' must return either true or WP_Error.", 'jetpack' ), 'update_permission_check' ), 'Jetpack 6.8' );
+ /* translators: %s: the name of an API response field */
+ return new WP_Error( 'invalid_user_permission', sprintf( __( "You are not allowed to access the '%s' field.", 'jetpack' ), $this->field_name ) );
+ }
+
+ if ( is_wp_error( $permission_check ) ) {
+ return $permission_check;
+ }
+
+ $updated = $this->update( $value, $object_data, $request );
+
+ if ( is_wp_error( $updated ) ) {
+ return $updated;
+ }
+ }
+
+ /**
+ * Permission Check for the field's getter. Must be implemented in the inheriting class.
+ *
+ * @param mixed $object_data Whatever the endpoint would return for its response.
+ * @param WP_REST_Request $request
+ * @return true|WP_Error
+ */
+ public function get_permission_check( $object_data, $request ) {
+ /* translators: %s: get_permission_check() */
+ _doing_it_wrong( 'WPCOM_REST_API_V2_Field_Controller::get_permission_check', sprintf( __( "Method '%s' must be overridden.", 'jetpack' ), __METHOD__ ), 'Jetpack 6.8' );
+ }
+
+ /**
+ * The field's "raw" getter. Must be implemented in the inheriting class.
+ *
+ * @param mixed $object_data Whatever the endpoint would return for its response.
+ * @param WP_REST_Request $request
+ * @return mixed
+ */
+ public function get( $object_data, $request ) {
+ /* translators: %s: get() */
+ _doing_it_wrong( 'WPCOM_REST_API_V2_Field_Controller::get', sprintf( __( "Method '%s' must be overridden.", 'jetpack' ), __METHOD__ ), 'Jetpack 6.8' );
+ }
+
+ /**
+ * Permission Check for the field's setter. Must be implemented in the inheriting class.
+ *
+ * @param mixed $value The new value for the field.
+ * @param mixed $object_data Probably a WordPress object (e.g., WP_Post)
+ * @param WP_REST_Request $request
+ * @return true|WP_Error
+ */
+ public function update_permission_check( $value, $object_data, $request ) {
+ /* translators: %s: update_permission_check() */
+ _doing_it_wrong( 'WPCOM_REST_API_V2_Field_Controller::update_permission_check', sprintf( __( "Method '%s' must be overridden.", 'jetpack' ), __METHOD__ ), 'Jetpack 6.8' );
+ }
+
+ /**
+ * The field's "raw" setter. Must be implemented in the inheriting class.
+ *
+ * @param mixed $value The new value for the field.
+ * @param mixed $object_data Probably a WordPress object (e.g., WP_Post)
+ * @param WP_REST_Request $request
+ * @return mixed
+ */
+ public function update( $value, $object_data, $request ) {
+ /* translators: %s: update() */
+ _doing_it_wrong( 'WPCOM_REST_API_V2_Field_Controller::update', sprintf( __( "Method '%s' must be overridden.", 'jetpack' ), __METHOD__ ), 'Jetpack 6.8' );
+ }
+
+ /**
+ * The JSON Schema for the field
+ *
+ * @link https://json-schema.org/understanding-json-schema/
+ * As of WordPress 5.0, Core currently understands:
+ * * type
+ * * string - not minLength, not maxLength, not pattern
+ * * integer - minimum, maximum, exclusiveMinimum, exclusiveMaximum, not multipleOf
+ * * number - minimum, maximum, exclusiveMinimum, exclusiveMaximum, not multipleOf
+ * * boolean
+ * * null
+ * * object - properties, additionalProperties, not propertyNames, not dependencies, not patternProperties, not required
+ * * array: only lists, not tuples - items, not minItems, not maxItems, not uniqueItems, not contains
+ * * enum
+ * * format
+ * * date-time
+ * * email
+ * * ip
+ * * uri
+ * As of WordPress 5.0, Core does not support:
+ * * Multiple type: `type: [ 'string', 'integer' ]`
+ * * $ref, allOf, anyOf, oneOf, not, const
+ *
+ * @return array
+ */
+ public function get_schema() {
+ /* translators: %s: get_schema() */
+ _doing_it_wrong( 'WPCOM_REST_API_V2_Field_Controller::get_schema', sprintf( __( "Method '%s' must be overridden.", 'jetpack' ), __METHOD__ ), 'Jetpack 6.8' );
+ }
+
+ /**
+ * @param array $schema
+ * @param string $context REST API Request context
+ * @return bool
+ */
+ private function is_valid_for_context( $schema, $context ) {
+ return empty( $schema['context'] ) || in_array( $context, $schema['context'], true );
+ }
+
+ /**
+ * Removes properties that should not appear in the current
+ * request's context
+ *
+ * $context is a Core REST API Framework request attribute that is
+ * always one of:
+ * * view (what you see on the blog)
+ * * edit (what you see in an editor)
+ * * embed (what you see in, e.g., an oembed)
+ *
+ * Fields (and sub-fields, and sub-sub-...) can be flagged for a
+ * set of specific contexts via the field's schema.
+ *
+ * The Core API will filter out top-level fields with the wrong
+ * context, but will not recurse deeply enough into arrays/objects
+ * to remove all levels of sub-fields with the wrong context.
+ *
+ * This function handles that recursion.
+ *
+ * @param mixed $value
+ * @param array $schema
+ * @param string $context REST API Request context
+ * @return mixed Filtered $value
+ */
+ final public function filter_response_by_context( $value, $schema, $context ) {
+ if ( ! $this->is_valid_for_context( $schema, $context ) ) {
+ // We use this intentionally odd looking WP_Error object
+ // internally only in this recursive function (see below
+ // in the `object` case). It will never be output by the REST API.
+ // If we return this for the top level object, Core
+ // correctly remove the top level object from the response
+ // for us.
+ return new WP_Error( '__wrong-context__' );
+ }
+
+ switch ( $schema['type'] ) {
+ case 'array':
+ if ( ! isset( $schema['items'] ) ) {
+ return $value;
+ }
+
+ // Shortcircuit if we know none of the items are valid for this context.
+ // This would only happen in a strangely written schema.
+ if ( ! $this->is_valid_for_context( $schema['items'], $context ) ) {
+ return array();
+ }
+
+ // Recurse to prune sub-properties of each item.
+ foreach ( $value as $key => $item ) {
+ $value[ $key ] = $this->filter_response_by_context( $item, $schema['items'], $context );
+ }
+
+ return $value;
+ case 'object':
+ if ( ! isset( $schema['properties'] ) ) {
+ return $value;
+ }
+
+ foreach ( $value as $field_name => $field_value ) {
+ if ( isset( $schema['properties'][ $field_name ] ) ) {
+ $field_value = $this->filter_response_by_context( $field_value, $schema['properties'][ $field_name ], $context );
+ if ( is_wp_error( $field_value ) && '__wrong-context__' === $field_value->get_error_code() ) {
+ unset( $value[ $field_name ] );
+ } else {
+ // Respect recursion that pruned sub-properties of each property.
+ $value[ $field_name ] = $field_value;
+ }
+ }
+ }
+
+ return (object) $value;
+ }
+
+ return $value;
+ }
+}
diff --git a/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php b/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php
index ac912269..45d10d14 100644
--- a/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php
+++ b/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php
@@ -746,8 +746,14 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint {
case 'bing':
case 'pinterest':
case 'yandex':
- $grouped_options = $grouped_options_current = (array) get_option( 'verification_services_codes' );
- $grouped_options[$option] = $value;
+ $grouped_options = $grouped_options_current = (array) get_option( 'verification_services_codes' );
+
+ // Extracts the content attribute from the HTML meta tag if needed
+ if ( preg_match( '#.*<meta name="(?:[^"]+)" content="([^"]+)" />.*#i', $value, $matches ) ) {
+ $grouped_options[ $option ] = $matches[1];
+ } else {
+ $grouped_options[ $option ] = $value;
+ }
// If option value was the same, consider it done.
$updated = $grouped_options_current != $grouped_options ? update_option( 'verification_services_codes', $grouped_options ) : true;
diff --git a/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php b/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php
index 68327f51..c8fba69c 100644
--- a/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php
+++ b/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php
@@ -48,6 +48,69 @@ class Jetpack_Core_API_Site_Endpoint {
}
/**
+ * Returns the result of `/sites/%s/posts/%d/related` endpoint call.
+ * Results are not cached and are retrieved in real time.
+ *
+ * @since 6.7.0
+ *
+ * @param int ID of the post to get related posts of
+ *
+ * @return array
+ */
+ public static function get_related_posts( $api_request ) {
+ $params = $api_request->get_params();
+ $post_id = ! empty( $params['post_id'] ) ? absint( $params['post_id'] ) : 0;
+
+ if ( ! $post_id ) {
+ return new WP_Error(
+ 'incorrect_post_id',
+ esc_html__( 'You need to specify a correct ID of a post to return related posts for.', 'jetpack' ),
+ array( 'status' => 400 )
+ );
+ }
+
+ // Make the API request
+ $request = sprintf( '/sites/%d/posts/%d/related', Jetpack_Options::get_option( 'id' ), $post_id );
+ $request_args = array(
+ 'headers' => array(
+ 'Content-Type' => 'application/json',
+ ),
+ 'timeout' => 10,
+ 'method' => 'POST',
+ );
+ $response = Jetpack_Client::wpcom_json_api_request_as_blog( $request, '1.1', $request_args );
+
+ // Bail if there was an error or malformed response
+ if ( is_wp_error( $response ) || ! is_array( $response ) || ! isset( $response['body'] ) ) {
+ return new WP_Error(
+ 'failed_to_fetch_data',
+ esc_html__( 'Unable to fetch the requested data.', 'jetpack' ),
+ array( 'status' => 400 )
+ );
+ }
+
+ // Decode the results
+ $results = json_decode( wp_remote_retrieve_body( $response ), true );
+
+ $related_posts = array();
+ if ( isset( $results['hits'] ) && is_array( $results['hits'] ) ) {
+ $related_posts_ids = array_map( array( 'Jetpack_Core_API_Site_Endpoint', 'get_related_post_id' ), $results['hits'] );
+
+ $related_posts_instance = Jetpack_RelatedPosts::init();
+ foreach ( $related_posts_ids as $related_post_id ) {
+ $related_posts[] = $related_posts_instance->get_related_post_data_for_post( $related_post_id, 0, 0 );
+ }
+ }
+
+ return rest_ensure_response( array(
+ 'code' => 'success',
+ 'message' => esc_html__( 'Related posts retrieved successfully.', 'jetpack' ),
+ 'posts' => $related_posts,
+ )
+ );
+ }
+
+ /**
* Check that the current user has permissions to request information about this site.
*
* @since 5.1.0
@@ -57,4 +120,16 @@ class Jetpack_Core_API_Site_Endpoint {
public static function can_request() {
return current_user_can( 'jetpack_manage_modules' );
}
+
+ /**
+ * Returns the post ID out of a related post entry from the
+ * `/sites/%s/posts/%d/related` WP.com endpoint.
+ *
+ * @since 6.7.0
+ *
+ * @return int
+ */
+ public static function get_related_post_id( $item ) {
+ return $item['fields']['post_id'];
+ }
}
diff --git a/plugins/jetpack/_inc/lib/core-api/load-wpcom-endpoints.php b/plugins/jetpack/_inc/lib/core-api/load-wpcom-endpoints.php
new file mode 100644
index 00000000..2b26f78c
--- /dev/null
+++ b/plugins/jetpack/_inc/lib/core-api/load-wpcom-endpoints.php
@@ -0,0 +1,40 @@
+<?php
+
+/*
+ * Loader for WP REST API endpoints that are synced with WP.com.
+ *
+ * On WP.com see:
+ * - wp-content/mu-plugins/rest-api.php
+ * - wp-content/rest-api-plugins/jetpack-endpoints/
+ */
+
+function wpcom_rest_api_v2_load_plugin_files( $file_pattern ) {
+ $plugins = glob( dirname( __FILE__ ) . '/' . $file_pattern );
+
+ if ( ! is_array( $plugins ) ) {
+ return;
+ }
+
+ foreach ( array_filter( $plugins, 'is_file' ) as $plugin ) {
+ require_once $plugin;
+ }
+}
+
+// API v2 plugins: define a class, then call this function.
+function wpcom_rest_api_v2_load_plugin( $class_name ) {
+ global $wpcom_rest_api_v2_plugins;
+
+ if ( ! isset( $wpcom_rest_api_v2_plugins ) ) {
+ $_GLOBALS['wpcom_rest_api_v2_plugins'] = $wpcom_rest_api_v2_plugins = array();
+ }
+
+ if ( ! isset( $wpcom_rest_api_v2_plugins[ $class_name ] ) ) {
+ $wpcom_rest_api_v2_plugins[ $class_name ] = new $class_name;
+ }
+}
+
+require dirname( __FILE__ ) . '/class-wpcom-rest-field-controller.php';
+
+// Now load the endpoint files.
+wpcom_rest_api_v2_load_plugin_files( 'wpcom-endpoints/*.php' );
+wpcom_rest_api_v2_load_plugin_files( 'wpcom-fields/*.php' );
diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/hello.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/hello.php
new file mode 100644
index 00000000..a05769b2
--- /dev/null
+++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/hello.php
@@ -0,0 +1,22 @@
+<?php
+
+class WPCOM_REST_API_V2_Endpoint_Hello {
+ public function __construct() {
+ add_action( 'rest_api_init', array( $this, 'register_routes' ) );
+ }
+
+ public function register_routes() {
+ register_rest_route( 'wpcom/v2', '/hello', array(
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $this, 'get_data' ),
+ ),
+ ) );
+ }
+
+ public function get_data( $request ) {
+ return array( 'hello' => 'world' );
+ }
+}
+
+wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Hello' );
diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php
new file mode 100644
index 00000000..86019880
--- /dev/null
+++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php
@@ -0,0 +1,117 @@
+<?php
+
+require_once dirname( __FILE__ ) . '/publicize-connections.php';
+
+/**
+ * Publicize: List Connection Test Result Data
+ *
+ * All the same data as the Publicize Connections Endpoint, plus test results.
+ *
+ * @since 6.8
+ */
+class WPCOM_REST_API_V2_Endpoint_List_Publicize_Connection_Test_Results extends WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections {
+ public function __construct() {
+ $this->namespace = 'wpcom/v2';
+ $this->rest_base = 'publicize/connection-test-results';
+
+ add_action( 'rest_api_init', array( $this, 'register_routes' ) );
+ }
+
+ /**
+ * Called automatically on `rest_api_init()`.
+ */
+ public function register_routes() {
+ register_rest_route(
+ $this->namespace,
+ '/' . $this->rest_base,
+ array(
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $this, 'get_items' ),
+ 'permission_callback' => array( $this, 'get_items_permission_check' ),
+ ),
+ 'schema' => array( $this, 'get_public_item_schema' ),
+ )
+ );
+ }
+
+ /**
+ * Adds the test results properties to the Connection schema.
+ *
+ * @return array
+ */
+ public function get_item_schema() {
+ $schema = array(
+ '$schema' => 'http://json-schema.org/draft-04/schema#',
+ 'title' => 'jetpack-publicize-connection-test-results',
+ 'type' => 'object',
+ 'properties' => $this->get_connection_schema_properties() + array(
+ 'test_success' => array(
+ 'description' => __( 'Did the Publicize Connection test pass?', 'jetpack' ),
+ 'type' => 'boolean',
+ ),
+ 'test_message' => array(
+ 'description' => __( 'Publicize Connection success or error message', 'jetpack' ),
+ 'type' => 'string',
+ ),
+ 'can_refresh' => array(
+ 'description' => __( 'Can the current user refresh the Publicize Connection?', 'jetpack' ),
+ 'type' => 'boolean',
+ ),
+ 'refresh_text' => array(
+ 'description' => __( 'Message instructing the user to refresh their Connection to the Publicize Service', 'jetpack' ),
+ 'type' => 'string',
+ ),
+ 'refresh_url' => array(
+ 'description' => __( 'URL for refreshing the Connection to the Publicize Service', 'jetpack' ),
+ 'type' => 'string',
+ 'format' => 'uri',
+ ),
+ ),
+ );
+
+ return $this->add_additional_fields_schema( $schema );
+ }
+
+ /**
+ * @param WP_REST_Request
+ * @see Publicize::get_publicize_conns_test_results()
+ * @return WP_REST_Response suitable for 1-page collection
+ */
+ public function get_items( $request ) {
+ global $publicize;
+
+ $items = $this->get_connections();
+
+ $test_results = $publicize->get_publicize_conns_test_results();
+ $test_results_by_unique_id = array();
+ foreach ( $test_results as $test_result ) {
+ $test_results_by_unique_id[ $test_result['unique_id'] ] = $test_result;
+ }
+
+ $mapping = array(
+ 'test_success' => 'connectionTestPassed',
+ 'test_message' => 'connectionTestMessage',
+ 'can_refresh' => 'userCanRefresh',
+ 'refresh_text' => 'refreshText',
+ 'refresh_url' => 'refreshURL',
+ );
+
+ foreach ( $items as &$item ) {
+ $test_result = $test_results_by_unique_id[ $item['id'] ];
+
+ foreach ( $mapping as $field => $test_result_field ) {
+ $item[ $field ] = $test_result[ $test_result_field ];
+ }
+ }
+
+ $response = rest_ensure_response( $items );
+
+ $response->header( 'X-WP-Total', count( $items ) );
+ $response->header( 'X-WP-TotalPages', 1 );
+
+ return $response;
+ }
+}
+
+wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_List_Publicize_Connection_Test_Results' );
diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connections.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connections.php
new file mode 100644
index 00000000..f7e9b351
--- /dev/null
+++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connections.php
@@ -0,0 +1,186 @@
+<?php
+
+/**
+ * Publicize: List Connections
+ *
+ * [
+ * { # Connnection Object. See schema for more detail.
+ * id: (string) Connection unique_id
+ * service_name: (string) Service slug
+ * display_name: (string) User name/display name of user/connection on Service
+ * global: (boolean) Is the Connection available to all users of the site?
+ * },
+ * ...
+ * ]
+ *
+ * @since 6.8
+ */
+class WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections extends WP_REST_Controller {
+ /**
+ * Flag to help WordPress.com decide where it should look for
+ * Publicize data. Ignored for direct requests to Jetpack sites.
+ *
+ * @var bool $wpcom_is_wpcom_only_endpoint
+ */
+ public $wpcom_is_wpcom_only_endpoint = true;
+
+ public function __construct() {
+ $this->namespace = 'wpcom/v2';
+ $this->rest_base = 'publicize/connections';
+
+ add_action( 'rest_api_init', array( $this, 'register_routes' ) );
+ }
+
+ /**
+ * Called automatically on `rest_api_init()`.
+ */
+ public function register_routes() {
+ register_rest_route(
+ $this->namespace,
+ '/' . $this->rest_base,
+ array(
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $this, 'get_items' ),
+ 'permission_callback' => array( $this, 'get_items_permission_check' ),
+ ),
+ 'schema' => array( $this, 'get_public_item_schema' ),
+ )
+ );
+ }
+
+ /**
+ * Helper for generating schema. Used by this endpoint and by the
+ * Connection Test Result endpoint.
+ *
+ * @internal
+ * @return array
+ */
+ protected function get_connection_schema_properties() {
+ return array(
+ 'id' => array(
+ 'description' => __( 'Unique identifier for the Publicize Connection', 'jetpack' ),
+ 'type' => 'string',
+ ),
+ 'service_name' => array(
+ 'description' => __( 'Alphanumeric identifier for the Publicize Service', 'jetpack' ),
+ 'type' => 'string',
+ ),
+ 'display_name' => array(
+ 'description' => __( 'Username of the connected account', 'jetpack' ),
+ 'type' => 'string',
+ ),
+ 'global' => array(
+ 'description' => __( 'Is this connection available to all users?', 'jetpack' ),
+ 'type' => 'boolean',
+ ),
+ );
+ }
+
+ /**
+ * @return array
+ */
+ public function get_item_schema() {
+ $schema = array(
+ '$schema' => 'http://json-schema.org/draft-04/schema#',
+ 'title' => 'jetpack-publicize-connection',
+ 'type' => 'object',
+ 'properties' => $this->get_connection_schema_properties(),
+ );
+
+ return $this->add_additional_fields_schema( $schema );
+ }
+
+ /**
+ * Helper for retrieving Connections. Used by this endpoint and by
+ * the Connection Test Result endpoint.
+ *
+ * @internal
+ * @return array
+ */
+ protected function get_connections() {
+ global $publicize;
+
+ $items = array();
+
+ foreach ( (array) $publicize->get_services( 'connected' ) as $service_name => $connections ) {
+ foreach ( $connections as $connection ) {
+ $connection_meta = $publicize->get_connection_meta( $connection );
+ $connection_data = $connection_meta['connection_data'];
+
+ $items[] = array(
+ 'id' => (string) $publicize->get_connection_unique_id( $connection ),
+ 'service_name' => $service_name,
+ 'display_name' => $publicize->get_display_name( $service_name, $connection ),
+ // We expect an integer, but do loose comparison below in case some other type is stored
+ 'global' => 0 == $connection_data['user_id'],
+ );
+ }
+ }
+
+ return $items;
+ }
+
+ /**
+ * @param WP_REST_Request $request
+ * @return WP_REST_Response suitable for 1-page collection
+ */
+ public function get_items( $request ) {
+ $items = array();
+
+ foreach ( $this->get_connections() as $item ) {
+ $items[] = $this->prepare_item_for_response( $item, $request );
+ }
+
+ $response = rest_ensure_response( $items );
+ $response->header( 'X-WP-Total', count( $items ) );
+ $response->header( 'X-WP-TotalPages', 1 );
+
+ return $response;
+ }
+
+ /**
+ * Filters out data based on ?_fields= request parameter
+ *
+ * @param array $connection
+ * @param WP_REST_Request $request
+ * @return array filtered $connection
+ */
+ public function prepare_item_for_response( $connection, $request ) {
+ if ( ! is_callable( array( $this, 'get_fields_for_response' ) ) ) {
+ return $connection;
+ }
+
+ $fields = $this->get_fields_for_response( $request );
+
+ $response_data = array();
+ foreach ( $connection as $field => $value ) {
+ if ( in_array( $field, $fields, true ) ) {
+ $response_data[ $field ] = $value;
+ }
+ }
+
+ return $response_data;
+ }
+
+ /**
+ * Verify that user can access Publicize data
+ *
+ * @return true|WP_Error
+ */
+ public function get_items_permission_check() {
+ global $publicize;
+
+ if ( $publicize->current_user_can_access_publicize_data() ) {
+ return true;
+ }
+
+ return new WP_Error(
+ 'invalid_user_permission_publicize',
+ __( 'Sorry, you are not allowed to access Publicize data on this site.', 'jetpack' ),
+ array( 'status' => rest_authorization_required_code() )
+ );
+ }
+}
+
+wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections' );
diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-services.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-services.php
new file mode 100644
index 00000000..fb418263
--- /dev/null
+++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-services.php
@@ -0,0 +1,159 @@
+<?php
+
+/**
+ * Publicize: List Publicize Services
+ *
+ * [
+ * { # Service Object. See schema for more detail.
+ * name: (string) Service slug
+ * label: (string) Human readable label for the Service
+ * url: (string) Connect URL
+ * },
+ * ...
+ * ]
+ *
+ * @since 6.8
+ */
+class WPCOM_REST_API_V2_Endpoint_List_Publicize_Services extends WP_REST_Controller {
+ /**
+ * Flag to help WordPress.com decide where it should look for
+ * Publicize data. Ignored for direct requests to Jetpack sites.
+ *
+ * @var bool $wpcom_is_wpcom_only_endpoint
+ */
+ public $wpcom_is_wpcom_only_endpoint = true;
+
+ public function __construct() {
+ $this->namespace = 'wpcom/v2';
+ $this->rest_base = 'publicize/services';
+
+ add_action( 'rest_api_init', array( $this, 'register_routes' ) );
+ }
+
+ /**
+ * Called automatically on `rest_api_init()`.
+ */
+ public function register_routes() {
+ register_rest_route(
+ $this->namespace,
+ '/' . $this->rest_base,
+ array(
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $this, 'get_items' ),
+ 'permission_callback' => array( $this, 'get_items_permission_check' ),
+ ),
+ 'schema' => array( $this, 'get_public_item_schema' ),
+ )
+ );
+ }
+
+ /**
+ * @return array
+ */
+ public function get_item_schema() {
+ $schema = array(
+ '$schema' => 'http://json-schema.org/draft-04/schema#',
+ 'title' => 'jetpack-publicize-service',
+ 'type' => 'object',
+ 'properties' => array(
+ 'name' => array(
+ 'description' => __( 'Alphanumeric identifier for the Publicize Service', 'jetpack' ),
+ 'type' => 'string',
+ ),
+ 'label' => array(
+ 'description' => __( 'Human readable label for the Publicize Service', 'jetpack' ),
+ 'type' => 'string',
+ ),
+ 'url' => array(
+ 'description' => __( 'The URL used to connect to the Publicize Service', 'jetpack' ),
+ 'type' => 'string',
+ 'format' => 'uri',
+ ),
+ ),
+ );
+
+ return $this->add_additional_fields_schema( $schema );
+ }
+
+ /**
+ * Retrieves available Publicize Services.
+ *
+ * @see Publicize::get_available_service_data()
+ *
+ * @param WP_REST_Request $request
+ * @return WP_REST_Response suitable for 1-page collection
+ */
+ public function get_items( $request ) {
+ global $publicize;
+ /**
+ * We need this because Publicize::get_available_service_data() uses `Jetpack_Keyring_Service_Helper`
+ * and `Jetpack_Keyring_Service_Helper` relies on `menu_page_url()`.
+ *
+ * We also need add_submenu_page(), as the URLs for connecting each service
+ * rely on the `sharing` menu subpage being present.
+ */
+ include_once ABSPATH . 'wp-admin/includes/plugin.php';
+
+ // The `sharing` submenu page must exist for service connect URLs to be correct.
+ add_submenu_page( 'options-general.php', '', '', 'manage_options', 'sharing', '__return_empty_string' );
+
+ $services_data = $publicize->get_available_service_data();
+
+ $services = array();
+ foreach ( $services_data as $service_data ) {
+ $services[] = $this->prepare_item_for_response( $service_data, $request );
+ }
+
+ $response = rest_ensure_response( $services );
+ $response->header( 'X-WP-Total', count( $services ) );
+ $response->header( 'X-WP-TotalPages', 1 );
+
+ return $response;
+ }
+
+ /**
+ * Filters out data based on ?_fields= request parameter
+ *
+ * @param array $service
+ * @param WP_REST_Request $request
+ * @return array filtered $service
+ */
+ public function prepare_item_for_response( $service, $request ) {
+ if ( ! is_callable( array( $this, 'get_fields_for_response' ) ) ) {
+ return $service;
+ }
+
+ $fields = $this->get_fields_for_response( $request );
+
+ $response_data = array();
+ foreach ( $service as $field => $value ) {
+ if ( in_array( $field, $fields, true ) ) {
+ $response_data[ $field ] = $value;
+ }
+ }
+
+ return $response_data;
+ }
+
+ /**
+ * Verify that user can access Publicize data
+ *
+ * @return true|WP_Error
+ */
+ public function get_items_permission_check() {
+ global $publicize;
+
+ if ( $publicize->current_user_can_access_publicize_data() ) {
+ return true;
+ }
+
+ return new WP_Error(
+ 'invalid_user_permission_publicize',
+ __( 'Sorry, you are not allowed to access Publicize data on this site.', 'jetpack' ),
+ array( 'status' => rest_authorization_required_code() )
+ );
+ }
+}
+
+wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_List_Publicize_Services' );
diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/sites-posts-featured-media-url.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/sites-posts-featured-media-url.php
new file mode 100644
index 00000000..4c34161c
--- /dev/null
+++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/sites-posts-featured-media-url.php
@@ -0,0 +1,37 @@
+<?php
+
+/*
+ * Plugin Name: WPCOM Add Featured Media URL
+ *
+ * Adds `jetpack_featured_media_url` to post responses
+ */
+
+class WPCOM_REST_API_V2_Sites_Posts_Add_Featured_Media_URL {
+ function __construct() {
+ add_action( 'rest_api_init', array( $this, 'add_featured_media_url' ) );
+ }
+
+ function add_featured_media_url() {
+ register_rest_field( 'post', 'jetpack_featured_media_url',
+ array(
+ 'get_callback' => array( $this, 'get_featured_media_url' ),
+ 'update_callback' => null,
+ 'schema' => null,
+ )
+ );
+ }
+
+ function get_featured_media_url( $object, $field_name, $request ) {
+ $featured_media_url = '';
+ $image_attributes = wp_get_attachment_image_src(
+ get_post_thumbnail_id( $object['id'] ),
+ 'full'
+ );
+ if ( is_array( $image_attributes ) && isset( $image_attributes[0] ) ) {
+ $featured_media_url = (string) $image_attributes[0];
+ }
+ return $featured_media_url;
+ }
+}
+
+wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Sites_Posts_Add_Featured_Media_URL' );
diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-fields/post-fields-publicize-connections.php b/plugins/jetpack/_inc/lib/core-api/wpcom-fields/post-fields-publicize-connections.php
new file mode 100644
index 00000000..1aa8ec86
--- /dev/null
+++ b/plugins/jetpack/_inc/lib/core-api/wpcom-fields/post-fields-publicize-connections.php
@@ -0,0 +1,337 @@
+<?php
+
+/**
+ * Add per-post Publicize Connection data.
+ *
+ * { # Post Object
+ * ...
+ * jetpack_publicize_connections: { # Defined below in this file. See schema for more detail.
+ * id: (string) Connection unique_id
+ * service_name: (string) Service slug
+ * display_name: (string) User name/display name of user/connection on Service
+ * enabled: (boolean) Is this connection slated to be shared to? context=edit only
+ * done: (boolean) Is this post (or connection) done sharing? context=edit only
+ * toggleable: (boolean) Can the current user change the `enabled` setting for this Connection+Post? context=edit only
+ * }
+ * ...
+ * meta: { # Not defined in this file. Handled in modules/publicize/publicize.php via `register_meta()`
+ * jetpack_publicize_message: (string) The message to use instead of the post's title when sharing.
+ * }
+ * ...
+ * }
+ *
+ * @since 6.8.0
+ */
+class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_V2_Field_Controller {
+ protected $object_type = 'post';
+ protected $field_name = 'jetpack_publicize_connections';
+
+ public $memoized_updates = array();
+
+ /**
+ * Registers the jetpack_publicize_connections field. Called
+ * automatically on `rest_api_init()`.
+ */
+ public function register_fields() {
+ $this->object_type = get_post_types_by_support( 'publicize' );
+
+ foreach ( $this->object_type as $post_type ) {
+ // Adds meta support for those post types that don't already have it.
+ // Only runs during REST API requests, so it doesn't impact UI.
+ if ( ! post_type_supports( $post_type, 'custom-fields' ) ) {
+ add_post_type_support( $post_type, 'custom-fields' );
+ }
+
+ add_filter( 'rest_pre_insert_' . $post_type, array( $this, 'rest_pre_insert' ), 10, 2 );
+ add_action( 'rest_insert_' . $post_type, array( $this, 'rest_insert' ), 10, 3 );
+ }
+
+ parent::register_fields();
+ }
+
+ /**
+ * Defines data structure and what elements are visible in which contexts
+ */
+ public function get_schema() {
+ return array(
+ '$schema' => 'http://json-schema.org/draft-04/schema#',
+ 'title' => 'jetpack-publicize-post-connections',
+ 'type' => 'array',
+ 'context' => array( 'view', 'edit' ),
+ 'items' => $this->post_connection_schema(),
+ 'default' => array(),
+ );
+ }
+
+ private function post_connection_schema() {
+ return array(
+ '$schema' => 'http://json-schema.org/draft-04/schema#',
+ 'title' => 'jetpack-publicize-post-connection',
+ 'type' => 'object',
+ 'properties' => array(
+ 'id' => array(
+ 'description' => __( 'Unique identifier for the Publicize Connection', 'jetpack' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'service_name' => array(
+ 'description' => __( 'Alphanumeric identifier for the Publicize Service', 'jetpack' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'display_name' => array(
+ 'description' => __( 'Username of the connected account', 'jetpack' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'enabled' => array(
+ 'description' => __( 'Whether to share to this connection', 'jetpack' ),
+ 'type' => 'boolean',
+ 'context' => array( 'edit' ),
+ ),
+ 'done' => array(
+ 'description' => __( 'Whether Publicize has already finished sharing for this post', 'jetpack' ),
+ 'type' => 'boolean',
+ 'context' => array( 'edit' ),
+ 'readonly' => true,
+ ),
+ 'toggleable' => array(
+ 'description' => __( 'Whether `enable` can be changed for this post/connection', 'jetpack' ),
+ 'type' => 'boolean',
+ 'context' => array( 'edit' ),
+ 'readonly' => true,
+ ),
+ ),
+ );
+ }
+
+ /**
+ * @param int $post_id
+ * @return true|WP_Error
+ */
+ function permission_check( $post_id ) {
+ global $publicize;
+
+ if ( $publicize->current_user_can_access_publicize_data( $post_id ) ) {
+ return true;
+ }
+
+ return new WP_Error(
+ 'invalid_user_permission_publicize',
+ __( 'Sorry, you are not allowed to access Publicize data for this post.', 'jetpack' ),
+ array( 'status' => rest_authorization_required_code() )
+ );
+ }
+
+ /**
+ * Getter permission check
+ *
+ * @param array $post_array Response data from Post Endpoint
+ * @return true|WP_Error
+ */
+ function get_permission_check( $post_array, $request ) {
+ return $this->permission_check( isset( $post_array['id'] ) ? $post_array['id'] : 0 );
+
+ }
+
+ /**
+ * Setter permission check
+ *
+ * @param WP_Post $post
+ * @return true|WP_Error
+ */
+ public function update_permission_check( $value, $post, $request ) {
+ return $this->permission_check( isset( $post->ID ) ? $post->ID : 0 );
+ }
+
+ /**
+ * Getter: Retrieve current list of connected social accounts for a given post.
+ *
+ * @see Publicize::get_filtered_connection_data()
+ *
+ * @param array $post_array Response from Post Endpoint
+ * @param WP_REST_Request
+ *
+ * @return array List of connections
+ */
+ public function get( $post_array, $request ) {
+ global $publicize;
+
+ $schema = $this->post_connection_schema();
+ $properties = array_keys( $schema['properties'] );
+
+ $connections = $publicize->get_filtered_connection_data( $post_array['id'] );
+
+ $output_connections = array();
+ foreach ( $connections as $connection ) {
+ $output_connection = array();
+ foreach ( $properties as $property ) {
+ if ( isset( $connection[ $property ] ) ) {
+ $output_connection[ $property ] = $connection[ $property ];
+ }
+ }
+
+ $output_connection['id'] = (string) $connection['unique_id'];
+
+ $output_connections[] = $output_connection;
+ }
+
+ return $output_connections;
+ }
+
+ /**
+ * Prior to updating the post, first calculate which Services to
+ * Publicize to and which to skip.
+ *
+ * @param object $post Post data to insert/update.
+ * @param WP_REST_Request $request
+ * @return Filtered $post
+ */
+ public function rest_pre_insert( $post, $request ) {
+ if ( ! isset( $request['jetpack_publicize_connections'] ) ) {
+ return $post;
+ }
+
+ $permission_check = $this->update_permission_check( $request['jetpack_publicize_connections'], $post, $request );
+
+ if ( is_wp_error( $permission_check ) ) {
+ return $permission_check;
+ }
+
+ // memoize
+ $this->get_meta_to_update( $request['jetpack_publicize_connections'], isset( $post->ID ) ? $post->ID : 0 );
+
+ return $post;
+ }
+
+ /**
+ * After creating a new post, update our cached data to reflect
+ * the new post ID.
+ *
+ * @param WP_Post $post
+ * @param WP_REST_Request $request
+ * @param bool $is_new
+ */
+ public function rest_insert( $post, $request, $is_new ) {
+ if ( ! $is_new ) {
+ // An existing post was edited - no need to update
+ // our cache - we started out knowing the correct
+ // post ID.
+ return;
+ }
+
+ if ( ! isset( $request['jetpack_publicize_connections'] ) ) {
+ return;
+ }
+
+ if ( ! isset( $this->memoized_updates[0] ) ) {
+ return;
+ }
+
+ $this->memoized_updates[ $post->ID ] = $this->memoized_updates[0];
+ unset( $this->memoized_updates[0] );
+ }
+
+ protected function get_meta_to_update( $requested_connections, $post_id = 0 ) {
+ global $publicize;
+
+ if ( isset( $this->memoized_updates[$post_id] ) ) {
+ return $this->memoized_updates[$post_id];
+ }
+
+ $available_connections = $publicize->get_filtered_connection_data( $post_id );
+
+ $changed_connections = array();
+
+ // Build lookup mappings
+ $available_connections_by_unique_id = array();
+ $available_connections_by_service_name = array();
+ foreach ( $available_connections as $available_connection ) {
+ $available_connections_by_unique_id[ $available_connection['unique_id'] ] = $available_connection;
+
+ if ( ! isset( $available_connections_by_service_name[ $available_connection['service_name'] ] ) ) {
+ $available_connections_by_service_name[ $available_connection['service_name'] ] = array();
+ }
+ $available_connections_by_service_name[ $available_connection['service_name'] ][] = $available_connection;
+ }
+
+ // Handle { service_name: $service_name, enabled: (bool) }
+ foreach ( $requested_connections as $requested_connection ) {
+ if ( ! isset( $requested_connection['service_name'] ) ) {
+ continue;
+ }
+
+ if ( ! isset( $available_connections_by_service_name[ $requested_connection['service_name'] ] ) ) {
+ continue;
+ }
+
+ foreach ( $available_connections_by_service_name[ $requested_connection['service_name'] ] as $available_connection ) {
+ $changed_connections[ $available_connection['unique_id'] ] = $requested_connection['enabled'];
+ }
+ }
+
+ // Handle { id: $id, enabled: (bool) }
+ // These override the service_name settings
+ foreach ( $requested_connections as $requested_connection ) {
+ if ( ! isset( $requested_connection['id'] ) ) {
+ continue;
+ }
+
+ if ( ! isset( $available_connections_by_unique_id[ $requested_connection['id'] ] ) ) {
+ continue;
+ }
+
+ $changed_connections[ $requested_connection['id'] ] = $requested_connection['enabled'];
+ }
+
+ // Set all changed connections to their new value
+ foreach ( $changed_connections as $unique_id => $enabled ) {
+ $connection = $available_connections_by_unique_id[ $unique_id ];
+
+ if ( $connection['done'] || ! $connection['toggleable'] ) {
+ continue;
+ }
+
+ $available_connections_by_unique_id[ $unique_id ]['enabled'] = $enabled;
+ }
+
+ $meta_to_update = array();
+ // For all connections, ensure correct post_meta
+ foreach ( $available_connections_by_unique_id as $unique_id => $available_connection ) {
+ if ( $available_connection['enabled'] ) {
+ $meta_to_update[$publicize->POST_SKIP . $unique_id] = null;
+ } else {
+ $meta_to_update[$publicize->POST_SKIP . $unique_id] = 1;
+ }
+ }
+
+ $this->memoized_updates[$post_id] = $meta_to_update;
+
+ return $meta_to_update;
+ }
+
+ /**
+ * Update the connections slated to be shared to.
+ *
+ * @param array $requested_connections
+ * Items are either `{ id: (string) }` or `{ service_name: (string) }`
+ * @param WP_Post $post
+ * @param WP_REST_Request
+ */
+ public function update( $requested_connections, $post, $request ) {
+ foreach ( $this->get_meta_to_update( $requested_connections, $post->ID ) as $meta_key => $meta_value ) {
+ if ( is_null( $meta_value ) ) {
+ delete_post_meta( $post->ID, $meta_key );
+ } else {
+ update_post_meta( $post->ID, $meta_key, $meta_value );
+ }
+ }
+ }
+}
+
+if ( Jetpack::is_module_active( 'publicize' ) ) {
+ wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Post_Publicize_Connections_Field' );
+} \ No newline at end of file
diff --git a/plugins/jetpack/_inc/lib/tonesque.php b/plugins/jetpack/_inc/lib/tonesque.php
index bb4c7477..17158e3d 100644
--- a/plugins/jetpack/_inc/lib/tonesque.php
+++ b/plugins/jetpack/_inc/lib/tonesque.php
@@ -86,7 +86,7 @@ class Tonesque {
*
* Construct object from image.
*
- * @param optional $type (hex, rgb, hsl)
+ * @param optional $type (hex, rgb, hsv)
* @return color as a string formatted as $type
*
*/