initial
This commit is contained in:
+382
@@ -0,0 +1,382 @@
|
||||
<?php
|
||||
/**
|
||||
* Publicize Connections class.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize;
|
||||
|
||||
use Automattic\Jetpack\Connection;
|
||||
use Automattic\Jetpack\Publicize\REST_API\Proxy_Requests;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
|
||||
/**
|
||||
* Publicize Connections class.
|
||||
*/
|
||||
class Connections {
|
||||
|
||||
const CONNECTIONS_TRANSIENT = 'jetpack_social_connections_list';
|
||||
|
||||
/**
|
||||
* Get all connections.
|
||||
*
|
||||
* @param array $args Arguments
|
||||
* - 'ignore_cache': bool Whether to ignore the cache and fetch the connections from the API.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_all( $args = array() ) {
|
||||
|
||||
if ( Publicize_Utils::is_wpcom() ) {
|
||||
$connections = self::wpcom_get_connections( array( 'context' => 'blog' ) );
|
||||
} else {
|
||||
|
||||
$ignore_cache = $args['ignore_cache'] ?? false;
|
||||
|
||||
$connections = get_transient( self::CONNECTIONS_TRANSIENT );
|
||||
|
||||
if ( $ignore_cache || false === $connections ) {
|
||||
$connections = self::fetch_and_cache_connections();
|
||||
}
|
||||
}
|
||||
|
||||
return $connections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a connection by connection_id.
|
||||
*
|
||||
* @param string $connection_id Connection ID.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public static function get_by_id( $connection_id ) {
|
||||
|
||||
$connections = self::get_all();
|
||||
|
||||
foreach ( $connections as $connection ) {
|
||||
if ( $connection['connection_id'] === $connection_id ) {
|
||||
return $connection;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all connections for the current user.
|
||||
*
|
||||
* @param array $args Arguments. Same as self::get_all().
|
||||
*
|
||||
* @see Automattic\Jetpack\Publicize\Connections::get_all()
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_all_for_user( $args = array() ) {
|
||||
$connections = self::get_all( $args );
|
||||
|
||||
$connections_for_user = array();
|
||||
|
||||
foreach ( $connections as $connection ) {
|
||||
|
||||
if ( self::is_shared( $connection ) || self::user_owns_connection( $connection ) ) {
|
||||
$connections_for_user[] = $connection;
|
||||
}
|
||||
}
|
||||
|
||||
return $connections_for_user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a connection is shared.
|
||||
*
|
||||
* @param array $connection The connection.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_shared( $connection ) {
|
||||
return ! empty( $connection['shared'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the current user owns a connection.
|
||||
*
|
||||
* @param array $connection The connection.
|
||||
* @param int $user_id The user ID. Defaults to the current user.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function user_owns_connection( $connection, $user_id = null ) {
|
||||
if ( Publicize_Utils::is_wpcom() ) {
|
||||
$wpcom_user_id = get_current_user_id();
|
||||
} else {
|
||||
|
||||
$wpcom_user_data = ( new Connection\Manager() )->get_connected_user_data( $user_id );
|
||||
|
||||
$wpcom_user_id = ! empty( $wpcom_user_data['ID'] ) ? $wpcom_user_data['ID'] : null;
|
||||
}
|
||||
|
||||
return $wpcom_user_id && $connection['wpcom_user_id'] === $wpcom_user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch connections from the REST API and cache them.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function fetch_and_cache_connections() {
|
||||
$connections = self::fetch_site_connections();
|
||||
|
||||
if ( is_wp_error( $connections ) ) {
|
||||
// @todo log error.
|
||||
return array();
|
||||
}
|
||||
|
||||
if ( is_array( $connections ) ) {
|
||||
if ( ! set_transient( self::CONNECTIONS_TRANSIENT, $connections, HOUR_IN_SECONDS * 4 ) ) {
|
||||
// If the transient has beeen set in another request, the call to set_transient can fail.
|
||||
// If so, we can delete the transient and try again.
|
||||
self::clear_cache();
|
||||
|
||||
set_transient( self::CONNECTIONS_TRANSIENT, $connections, HOUR_IN_SECONDS * 4 );
|
||||
}
|
||||
}
|
||||
|
||||
return $connections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch connections for the site from WPCOM REST API.
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function fetch_site_connections() {
|
||||
$proxy = new Proxy_Requests( 'publicize/connections' );
|
||||
|
||||
$request = new WP_REST_Request( 'GET' );
|
||||
|
||||
return $proxy->proxy_request_to_wpcom_as_blog( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all connections. Meant to be called directly only on WPCOM.
|
||||
*
|
||||
* @param array $args Arguments
|
||||
* - 'test_connections': bool Whether to run connection tests.
|
||||
* - 'context': enum('blog', 'user') Whether to include connections for the current blog or user.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function wpcom_get_connections( $args = array() ) {
|
||||
// Ensure that we are on WPCOM.
|
||||
Publicize_Utils::assert_is_wpcom( __METHOD__ );
|
||||
|
||||
/**
|
||||
* Publicize instance.
|
||||
*/
|
||||
global $publicize;
|
||||
|
||||
$items = array();
|
||||
|
||||
$run_tests = $args['test_connections'] ?? false;
|
||||
|
||||
$test_results = $run_tests ? self::get_test_status() : array();
|
||||
|
||||
$service_connections = $publicize->get_all_connections_for_blog_id( get_current_blog_id() );
|
||||
|
||||
$context = $args['context'] ?? 'user';
|
||||
|
||||
foreach ( $service_connections as $service_name => $connections ) {
|
||||
foreach ( $connections as $connection ) {
|
||||
$connection_id = $publicize->get_connection_id( $connection );
|
||||
|
||||
$item = self::wpcom_prepare_connection_data( $connection, $service_name );
|
||||
|
||||
$item['status'] = $test_results[ $connection_id ] ?? null;
|
||||
|
||||
// For blog context, return all connections.
|
||||
// Otherwise, return only connections owned by the user and the shared ones.
|
||||
if ( 'blog' === $context || $item['shared'] || self::user_owns_connection( $item ) ) {
|
||||
$items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out data based on ?_fields= request parameter
|
||||
*
|
||||
* @param mixed $connection Connection to prepare.
|
||||
* @param string $service_name Service name.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function wpcom_prepare_connection_data( $connection, $service_name ) {
|
||||
// Ensure that we are on WPCOM.
|
||||
Publicize_Utils::assert_is_wpcom( __METHOD__ );
|
||||
|
||||
/**
|
||||
* Publicize instance.
|
||||
*/
|
||||
global $publicize;
|
||||
|
||||
$connection_id = $publicize->get_connection_id( $connection );
|
||||
|
||||
$connection_meta = $publicize->get_connection_meta( $connection );
|
||||
$connection_data = $connection_meta['connection_data'];
|
||||
|
||||
$row_meta = $connection_data['meta'] ?? array();
|
||||
|
||||
return array(
|
||||
'connection_id' => (string) $connection_id,
|
||||
'display_name' => (string) $publicize->get_display_name( $service_name, $connection ),
|
||||
'external_handle' => (string) $publicize->get_external_handle( $service_name, $connection ),
|
||||
'external_id' => $connection_meta['external_id'] ?? '',
|
||||
'profile_link' => (string) $publicize->get_profile_link( $service_name, $connection ),
|
||||
'profile_picture' => (string) $publicize->get_profile_picture( $connection ),
|
||||
'service_label' => (string) Publicize::get_service_label( $service_name ),
|
||||
'service_name' => $service_name,
|
||||
'shared' => ! $connection_data['user_id'],
|
||||
'template' => (string) ( $row_meta['template'] ?? '' ),
|
||||
'wpcom_user_id' => (int) $connection_data['user_id'],
|
||||
|
||||
// Deprecated fields.
|
||||
'id' => (string) $publicize->get_connection_unique_id( $connection ),
|
||||
'username' => $publicize->get_username( $service_name, $connection ),
|
||||
'profile_display_name' => ! empty( $connection_meta['profile_display_name'] ) ? $connection_meta['profile_display_name'] : '',
|
||||
// phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- We expect an integer, but do loose comparison below in case some other type is stored.
|
||||
'global' => 0 == $connection_data['user_id'],
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a connection. Meant to be called directly only on WPCOM.
|
||||
*
|
||||
* @param mixed $input Input data.
|
||||
*
|
||||
* @return string|WP_Error Connection ID or WP_Error.
|
||||
*/
|
||||
public static function wpcom_create_connection( $input ) {
|
||||
// Ensure that we are on WPCOM.
|
||||
Publicize_Utils::assert_is_wpcom( __METHOD__ );
|
||||
|
||||
require_lib( 'social-connections-rest-helper' );
|
||||
|
||||
$connections_helper = \Social_Connections_Rest_Helper::init();
|
||||
|
||||
$result = $connections_helper->create_publicize_connection( $input );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ( ! isset( $result['ID'] ) ) {
|
||||
return new WP_Error(
|
||||
'wpcom_connection_creation_failed',
|
||||
__( 'Something went wrong while creating a connection.', 'jetpack-publicize-pkg' )
|
||||
);
|
||||
}
|
||||
|
||||
return (string) $result['ID'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a connection. Meant to be called directly only on WPCOM.
|
||||
*
|
||||
* @param string $connection_id Connection ID.
|
||||
* @param mixed $input Input data.
|
||||
*
|
||||
* @return string|WP_Error Connection ID or WP_Error.
|
||||
*/
|
||||
public static function wpcom_update_connection( $connection_id, $input ) {
|
||||
// Ensure that we are on WPCOM.
|
||||
Publicize_Utils::assert_is_wpcom( __METHOD__ );
|
||||
|
||||
require_lib( 'social-connections-rest-helper' );
|
||||
$connections_helper = \Social_Connections_Rest_Helper::init();
|
||||
|
||||
$result = $connections_helper->update_connection( $connection_id, $input );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ( ! $result ) {
|
||||
return new WP_Error(
|
||||
'wpcom_connection_updation_failed',
|
||||
__( 'Something went wrong while updating the connection.', 'jetpack-publicize-pkg' )
|
||||
);
|
||||
}
|
||||
|
||||
return (string) $connection_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a connection. Meant to be called directly only on WPCOM.
|
||||
*
|
||||
* @param string $connection_id Connection ID.
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public static function wpcom_delete_connection( $connection_id ) {
|
||||
// Ensure that we are on WPCOM.
|
||||
Publicize_Utils::assert_is_wpcom( __METHOD__ );
|
||||
|
||||
require_lib( 'social-connections-rest-helper' );
|
||||
$connections_helper = \Social_Connections_Rest_Helper::init();
|
||||
|
||||
$result = $connections_helper->delete_publicize_connection( $connection_id );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ( ! $result ) {
|
||||
return new WP_Error(
|
||||
'wpcom_connection_deletion_failed',
|
||||
__( 'Something went wrong while deleting the connection.', 'jetpack-publicize-pkg' )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the connections test status.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_test_status() {
|
||||
/**
|
||||
* Publicize instance.
|
||||
*
|
||||
* @var \Automattic\Jetpack\Publicize\Publicize $publicize
|
||||
*/
|
||||
global $publicize;
|
||||
|
||||
$test_results = $publicize->get_publicize_conns_test_results();
|
||||
|
||||
$test_results_map = array();
|
||||
|
||||
foreach ( $test_results as $test_result ) {
|
||||
$result = $test_result['connectionTestPassed'];
|
||||
if ( 'must_reauth' !== $result ) {
|
||||
$result = $test_result['connectionTestPassed'] ? 'ok' : 'broken';
|
||||
}
|
||||
$test_results_map[ $test_result['connectionID'] ] = $result;
|
||||
}
|
||||
|
||||
return $test_results_map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the connections cache.
|
||||
*/
|
||||
public static function clear_cache() {
|
||||
delete_transient( self::CONNECTIONS_TRANSIENT );
|
||||
}
|
||||
}
|
||||
+432
@@ -0,0 +1,432 @@
|
||||
<?php
|
||||
/**
|
||||
* Focal point crop helpers.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize;
|
||||
|
||||
use Automattic\Jetpack\Image_CDN\Image_CDN;
|
||||
use Automattic\Jetpack\Image_CDN\Image_CDN_Core;
|
||||
use Automattic\Jetpack\Status;
|
||||
|
||||
/**
|
||||
* Shared focal point crop helpers for Jetpack Social images.
|
||||
*/
|
||||
class Focal_Point {
|
||||
|
||||
/**
|
||||
* Target Open Graph image width.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const OG_IMAGE_WIDTH = 1200;
|
||||
|
||||
/**
|
||||
* Target Open Graph image height.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const OG_IMAGE_HEIGHT = 630;
|
||||
|
||||
/**
|
||||
* Get the stored focal point for an image.
|
||||
*
|
||||
* @param int $attachment_id Attachment ID.
|
||||
* @return array|null {
|
||||
* Focal point, or null when not set or invalid.
|
||||
*
|
||||
* @type float $x X axis, 0-1.
|
||||
* @type float $y Y axis, 0-1.
|
||||
* }
|
||||
*/
|
||||
public static function get_for_image( $attachment_id ) {
|
||||
$attachment_id = absint( $attachment_id );
|
||||
|
||||
if ( ! $attachment_id ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$focal_point = get_metadata_raw( 'post', $attachment_id, Publicize_Base::ATTACHMENT_IMAGE_FOCAL_POINT, true );
|
||||
|
||||
if ( ! self::is_valid_focal_point( $focal_point ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( self::is_default_focal_point( $focal_point ) && ! self::has_stored_focal_point_meta( $attachment_id ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array(
|
||||
'x' => (float) $focal_point['x'],
|
||||
'y' => (float) $focal_point['y'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a focal-point cropped image for an attachment.
|
||||
*
|
||||
* @param int $attachment_id Attachment ID.
|
||||
* @param int $target_width Target width.
|
||||
* @param int $target_height Target height.
|
||||
* @return array|null {
|
||||
* Image data, or null when a cropped image cannot be generated.
|
||||
*
|
||||
* @type string $url Image source URL.
|
||||
* @type int $width Image width in pixels.
|
||||
* @type int $height Image height in pixels.
|
||||
* }
|
||||
*/
|
||||
public static function get_cropped_image( $attachment_id, $target_width = self::OG_IMAGE_WIDTH, $target_height = self::OG_IMAGE_HEIGHT ) {
|
||||
$focal_point = self::get_for_image( $attachment_id );
|
||||
|
||||
if ( ! $focal_point ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$crop_data = self::get_crop_data( $attachment_id, $focal_point['x'], $focal_point['y'], $target_width, $target_height );
|
||||
|
||||
if ( ! $crop_data ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array(
|
||||
'url' => $crop_data['url'],
|
||||
'width' => $crop_data['width'],
|
||||
'height' => $crop_data['height'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a focal-point cropped URL for an attachment.
|
||||
*
|
||||
* @param int $attachment_id Attachment ID.
|
||||
* @param float $focal_x Focal point x axis, 0-1.
|
||||
* @param float $focal_y Focal point y axis, 0-1.
|
||||
* @param int $target_width Target width.
|
||||
* @param int $target_height Target height.
|
||||
* @return string|null Cropped URL, or null when one cannot be generated.
|
||||
*/
|
||||
public static function get_cropped_url( $attachment_id, $focal_x, $focal_y, $target_width = self::OG_IMAGE_WIDTH, $target_height = self::OG_IMAGE_HEIGHT ) {
|
||||
$crop_data = self::get_crop_data( $attachment_id, $focal_x, $focal_y, $target_width, $target_height );
|
||||
|
||||
return $crop_data ? $crop_data['url'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a source crop rectangle for a focal point and target aspect ratio.
|
||||
*
|
||||
* The crop model matches the Social previews: center the crop on the focal
|
||||
* point, then clamp the crop rectangle to the source image edges.
|
||||
*
|
||||
* @param int $source_width Source image width.
|
||||
* @param int $source_height Source image height.
|
||||
* @param float $focal_x Focal point x axis, 0-1.
|
||||
* @param float $focal_y Focal point y axis, 0-1.
|
||||
* @param float $aspect Target aspect ratio.
|
||||
* @return array|null {
|
||||
* Crop rectangle, or null when inputs are invalid.
|
||||
*
|
||||
* @type int $x Source x coordinate.
|
||||
* @type int $y Source y coordinate.
|
||||
* @type int $width Crop width.
|
||||
* @type int $height Crop height.
|
||||
* }
|
||||
*/
|
||||
public static function crop_rect( $source_width, $source_height, $focal_x, $focal_y, $aspect ) {
|
||||
$source_width = absint( $source_width );
|
||||
$source_height = absint( $source_height );
|
||||
$aspect = (float) $aspect;
|
||||
|
||||
if ( ! $source_width || ! $source_height || $aspect <= 0 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$focal_x = self::clamp( (float) $focal_x, 0, 1 );
|
||||
$focal_y = self::clamp( (float) $focal_y, 0, 1 );
|
||||
|
||||
$crop_width = min( $source_width, $source_height * $aspect );
|
||||
$crop_height = $crop_width / $aspect;
|
||||
|
||||
if ( $crop_height > $source_height ) {
|
||||
$crop_height = $source_height;
|
||||
$crop_width = $crop_height * $aspect;
|
||||
}
|
||||
|
||||
$crop_width = max( 1, min( $source_width, (int) round( $crop_width ) ) );
|
||||
$crop_height = max( 1, min( $source_height, (int) round( $crop_height ) ) );
|
||||
$crop_x = (int) self::clamp( round( $focal_x * $source_width - $crop_width / 2 ), 0, $source_width - $crop_width );
|
||||
$crop_y = (int) self::clamp( round( $focal_y * $source_height - $crop_height / 2 ), 0, $source_height - $crop_height );
|
||||
|
||||
return array(
|
||||
'x' => $crop_x,
|
||||
'y' => $crop_y,
|
||||
'width' => $crop_width,
|
||||
'height' => $crop_height,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all crop data needed for a Photon URL and dimensions.
|
||||
*
|
||||
* @param int $attachment_id Attachment ID.
|
||||
* @param float $focal_x Focal point x axis, 0-1.
|
||||
* @param float $focal_y Focal point y axis, 0-1.
|
||||
* @param int $target_width Target width.
|
||||
* @param int $target_height Target height.
|
||||
* @return array|null Crop data, or null.
|
||||
*/
|
||||
private static function get_crop_data( $attachment_id, $focal_x, $focal_y, $target_width, $target_height ) {
|
||||
$attachment_id = absint( $attachment_id );
|
||||
$target_width = absint( $target_width );
|
||||
$target_height = absint( $target_height );
|
||||
|
||||
if ( ! $attachment_id || ! $target_width || ! $target_height || ! wp_attachment_is_image( $attachment_id ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
! class_exists( Image_CDN_Core::class )
|
||||
|| ! method_exists( Image_CDN_Core::class, 'cdn_url' )
|
||||
|| ! method_exists( Image_CDN_Core::class, 'is_cdn_url' )
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ( new Status() )->is_private_site() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$source_url = wp_get_attachment_url( $attachment_id );
|
||||
|
||||
if ( ! $source_url || ! self::is_supported_image_url( $source_url ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$dimensions = self::get_dimensions( $attachment_id );
|
||||
|
||||
if ( ! $dimensions ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$aspect = $target_width / $target_height;
|
||||
$crop_rect = self::crop_rect( $dimensions['width'], $dimensions['height'], $focal_x, $focal_y, $aspect );
|
||||
|
||||
if ( ! $crop_rect ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$args = array();
|
||||
$needs_crop = self::needs_crop( $crop_rect, $dimensions );
|
||||
$resize_width = $crop_rect['width'];
|
||||
$resize_height = $crop_rect['height'];
|
||||
|
||||
if ( $needs_crop ) {
|
||||
$args['crop'] = sprintf(
|
||||
'%dpx,%dpx,%dpx,%dpx',
|
||||
$crop_rect['x'],
|
||||
$crop_rect['y'],
|
||||
$crop_rect['width'],
|
||||
$crop_rect['height']
|
||||
);
|
||||
}
|
||||
|
||||
if ( $crop_rect['width'] > $target_width || $crop_rect['height'] > $target_height ) {
|
||||
$scale = min( $target_width / $crop_rect['width'], $target_height / $crop_rect['height'] );
|
||||
$resize_width = max( 1, (int) round( $crop_rect['width'] * $scale ) );
|
||||
$resize_height = max( 1, (int) round( $crop_rect['height'] * $scale ) );
|
||||
$args['resize'] = $resize_width . ',' . $resize_height;
|
||||
}
|
||||
|
||||
if ( ! $args ) {
|
||||
return array(
|
||||
'url' => $source_url,
|
||||
'width' => $dimensions['width'],
|
||||
'height' => $dimensions['height'],
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! self::can_preserve_source_query_string( $source_url ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cropped_url = Image_CDN_Core::cdn_url(
|
||||
$source_url,
|
||||
$args
|
||||
);
|
||||
|
||||
if ( ! $cropped_url || $cropped_url === $source_url ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array(
|
||||
'url' => $cropped_url,
|
||||
'width' => $resize_width,
|
||||
'height' => $resize_height,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the crop rectangle changes the source image.
|
||||
*
|
||||
* @param array $crop_rect Crop rectangle.
|
||||
* @param array $dimensions Source dimensions.
|
||||
* @return bool Whether the source image needs a crop operation.
|
||||
*/
|
||||
private static function needs_crop( $crop_rect, $dimensions ) {
|
||||
return 0 !== $crop_rect['x']
|
||||
|| 0 !== $crop_rect['y']
|
||||
|| $crop_rect['width'] !== $dimensions['width']
|
||||
|| $crop_rect['height'] !== $dimensions['height'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image dimensions from attachment metadata.
|
||||
*
|
||||
* @param int $attachment_id Attachment ID.
|
||||
* @return array|null {
|
||||
* Dimensions, or null.
|
||||
*
|
||||
* @type int $width Image width.
|
||||
* @type int $height Image height.
|
||||
* }
|
||||
*/
|
||||
private static function get_dimensions( $attachment_id ) {
|
||||
$metadata = wp_get_attachment_metadata( $attachment_id );
|
||||
|
||||
if (
|
||||
! is_array( $metadata )
|
||||
|| empty( $metadata['width'] )
|
||||
|| empty( $metadata['height'] )
|
||||
|| ! is_numeric( $metadata['width'] )
|
||||
|| ! is_numeric( $metadata['height'] )
|
||||
|| $metadata['width'] <= 0
|
||||
|| $metadata['height'] <= 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array(
|
||||
'width' => absint( $metadata['width'] ),
|
||||
'height' => absint( $metadata['height'] ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a URL has an Image CDN supported extension.
|
||||
*
|
||||
* @param string $url Image URL.
|
||||
* @return bool Whether Image CDN supports the URL extension.
|
||||
*/
|
||||
private static function is_supported_image_url( $url ) {
|
||||
if ( ! class_exists( Image_CDN::class ) || ! method_exists( Image_CDN::class, 'get_supported_extensions' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$path = wp_parse_url( $url, PHP_URL_PATH );
|
||||
|
||||
if ( ! $path ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array( strtolower( pathinfo( $path, PATHINFO_EXTENSION ) ), Image_CDN::get_supported_extensions(), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether Photon will preserve the source URL query string.
|
||||
*
|
||||
* Photon ignores source query strings by default. Some attachment providers put
|
||||
* required signatures in the query string, while transformed CDN URLs can already
|
||||
* contain ordered image manipulation args. Skip focal crops unless the domain opts
|
||||
* in to query string preservation.
|
||||
*
|
||||
* @param string $url Image URL.
|
||||
* @return bool Whether the source query string can be preserved.
|
||||
*/
|
||||
private static function can_preserve_source_query_string( $url ) {
|
||||
$url_parts = wp_parse_url( $url );
|
||||
|
||||
if ( ! is_array( $url_parts ) || empty( $url_parts['query'] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$host = strtolower( $url_parts['host'] ?? '' );
|
||||
|
||||
if ( '' === $host ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( Image_CDN_Core::is_cdn_url( $url ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow Photon to add source query strings for opted-in domains.
|
||||
*
|
||||
* @module photon
|
||||
*
|
||||
* @param bool false Should query strings be added to the image URL. Default is false.
|
||||
* @param string $host Image URL's host.
|
||||
*/
|
||||
return (bool) apply_filters( 'jetpack_photon_add_query_string_to_domain', false, $host );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate focal point shape.
|
||||
*
|
||||
* @param mixed $value Value to validate.
|
||||
* @return bool Whether the value is a valid focal point.
|
||||
*/
|
||||
private static function is_valid_focal_point( $value ) {
|
||||
if ( ! is_array( $value ) || ! array_key_exists( 'x', $value ) || ! array_key_exists( 'y', $value ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_numeric( $value['x'] )
|
||||
&& is_numeric( $value['y'] )
|
||||
&& $value['x'] >= 0
|
||||
&& $value['x'] <= 1
|
||||
&& $value['y'] >= 0
|
||||
&& $value['y'] <= 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the focal point is the registered default.
|
||||
*
|
||||
* @param array $value Focal point.
|
||||
* @return bool Whether the value is the default center point.
|
||||
*/
|
||||
private static function is_default_focal_point( $value ) {
|
||||
return 0.5 === (float) $value['x'] && 0.5 === (float) $value['y'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the focal point meta key is actually stored.
|
||||
*
|
||||
* Registered meta defaults can appear through the metadata API even when no
|
||||
* row has been saved. Only use the default center point when the key exists.
|
||||
*
|
||||
* @param int $attachment_id Attachment ID.
|
||||
* @return bool Whether the focal point key is stored on the attachment.
|
||||
*/
|
||||
private static function has_stored_focal_point_meta( $attachment_id ) {
|
||||
// metadata_exists() applies metadata filters, so registered defaults can look stored.
|
||||
$stored_meta_keys = get_post_custom_keys( $attachment_id );
|
||||
|
||||
return is_array( $stored_meta_keys )
|
||||
&& in_array( Publicize_Base::ATTACHMENT_IMAGE_FOCAL_POINT, $stored_meta_keys, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clamp a numeric value.
|
||||
*
|
||||
* @param float $value Value to clamp.
|
||||
* @param float $min Minimum value.
|
||||
* @param float $max Maximum value.
|
||||
* @return float Clamped value.
|
||||
*/
|
||||
private static function clamp( $value, $min, $max ) {
|
||||
return min( max( $value, $min ), $max );
|
||||
}
|
||||
}
|
||||
+295
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
/**
|
||||
* Keyring helper.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize;
|
||||
|
||||
use Automattic\Jetpack\Connection\Secrets;
|
||||
use Automattic\Jetpack\Paths;
|
||||
use Jetpack_IXR_Client;
|
||||
use Jetpack_Options;
|
||||
|
||||
/**
|
||||
* A series of utilities to interact with a Keyring instance.
|
||||
*/
|
||||
class Keyring_Helper {
|
||||
/**
|
||||
* Class instance
|
||||
*
|
||||
* @var Keyring_Helper
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Whether the `sharing` page is registered.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private static $is_sharing_page_registered = false;
|
||||
|
||||
/**
|
||||
* Initialize instance.
|
||||
*/
|
||||
public static function init() {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new Keyring_Helper();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
const 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',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
private function __construct() {
|
||||
add_action( 'admin_init', array( __CLASS__, 'intercept_request' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a URL to the public-api actions. Works like WP's admin_url.
|
||||
* On WordPress.com this is/calls Keyring::admin_url.
|
||||
*
|
||||
* @param string $service Shortname of a specific service.
|
||||
* @param array $params Parameters to append to an API connection URL.
|
||||
*
|
||||
* @return string URL to specific public-api process
|
||||
*/
|
||||
private static function api_url( $service = false, $params = array() ) {
|
||||
/**
|
||||
* Filters the API URL used to interact with WordPress.com.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @since-jetpack 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 ( array() !== $params ) {
|
||||
$url = add_query_arg( $params, $url );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a connection URL (sharing settings page with unique query args to create a connection).
|
||||
*
|
||||
* @param string $service_name Service name.
|
||||
* @param string $for Feature name.
|
||||
*/
|
||||
public 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,
|
||||
'publicize_action' => 1,
|
||||
),
|
||||
admin_url()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a URL to refresh a connection (sharing settings page with unique query args to refresh a connection).
|
||||
* Similar to connect_url, but with a refresh parameter.
|
||||
*
|
||||
* @param string $service_name Service name.
|
||||
* @param string $for Feature name.
|
||||
*/
|
||||
public 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" ),
|
||||
'publicize_action' => 1,
|
||||
),
|
||||
admin_url()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a URL to delete a connection (sharing settings page with unique query args to delete a connection).
|
||||
*
|
||||
* @param string $service_name Service name.
|
||||
* @param string $id Connection ID.
|
||||
*/
|
||||
public 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" ),
|
||||
'publicize_action' => 1,
|
||||
),
|
||||
admin_url()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build contents handling Keyring connection management into Sharing settings screen.
|
||||
*/
|
||||
public static function intercept_request() {
|
||||
if ( ! empty( $_GET['publicize_action'] ) && isset( $_GET['action'] ) ) {
|
||||
$service_name = null;
|
||||
|
||||
if ( isset( $_GET['service'] ) ) {
|
||||
$service_name = filter_var( wp_unslash( $_GET['service'] ) );
|
||||
}
|
||||
|
||||
switch ( $_GET['action'] ) {
|
||||
|
||||
case 'request':
|
||||
check_admin_referer( 'keyring-request', 'kr_nonce' );
|
||||
check_admin_referer( "keyring-request-$service_name", 'nonce' );
|
||||
|
||||
$verification = ( new Secrets() )->generate( 'publicize' );
|
||||
if ( ! $verification ) {
|
||||
$url = ( new Paths() )->admin_url( 'page=jetpack#/settings' );
|
||||
wp_die(
|
||||
sprintf(
|
||||
wp_kses(
|
||||
/* Translators: placeholder is a URL to a Settings page. */
|
||||
__( "Jetpack is not connected. Please connect Jetpack by visiting <a href='%s'>Settings</a>.", 'jetpack-publicize-pkg' ),
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
),
|
||||
)
|
||||
),
|
||||
esc_url( $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'];
|
||||
|
||||
$for = isset( $_GET['for'] ) ? sanitize_text_field( wp_unslash( $_GET['for'] ) ) : 'publicize';
|
||||
|
||||
$custom_inputs = array();
|
||||
|
||||
// For Bluesky.
|
||||
if ( isset( $_GET['handle'] ) && isset( $_GET['app_password'] ) ) {
|
||||
$custom_inputs['handle'] = sanitize_text_field( wp_unslash( $_GET['handle'] ) );
|
||||
|
||||
$custom_inputs['app_password'] = sanitize_text_field( wp_unslash( $_GET['app_password'] ) );
|
||||
}
|
||||
|
||||
// For Mastodon.
|
||||
if ( isset( $_GET['instance'] ) ) {
|
||||
$custom_inputs['instance'] = sanitize_text_field( wp_unslash( $_GET['instance'] ) );
|
||||
}
|
||||
|
||||
$user = wp_get_current_user();
|
||||
$redirect = self::api_url(
|
||||
$service_name,
|
||||
urlencode_deep(
|
||||
$custom_inputs +
|
||||
array(
|
||||
'action' => 'request',
|
||||
'redirect_uri' => add_query_arg( array( 'action' => 'done' ), menu_page_url( 'sharing', false ) ),
|
||||
'for' => $for,
|
||||
// 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 ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- The API URL is an external URL and is filterable.
|
||||
exit( 0 );
|
||||
|
||||
case 'completed':
|
||||
/*
|
||||
* We do not use a nonce here,
|
||||
* since we're populating a local cache of
|
||||
* the Publicize connections that were created and stored on WordPress.com.
|
||||
*/
|
||||
$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 = isset( $_GET['id'] ) ? filter_var( wp_unslash( $_GET['id'] ) ) : null;
|
||||
|
||||
check_admin_referer( 'keyring-request', 'kr_nonce' );
|
||||
check_admin_referer( "keyring-request-$service_name", 'nonce' );
|
||||
|
||||
self::disconnect( $service_name, $id );
|
||||
|
||||
do_action( 'connection_disconnected', $service_name );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a Publicize connection
|
||||
*
|
||||
* @param string $service_name Service name.
|
||||
* @param string $connection_id Connection ID.
|
||||
* @param int|bool $_blog_id Blog ID.
|
||||
* @param int|bool $_user_id User ID.
|
||||
* @param bool $force_delete Force delete the connection.
|
||||
*/
|
||||
public static function disconnect( $service_name, $connection_id, $_blog_id = false, $_user_id = false, $force_delete = false ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
$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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* Same-origin completion handler for the publicize auth_flow=v2 connect flow.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize;
|
||||
|
||||
/**
|
||||
* Handles the popup landing page that public-api redirects back to after a connection is
|
||||
* verified (auth_flow=v2).
|
||||
*
|
||||
* Because Meta/Threads sever `window.opener` via COOP, the popup cannot post its result back
|
||||
* to the opener tab. Instead public-api redirects the popup to this same-origin admin-post
|
||||
* endpoint, which broadcasts the request_id over a BroadcastChannel that the opener is
|
||||
* listening on, then closes itself. The opener then fetches the verified result once.
|
||||
*/
|
||||
class Keyring_Result_Handler {
|
||||
|
||||
/**
|
||||
* The admin-post action that the connect popup is redirected back to.
|
||||
*/
|
||||
const ACTION = 'jetpack_social_keyring_done';
|
||||
|
||||
/**
|
||||
* The BroadcastChannel name shared with the client.
|
||||
*
|
||||
* Must match KEYRING_BROADCAST_CHANNEL in _inc/utils/request-external-access.js.
|
||||
*/
|
||||
const CHANNEL = 'jetpack-social-keyring';
|
||||
|
||||
/**
|
||||
* Register the handler.
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'admin_post_' . self::ACTION, array( __CLASS__, 'handle' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a minimal page that broadcasts the request_id to the opener and closes the popup.
|
||||
*
|
||||
* @return never
|
||||
*/
|
||||
public static function handle() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only: the request_id is an opaque token reflected back to a same-origin BroadcastChannel; nothing is mutated.
|
||||
$request_id = isset( $_GET['request_id'] ) ? sanitize_key( wp_unslash( $_GET['request_id'] ) ) : '';
|
||||
|
||||
nocache_headers();
|
||||
|
||||
if ( ! headers_sent() ) {
|
||||
header( 'Content-Type: text/html; charset=' . get_option( 'blog_charset' ) );
|
||||
}
|
||||
|
||||
$close_warning = esc_html__( 'You can close this window now.', 'jetpack-publicize-pkg' );
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html <?php language_attributes(); ?>>
|
||||
<head>
|
||||
<meta charset="<?php echo esc_attr( get_option( 'blog_charset' ) ); ?>" />
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<p><?php echo $close_warning; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped above. ?></p>
|
||||
<script>
|
||||
( function () {
|
||||
try {
|
||||
var channel = new BroadcastChannel( <?php echo wp_json_encode( self::CHANNEL, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> );
|
||||
channel.postMessage( {
|
||||
type: 'keyring-result',
|
||||
requestId: <?php echo wp_json_encode( $request_id, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?>,
|
||||
} );
|
||||
channel.close();
|
||||
} catch ( e ) {}
|
||||
|
||||
window.setTimeout( function () {
|
||||
window.close();
|
||||
}, 50 );
|
||||
} )();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
exit;
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
/**
|
||||
* Publicize Message Templates Placeholders class.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize;
|
||||
|
||||
use Automattic\Jetpack\Publicize\REST_API\Proxy_Requests;
|
||||
use WP_REST_Request;
|
||||
|
||||
/**
|
||||
* Fetches and caches the canonical message-template placeholder catalogue from WPCOM.
|
||||
*
|
||||
* The value transient holds the catalogue and is only overwritten on a
|
||||
* successful refetch — we never want to drop back to an empty list.
|
||||
* The validity transient acts as a soft TTL: its expiry triggers the next refetch.
|
||||
*/
|
||||
class Message_Templates_Placeholders {
|
||||
|
||||
const VALUE_TRANSIENT = 'jetpack_social_message_template_placeholders';
|
||||
const VALIDITY_TRANSIENT = 'jetpack_social_message_template_placeholders_validity';
|
||||
|
||||
const VALIDITY_TTL = 3 * DAY_IN_SECONDS;
|
||||
const FAILURE_RETRY_TTL = HOUR_IN_SECONDS;
|
||||
|
||||
/**
|
||||
* Get the placeholder catalogue.
|
||||
*
|
||||
* @return array<int, array{id: string, label: string}>
|
||||
*/
|
||||
public static function get_all() {
|
||||
if ( Publicize_Utils::is_wpcom() ) {
|
||||
return self::wpcom_get_placeholders();
|
||||
}
|
||||
|
||||
$cached = get_transient( self::VALUE_TRANSIENT );
|
||||
$is_valid = false !== get_transient( self::VALIDITY_TRANSIENT );
|
||||
|
||||
if ( $is_valid ) {
|
||||
return is_array( $cached ) ? $cached : array();
|
||||
}
|
||||
|
||||
return self::fetch_and_cache( $cached );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch from WPCOM and update the cache.
|
||||
*
|
||||
* @param array|false $existing Existing cached value to fall back to on failure.
|
||||
* @return array<int, array{id: string, label: string}>
|
||||
*/
|
||||
public static function fetch_and_cache( $existing = false ) {
|
||||
$placeholders = self::fetch_from_wpcom();
|
||||
|
||||
if ( is_wp_error( $placeholders ) || empty( $placeholders ) ) {
|
||||
// Throttle retries so we don't hammer WPCOM while it's unreachable.
|
||||
set_transient( self::VALIDITY_TRANSIENT, 1, self::FAILURE_RETRY_TTL );
|
||||
|
||||
return is_array( $existing ) ? $existing : array();
|
||||
}
|
||||
|
||||
set_transient( self::VALUE_TRANSIENT, $placeholders, YEAR_IN_SECONDS );
|
||||
set_transient( self::VALIDITY_TRANSIENT, 1, self::VALIDITY_TTL );
|
||||
|
||||
return $placeholders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy the request to WPCOM as blog.
|
||||
*
|
||||
* @return array|\WP_Error
|
||||
*/
|
||||
public static function fetch_from_wpcom() {
|
||||
$proxy = new Proxy_Requests( 'publicize/message-templates/placeholders' );
|
||||
$request = new WP_REST_Request( 'GET' );
|
||||
|
||||
return $proxy->proxy_request_to_wpcom_as_blog( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and reshape the catalogue directly from WPCOM's helper. WPCOM Simple only.
|
||||
*
|
||||
* @return array<int, array{id: string, label: string}>
|
||||
*/
|
||||
public static function wpcom_get_placeholders() {
|
||||
Publicize_Utils::assert_is_wpcom( __METHOD__ );
|
||||
|
||||
require_lib( 'publicize/util/message-templates' );
|
||||
|
||||
$placeholders = array();
|
||||
foreach ( \Publicize\get_supported_placeholders() as $id => $entry ) {
|
||||
$placeholders[] = array(
|
||||
'id' => $id,
|
||||
'label' => $entry['title'],
|
||||
);
|
||||
}
|
||||
|
||||
return $placeholders;
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/**
|
||||
* Publicize_Assets.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize;
|
||||
|
||||
use Automattic\Jetpack\Assets;
|
||||
use Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills;
|
||||
|
||||
/**
|
||||
* Publicize_Assets class.
|
||||
*/
|
||||
class Publicize_Assets {
|
||||
|
||||
/**
|
||||
* Initialize the class.
|
||||
*/
|
||||
public static function configure() {
|
||||
Publicize_Script_Data::configure();
|
||||
|
||||
add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'enqueue_block_editor_scripts' ), 15 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to enqueue the block editor scripts.
|
||||
*
|
||||
* @return boolean True if the criteria are met.
|
||||
*/
|
||||
public static function should_enqueue_block_editor_scripts() {
|
||||
|
||||
$post_type = get_post_type();
|
||||
|
||||
if ( empty( $post_type ) || ! post_type_supports( $post_type, 'publicize' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** This filter is documented in projects/packages/publicize/src/class-publicize-base.php */
|
||||
$capability = apply_filters( 'jetpack_publicize_capability', 'publish_posts' );
|
||||
|
||||
return current_user_can( $capability );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue block editor scripts and styles.
|
||||
*/
|
||||
public static function enqueue_block_editor_scripts() {
|
||||
if ( ! self::should_enqueue_block_editor_scripts() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't want to render the Social UI in Jetpack sidebar
|
||||
// if Jetpack is old, which has it bundled.
|
||||
if ( defined( 'JETPACK__VERSION' ) && ( version_compare( (string) JETPACK__VERSION, '14.5-a.1', '<' ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$script_to_load = class_exists( 'Jetpack' ) ? 'block-editor-jetpack' : 'block-editor-social';
|
||||
|
||||
self::register_wp_build_polyfills();
|
||||
|
||||
// Dequeue the old Social assets.
|
||||
wp_dequeue_script( 'jetpack-social-editor' );
|
||||
wp_dequeue_style( 'jetpack-social-editor' );
|
||||
|
||||
Assets::register_script(
|
||||
'jetpack-social-editor',
|
||||
sprintf( '../build/%s.js', $script_to_load ),
|
||||
__FILE__,
|
||||
array(
|
||||
'in_footer' => true,
|
||||
'textdomain' => 'jetpack-publicize-pkg',
|
||||
'enqueue' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register polyfills for the wp-theme / wp-private-apis handles the Social bundles
|
||||
* depend on but WP < 7.0 does not ship (or ships with an incomplete allowlist).
|
||||
*
|
||||
* Only the two handles Social actually uses are requested, to keep the polyfill's
|
||||
* `wp-private-apis` force-replacement off any handle we don't need.
|
||||
*/
|
||||
public static function register_wp_build_polyfills() {
|
||||
if ( ! class_exists( WP_Build_Polyfills::class ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
WP_Build_Polyfills::register(
|
||||
'jetpack-social',
|
||||
array( 'wp-theme', 'wp-private-apis' )
|
||||
);
|
||||
}
|
||||
}
|
||||
+2147
File diff suppressed because it is too large
Load Diff
+290
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
/**
|
||||
* Publicize_Script_Data.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize;
|
||||
|
||||
use Automattic\Jetpack\Connection\Manager;
|
||||
use Automattic\Jetpack\Current_Plan;
|
||||
use Automattic\Jetpack\Publicize\Jetpack_Social_Settings\Settings;
|
||||
use Automattic\Jetpack\Publicize\Publicize_Utils as Utils;
|
||||
use Automattic\Jetpack\Publicize\Services as Publicize_Services;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
|
||||
/**
|
||||
* Publicize_Script_Data class.
|
||||
*/
|
||||
class Publicize_Script_Data {
|
||||
|
||||
/**
|
||||
* Get the publicize instance - properly typed
|
||||
*
|
||||
* @return Publicize
|
||||
*/
|
||||
public static function publicize() {
|
||||
/**
|
||||
* Publicize instance.
|
||||
*
|
||||
* @var Publicize $publicize
|
||||
*/
|
||||
global $publicize;
|
||||
|
||||
if ( ! $publicize && function_exists( 'publicize_init' ) ) {
|
||||
// @phan-suppress-next-line PhanUndeclaredFunction - phan is dumb not to see the function_exists check
|
||||
publicize_init();
|
||||
}
|
||||
|
||||
return $publicize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure script data.
|
||||
*/
|
||||
public static function configure() {
|
||||
add_filter( 'jetpack_admin_js_script_data', array( __CLASS__, 'set_admin_script_data' ), 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set script data.
|
||||
*
|
||||
* @param array $data The script data.
|
||||
*/
|
||||
public static function set_admin_script_data( $data ) {
|
||||
|
||||
$data['social'] = apply_filters( 'jetpack_social_admin_script_data', self::get_admin_script_data(), $data );
|
||||
|
||||
if ( empty( $data['site']['plan']['product_slug'] ) ) {
|
||||
$data['site']['plan'] = Current_Plan::get();
|
||||
}
|
||||
|
||||
// Override features for simple sites.
|
||||
if ( ( new Host() )->is_wpcom_simple() ) {
|
||||
$data['site']['plan']['features'] = Current_Plan::get_simple_site_specific_features();
|
||||
}
|
||||
|
||||
$data['site']['wpcom']['blog_id'] = Manager::get_site_id( true );
|
||||
|
||||
self::set_wpcom_user_data( $data['user']['current_user'] );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set wpcom user data.
|
||||
*
|
||||
* @param array $user_data The user data.
|
||||
*/
|
||||
private static function set_wpcom_user_data( &$user_data ) {
|
||||
if ( ( new Host() )->is_wpcom_simple() ) {
|
||||
$wpcom_user_data = array(
|
||||
'ID' => get_current_user_id(),
|
||||
'login' => wp_get_current_user()->user_login,
|
||||
);
|
||||
} else {
|
||||
$wpcom_user_data = ( new Manager() )->get_connected_user_data();
|
||||
}
|
||||
|
||||
$user_data['wpcom'] = array_merge(
|
||||
$user_data['wpcom'] ?? array(),
|
||||
$wpcom_user_data ? $wpcom_user_data : array()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the script data for admin UI.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_admin_script_data() {
|
||||
|
||||
// Only set script data on the social settings page,
|
||||
// the Jetpack settings page, or the block editor.
|
||||
$should_set_script_data = Utils::is_jetpack_settings_page()
|
||||
|| Utils::is_social_settings_page()
|
||||
|| Utils::should_block_editor_have_social();
|
||||
|
||||
if ( ! $should_set_script_data ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$basic_data = array(
|
||||
'api_paths' => self::get_api_paths(),
|
||||
'assets_url' => plugins_url( '/build/', __DIR__ ),
|
||||
'is_publicize_enabled' => Utils::is_publicize_active(),
|
||||
'message_templates' => array(),
|
||||
'supported_services' => array(),
|
||||
'urls' => array(),
|
||||
'settings' => self::get_social_settings(),
|
||||
'plugin_info' => self::get_plugin_info(),
|
||||
'nonces' => self::get_nonces(),
|
||||
);
|
||||
|
||||
if ( ! Utils::is_publicize_active() ) {
|
||||
return $basic_data;
|
||||
}
|
||||
|
||||
// Simple sites don't have a user connection.
|
||||
$is_publicize_configured = ( new Host() )->is_wpcom_simple() || Utils::is_connected();
|
||||
|
||||
if ( ! $is_publicize_configured ) {
|
||||
return $basic_data;
|
||||
}
|
||||
|
||||
return array_merge(
|
||||
$basic_data,
|
||||
array(
|
||||
'supported_services' => self::get_supported_services(),
|
||||
'urls' => self::get_urls(),
|
||||
'store_initial_state' => self::get_store_initial_state(),
|
||||
'message_templates' => array(
|
||||
'placeholders' => Message_Templates_Placeholders::get_all(),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the social settings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_social_settings() {
|
||||
|
||||
$settings = ( new Settings() );
|
||||
|
||||
return array(
|
||||
'socialImageGenerator' => $settings->get_image_generator_settings(),
|
||||
'utmSettings' => $settings->get_utm_settings(),
|
||||
'socialNotes' => array(
|
||||
'enabled' => $settings->is_social_notes_enabled(),
|
||||
'config' => $settings->get_social_notes_config(),
|
||||
),
|
||||
'showPricingPage' => $settings->should_show_pricing_page(),
|
||||
'messageTemplate' => $settings->get_message_template(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plugin info.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_plugin_info() {
|
||||
|
||||
$social_version = null;
|
||||
$jetpack_version = null;
|
||||
|
||||
if ( defined( 'JETPACK_SOCIAL_PLUGIN_ROOT_FILE' ) ) {
|
||||
|
||||
$plugin_data = get_plugin_data( (string) constant( 'JETPACK_SOCIAL_PLUGIN_ROOT_FILE' ), false, false );
|
||||
|
||||
$social_version = $plugin_data['Version'];
|
||||
}
|
||||
|
||||
if ( defined( 'JETPACK__VERSION' ) ) {
|
||||
$jetpack_version = constant( 'JETPACK__VERSION' );
|
||||
}
|
||||
|
||||
return array(
|
||||
'social' => array(
|
||||
'version' => $social_version,
|
||||
),
|
||||
'jetpack' => array(
|
||||
'version' => $jetpack_version,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the social store initial state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_store_initial_state() {
|
||||
|
||||
$post = get_post();
|
||||
|
||||
$share_status = array();
|
||||
|
||||
// get_post_share_status is not available on WPCOM yet.
|
||||
if ( Utils::should_block_editor_have_social() && $post ) {
|
||||
$share_status[ $post->ID ] = Share_Status::get_post_share_status( $post->ID );
|
||||
}
|
||||
|
||||
return array(
|
||||
'connectionData' => array(
|
||||
'connections' => Connections::get_all_for_user(),
|
||||
),
|
||||
'shareStatus' => $share_status,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the site has the feature flag enabled.
|
||||
*
|
||||
* @deprecated 0.69.1 Use Current_Plan::supports() directly instead.
|
||||
*
|
||||
* @todo Remove this method After March 2026.
|
||||
*
|
||||
* @param string $feature The feature name to check for, without the "social-" prefix.
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_feature_flag( $feature ): bool {
|
||||
return Current_Plan::supports( 'social-' . $feature );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of supported Publicize services.
|
||||
*
|
||||
* @return array List of external services and their settings.
|
||||
*/
|
||||
public static function get_supported_services() {
|
||||
return Publicize_Services::get_all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the API paths.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_api_paths() {
|
||||
|
||||
return array(
|
||||
'refreshConnections' => '/wpcom/v2/publicize/connections?test_connections=1',
|
||||
// The complete path will be like `/jetpack/v4/social/settings`.
|
||||
'socialToggleBase' => Utils::should_use_jetpack_module_endpoint() ? 'settings' : 'social/settings',
|
||||
'resharePost' => '/wpcom/v2/publicize/share-post/{postId}',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URLs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_urls() {
|
||||
|
||||
$urls = array(
|
||||
'connectionsManagementPage' => self::publicize()->publicize_connections_url(),
|
||||
);
|
||||
|
||||
// Escape the URLs.
|
||||
array_walk( $urls, 'esc_url_raw' );
|
||||
|
||||
return $urls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get nonces required by the Social admin UI.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_nonces() {
|
||||
return array(
|
||||
'refresh_plan' => wp_create_nonce( Social_Admin_Page::REFRESH_PLAN_NONCE_ACTION ),
|
||||
);
|
||||
}
|
||||
}
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
/**
|
||||
* Main Publicize class.
|
||||
*
|
||||
* @package automattic/jetpack
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize;
|
||||
|
||||
use Automattic\Jetpack\Current_Plan;
|
||||
use Automattic\Jetpack\Status;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
|
||||
/**
|
||||
* The class to configure and initialize the publicize package.
|
||||
*/
|
||||
class Publicize_Setup {
|
||||
|
||||
/**
|
||||
* Whether to update the plan information from WPCOM when initialising the package.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $refresh_plan_info = false;
|
||||
|
||||
/**
|
||||
* Whether the class has been initialized.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $initialized = false;
|
||||
|
||||
/**
|
||||
* To configure the publicize package, when called via the Config package.
|
||||
*/
|
||||
public static function configure() {
|
||||
add_action( 'jetpack_feature_publicize_enabled', array( __CLASS__, 'on_jetpack_feature_publicize_enabled' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to load the Publicize module.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function should_load() {
|
||||
|
||||
/**
|
||||
* We do not want to load Publicize on WPCOM private sites.
|
||||
*/
|
||||
$is_wpcom_platform_private_site = ( new Host() )->is_wpcom_platform() && ( new Status() )->is_private_site();
|
||||
|
||||
$should_load = ! $is_wpcom_platform_private_site;
|
||||
|
||||
/**
|
||||
* Filters the flag to decide whether to load the Publicize module.
|
||||
*
|
||||
* @since 0.64.0
|
||||
*
|
||||
* @param bool $should_load Whether to load the Publicize module.
|
||||
*/
|
||||
return (bool) apply_filters( 'jetpack_publicize_should_load', $should_load );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization of publicize logic that should always be loaded,
|
||||
* regardless of whether Publicize is enabled or not.
|
||||
*
|
||||
* You should justify everyting that is done here, as it will be loaded on every pageload.
|
||||
*/
|
||||
public static function pre_initialization() {
|
||||
if ( ! self::should_load() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$is_wpcom_simple = ( new Host() )->is_wpcom_simple();
|
||||
|
||||
/**
|
||||
* Assets are to be loaded in all cases.
|
||||
*
|
||||
* To allow loading of admin page and
|
||||
* the editor placeholder when publicize is OFF.
|
||||
*/
|
||||
Publicize_Assets::configure();
|
||||
|
||||
/**
|
||||
* Social admin page is to be always registered.
|
||||
*/
|
||||
Social_Admin_Page::init();
|
||||
|
||||
/**
|
||||
* The connect popup (auth_flow=v2) is redirected back to a same-origin admin-post
|
||||
* endpoint that broadcasts the result to the opener, so it must always be available.
|
||||
*/
|
||||
Keyring_Result_Handler::init();
|
||||
|
||||
if ( ! $is_wpcom_simple ) {
|
||||
/**
|
||||
* We need this only on Jetpack sites for Google Site auto-verification.
|
||||
*/
|
||||
add_action( 'init', array( Keyring_Helper::class, 'init' ), 9 );
|
||||
}
|
||||
|
||||
if ( $is_wpcom_simple ) {
|
||||
/**
|
||||
* Publicize is always enabled on WPCOM,
|
||||
* we can call the initialization method directly.
|
||||
*/
|
||||
add_action( 'plugins_loaded', array( self::class, 'on_jetpack_feature_publicize_enabled' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To configure the publicize package, when called via the Config package.
|
||||
*/
|
||||
public static function on_jetpack_feature_publicize_enabled() {
|
||||
if ( self::$initialized || ! self::should_load() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$initialized = true;
|
||||
|
||||
$is_wpcom_simple = ( new Host() )->is_wpcom_simple();
|
||||
|
||||
global $publicize;
|
||||
/**
|
||||
* If publicize is not initialzed on WPCOM,
|
||||
* it means that we are either on a public facing page
|
||||
* or a page where Publicize is not needed.
|
||||
* So, we will skip the whole set up here.
|
||||
*/
|
||||
if ( $is_wpcom_simple && ! $publicize ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $publicize_ui;
|
||||
|
||||
if ( ! isset( $publicize_ui ) ) {
|
||||
$publicize_ui = new Publicize_UI();
|
||||
}
|
||||
|
||||
$rest_controllers = array(
|
||||
REST_API\Connections_Controller::class,
|
||||
REST_API\Connections_Post_Field::class,
|
||||
REST_API\Keyring_Result_Controller::class,
|
||||
REST_API\Scheduled_Actions_Controller::class,
|
||||
REST_API\Services_Controller::class,
|
||||
REST_API\Share_Post_Controller::class,
|
||||
REST_API\Share_Status_Controller::class,
|
||||
REST_API\Social_Image_Generator_Controller::class,
|
||||
REST_API\Render_Messages_Controller::class,
|
||||
REST_API\Message_Templates_Placeholders_Controller::class,
|
||||
Jetpack_Social_Settings\Settings::class,
|
||||
);
|
||||
|
||||
// Load the REST controllers.
|
||||
foreach ( $rest_controllers as $controller ) {
|
||||
if ( $is_wpcom_simple ) {
|
||||
wpcom_rest_api_v2_load_plugin( $controller );
|
||||
} else {
|
||||
new $controller();
|
||||
}
|
||||
}
|
||||
|
||||
add_action( 'current_screen', array( self::class, 'add_filters_and_actions_for_screen' ), 5 );
|
||||
|
||||
( new Social_Image_Generator\Setup() )->init();
|
||||
|
||||
// Things that should not happen on WPCOM.
|
||||
if ( ! $is_wpcom_simple ) {
|
||||
add_action( 'rest_api_init', array( REST_Controller::class, 'register' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the current_screen has 'edit' as the base, add filter to change the post list tables.
|
||||
*
|
||||
* @param object $current_screen The current screen.
|
||||
*/
|
||||
public static function add_filters_and_actions_for_screen( $current_screen ) {
|
||||
if ( 'edit' !== $current_screen->base ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to enable/disable the Share action on the post list screen.
|
||||
*
|
||||
* The Share action allows users to reshare published posts via Jetpack Social.
|
||||
* It is automatically enabled for plans that support the 'republicize' feature,
|
||||
* but can be disabled via this filter.
|
||||
*
|
||||
* @since 0.2.0 Originally in jetpack-post-list package.
|
||||
* @since $$NEXT_VERSION$$ Moved to jetpack-publicize package.
|
||||
*
|
||||
* @param bool $show_share Whether to show the share action. Default true.
|
||||
* @param string $post_type The current post type.
|
||||
*/
|
||||
$show_share_action = Current_Plan::supports( 'republicize' )
|
||||
&& apply_filters( 'jetpack_post_list_display_share_action', true, $current_screen->post_type );
|
||||
|
||||
if ( $show_share_action ) {
|
||||
self::maybe_add_share_action( $current_screen->post_type );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Share action for post types that support publicize.
|
||||
*
|
||||
* @param string $post_type The post type.
|
||||
*/
|
||||
public static function maybe_add_share_action( $post_type ) {
|
||||
if (
|
||||
post_type_supports( $post_type, 'publicize' ) &&
|
||||
use_block_editor_for_post_type( $post_type )
|
||||
) {
|
||||
add_filter( 'post_row_actions', array( self::class, 'add_share_action' ), 20, 2 );
|
||||
add_filter( 'page_row_actions', array( self::class, 'add_share_action' ), 20, 2 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Share action link to the post row actions.
|
||||
*
|
||||
* @param array $post_actions The current post actions.
|
||||
* @param \WP_Post $post The post object.
|
||||
* @return array Modified post actions.
|
||||
*/
|
||||
public static function add_share_action( $post_actions, $post ) {
|
||||
$edit_url = get_edit_post_link( $post->ID, 'raw' );
|
||||
if ( ! $edit_url || 'publish' !== $post->post_status ) {
|
||||
return $post_actions;
|
||||
}
|
||||
|
||||
$url = add_query_arg( 'jetpack-editor-action', 'share_post', $edit_url );
|
||||
$text = _x( 'Share', 'Share the post on social networks', 'jetpack-publicize-pkg' );
|
||||
$title = _draft_or_post_title( $post );
|
||||
/* translators: post title */
|
||||
$label = sprintf( __( 'Share "%s" via Jetpack Social', 'jetpack-publicize-pkg' ), $title );
|
||||
$post_actions['share'] = sprintf( '<a href="%s" aria-label="%s">%s</a>', esc_url( $url ), esc_attr( $label ), esc_html( $text ) );
|
||||
|
||||
return $post_actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the blog ID based on the environment we're running in.
|
||||
*
|
||||
* @return int The WPCOM blog ID.
|
||||
*/
|
||||
public static function get_blog_id() {
|
||||
return defined( 'IS_WPCOM' ) && IS_WPCOM ? get_current_blog_id() : \Jetpack_Options::get_option( 'id' );
|
||||
}
|
||||
}
|
||||
+635
@@ -0,0 +1,635 @@
|
||||
<?php
|
||||
/**
|
||||
* Publicize_UI class.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize;
|
||||
|
||||
use Automattic\Jetpack\Assets;
|
||||
use Automattic\Jetpack\Current_Plan;
|
||||
use Automattic\Jetpack\Publicize\Publicize_Utils as Utils;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
|
||||
/**
|
||||
* Only user facing pieces of Publicize are found here.
|
||||
*/
|
||||
class Publicize_UI {
|
||||
/**
|
||||
* Contains an instance of class 'Publicize' which loads Keyring, sets up services, etc.
|
||||
*
|
||||
* @var Publicize Instance of Publicize
|
||||
*/
|
||||
public $publicize;
|
||||
|
||||
/**
|
||||
* URL to Sharing settings page in wordpress.com
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $publicize_settings_url = '';
|
||||
|
||||
/**
|
||||
* Hooks into WordPress to display the various pieces of UI and load our assets
|
||||
*/
|
||||
public function __construct() {
|
||||
global $publicize;
|
||||
if ( ! is_object( $publicize ) ) {
|
||||
$publicize = new Publicize();
|
||||
}
|
||||
$this->publicize = $publicize;
|
||||
|
||||
add_action( 'admin_init', array( $this, 'init' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize UI-related functionality.
|
||||
*/
|
||||
public function init() {
|
||||
// Show only to users with the capability required to manage their Publicize connections.
|
||||
if ( ! Utils::is_publicize_active() || ! $this->publicize->current_user_can_access_publicize_data() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->publicize_settings_url = $this->publicize->publicize_connections_url();
|
||||
|
||||
// Assets (css, js).
|
||||
add_action( 'admin_head-post.php', array( $this, 'post_page_metabox_assets' ) );
|
||||
add_action( 'admin_head-post-new.php', array( $this, 'post_page_metabox_assets' ) );
|
||||
|
||||
// Management of publicize (sharing screen, ajax/lightbox popup, and metabox on post screen).
|
||||
add_action( 'post_submitbox_misc_actions', array( $this, 'post_page_metabox' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* If the ShareDaddy plugin is not active we need to add the sharing settings page to the menu still
|
||||
*
|
||||
* @deprecated 0.42.3
|
||||
*/
|
||||
public function sharing_menu() {
|
||||
add_submenu_page(
|
||||
'options-general.php',
|
||||
esc_html__( 'Sharing Settings', 'jetpack-publicize-pkg' ),
|
||||
esc_html__( 'Sharing', 'jetpack-publicize-pkg' ),
|
||||
'publish_posts',
|
||||
'sharing',
|
||||
array( $this, 'wrapper_admin_page' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add admin page with wrapper.
|
||||
*
|
||||
* @deprecated 0.42.3
|
||||
*/
|
||||
public function wrapper_admin_page() {
|
||||
if ( class_exists( 'Jetpack_Admin_Page' ) ) {
|
||||
\Jetpack_Admin_Page::wrap_ui( array( $this, 'management_page' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Management page to load if Sharedaddy is not active so the 'pre_admin_screen_sharing' action exists.
|
||||
*
|
||||
* @deprecated 0.42.3
|
||||
*/
|
||||
public function management_page() {
|
||||
?>
|
||||
<div class="wrap">
|
||||
<div class="icon32" id="icon-options-general"><br /></div>
|
||||
<h1><?php esc_html_e( 'Sharing Settings', 'jetpack-publicize-pkg' ); ?></h1>
|
||||
|
||||
<?php
|
||||
/** This action is documented in modules/sharedaddy/sharing.php */
|
||||
do_action( 'pre_admin_screen_sharing' );
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Styling for the sharing screen and popups
|
||||
* JS for the options and switching
|
||||
*
|
||||
* @deprecated 0.42.3
|
||||
*/
|
||||
public function load_assets() {
|
||||
if ( class_exists( 'Jetpack_Admin_Page' ) ) {
|
||||
\Jetpack_Admin_Page::load_wrapper_styles();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the current user's publicized accounts for the blog
|
||||
* looks exactly like Publicize v1 for now, UI and functionality updates will come after the move to keyring
|
||||
*
|
||||
* @deprecated 0.42.3
|
||||
*/
|
||||
public function admin_page() {
|
||||
?>
|
||||
<h2 id="publicize"><?php esc_html_e( 'Jetpack Social', 'jetpack-publicize-pkg' ); ?></h2>
|
||||
<p><?php esc_html_e( 'Connect social media services to automatically share new posts.', 'jetpack-publicize-pkg' ); ?></p>
|
||||
<h4>
|
||||
<?php
|
||||
printf(
|
||||
wp_kses(
|
||||
/* translators: %s is the link to the Publicize page in Calypso */
|
||||
__( "We've made some updates to Jetpack Social. Please visit the <a href='%s' class='jptracks' data-jptracks-name='legacy_publicize_settings'>WordPress.com sharing page</a> to manage your Jetpack Social connections or use the button below.", 'jetpack-publicize-pkg' ),
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
'class' => array(),
|
||||
'data-jptracks-name' => array(),
|
||||
),
|
||||
)
|
||||
),
|
||||
esc_url( $this->publicize->publicize_connections_url() )
|
||||
);
|
||||
?>
|
||||
</h4>
|
||||
|
||||
<a href="<?php echo esc_url( $this->publicize->publicize_connections_url() ); ?>" class="button button-primary jptracks" data-jptracks-name='legacy_publicize_settings'><?php esc_html_e( 'Jetpack Social Settings', 'jetpack-publicize-pkg' ); ?></a>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* CSS for styling the publicize message box and counter that displays on the post page.
|
||||
* There is also some JavaScript for length counting and some basic display effects.
|
||||
*/
|
||||
public function post_page_metabox_assets() {
|
||||
// We don't need those assets for the block editor pages.
|
||||
$current_screen = get_current_screen();
|
||||
if ( $current_screen && $current_screen->is_block_editor ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$is_atomic_site = ( new Host() )->is_woa_site();
|
||||
$is_simple_site = ( new Host() )->is_wpcom_simple();
|
||||
$site_type = $is_atomic_site ? 'atomic' : ( $is_simple_site ? 'simple' : 'jetpack' );
|
||||
|
||||
Assets::register_script(
|
||||
'jetpack-social-classic-editor-options',
|
||||
'../build/classic-editor.js',
|
||||
__FILE__,
|
||||
array(
|
||||
'in_footer' => true,
|
||||
'enqueue' => true,
|
||||
'textdomain' => 'jetpack-publicize-pkg',
|
||||
)
|
||||
);
|
||||
|
||||
wp_add_inline_script(
|
||||
'jetpack-social-classic-editor-options',
|
||||
'var jetpackSocialClassicEditorOptions = ' . wp_json_encode(
|
||||
array(
|
||||
'connectionsUrl' => esc_url( $this->publicize_settings_url ),
|
||||
'isEnhancedPublishingEnabled' => $this->publicize->has_enhanced_publishing_feature(),
|
||||
'resharePath' => '/wpcom/v2/publicize/share-post/{postId}',
|
||||
'refreshConnections' => '/wpcom/v2/publicize/connections?test_connections=1',
|
||||
'isReshareSupported' => Current_Plan::supports( 'republicize' ),
|
||||
'siteType' => $site_type,
|
||||
),
|
||||
JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP
|
||||
),
|
||||
'before'
|
||||
);
|
||||
|
||||
$default_prefix = $this->publicize->default_prefix;
|
||||
$default_prefix = preg_replace( '/%([0-9])\$s/', '" + %\\1$s + "', wp_json_encode( (string) $default_prefix, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) );
|
||||
|
||||
$default_message = $this->publicize->default_message;
|
||||
$default_message = preg_replace( '/%([0-9])\$s/', '" + %\\1$s + "', wp_json_encode( (string) $default_message, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) );
|
||||
|
||||
$default_suffix = $this->publicize->default_suffix;
|
||||
$default_suffix = preg_replace( '/%([0-9])\$s/', '" + %\\1$s + "', wp_json_encode( (string) $default_suffix, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) );
|
||||
|
||||
$max_length = defined( 'JETPACK_PUBLICIZE_TWITTER_LENGTH' ) ? JETPACK_PUBLICIZE_TWITTER_LENGTH : 280;
|
||||
$max_length -= 24; // t.co link, space.
|
||||
|
||||
?>
|
||||
|
||||
<script type="text/javascript">
|
||||
jQuery( function($) {
|
||||
var wpasTitleCounter = $( '#wpas-title-counter' ),
|
||||
wpasTwitterCheckbox = $( '.wpas-submit-twitter' ).length,
|
||||
postTitle = $( '#title' ),
|
||||
wpasTitle = $( '#wpas-title' ).keyup( function() {
|
||||
var postTitleVal,
|
||||
length = wpasTitle.val().length;
|
||||
|
||||
if ( ! length ) {
|
||||
length = wpasTitle.attr( 'placeholder' ).length;
|
||||
}
|
||||
|
||||
wpasTitleCounter.text( length ).trigger( 'change' );
|
||||
} ),
|
||||
authClick = false;
|
||||
|
||||
wpasTitleCounter.on( 'change', function( e ) {
|
||||
if ( wpasTwitterCheckbox && parseInt( $( e.currentTarget ).text(), 10 ) > <?php echo (int) $max_length; ?> ) {
|
||||
wpasTitleCounter.addClass( 'wpas-twitter-length-limit' );
|
||||
} else {
|
||||
wpasTitleCounter.removeClass( 'wpas-twitter-length-limit' );
|
||||
}
|
||||
} );
|
||||
|
||||
// Keep the postTitle and the placeholder in sync
|
||||
postTitle.on( 'keyup', function( e ) {
|
||||
var url = $( '#sample-permalink' ).text();
|
||||
<?php // phpcs:ignore ?>
|
||||
var defaultMessage = $.trim( <?php printf( $default_prefix, 'url' ); ?> + <?php printf( $default_message, 'e.currentTarget.value', 'url' ); ?> + <?php printf( $default_suffix, 'url' ); ?> )
|
||||
.replace( /<[^>]+>/g,'');
|
||||
|
||||
wpasTitle.attr( 'placeholder', defaultMessage );
|
||||
wpasTitle.trigger( 'keyup' );
|
||||
} );
|
||||
|
||||
// set the initial placeholder
|
||||
postTitle.trigger( 'keyup' );
|
||||
|
||||
// If a custom message has been provided, open the UI so the author remembers
|
||||
if ( wpasTitle.val() && ! wpasTitle.prop( 'disabled' ) && wpasTitle.attr( 'placeholder' ) !== wpasTitle.val() ) {
|
||||
$( '#publicize-form' ).show();
|
||||
$( '#publicize-defaults' ).hide();
|
||||
$( '#publicize-form-edit' ).hide();
|
||||
}
|
||||
|
||||
$('#publicize-disconnected-form-show').click( function() {
|
||||
$('#publicize-form').slideDown( 'fast' );
|
||||
$(this).hide();
|
||||
} );
|
||||
|
||||
$('#publicize-disconnected-form-hide').click( function() {
|
||||
$('#publicize-form').slideUp( 'fast' );
|
||||
$('#publicize-disconnected-form-show').show();
|
||||
} );
|
||||
|
||||
$('#publicize-form-edit').click( function() {
|
||||
$('#publicize-form').slideDown( 'fast', function() {
|
||||
var selBeg = 0, selEnd = 0;
|
||||
wpasTitle.focus();
|
||||
|
||||
if ( ! wpasTitle.text() ) {
|
||||
wpasTitle.text( wpasTitle.attr( 'placeholder' ) );
|
||||
|
||||
selBeg = wpasTitle.text().indexOf( postTitle.val() );
|
||||
if ( selBeg < 0 ) {
|
||||
selBeg = 0;
|
||||
} else {
|
||||
selEnd = selBeg + postTitle.val().length;
|
||||
}
|
||||
|
||||
var domObj = wpasTitle.get(0);
|
||||
if ( domObj.setSelectionRange ) {
|
||||
domObj.setSelectionRange( selBeg, selEnd );
|
||||
} else if ( domObj.createTextRange ) {
|
||||
var r = domObj.createTextRange();
|
||||
r.moveStart( 'character', selBeg );
|
||||
r.moveEnd( 'character', selEnd );
|
||||
r.select();
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
$('#publicize-defaults').hide();
|
||||
$(this).hide();
|
||||
return false;
|
||||
} );
|
||||
|
||||
|
||||
$('#publicize-form-hide').click( function() {
|
||||
var newList = $.map( $('#publicize-form').slideUp( 'fast' ).find( ':checked' ), function( el ) {
|
||||
return $.trim( $(el).parent( 'label' ).text() );
|
||||
} );
|
||||
$('#publicize-defaults').html( '<strong>' + newList.join( '</strong>, <strong>' ) + '</strong>' ).show();
|
||||
$('#publicize-form-edit').show();
|
||||
return false;
|
||||
} );
|
||||
|
||||
$('.authorize-link').click( function() {
|
||||
if ( authClick ) {
|
||||
return false;
|
||||
}
|
||||
authClick = true;
|
||||
$(this).after( '<img src="images/loading.gif" class="alignleft" style="margin: 0 .5em" />' );
|
||||
$.ajaxSetup( { async: false } );
|
||||
|
||||
if ( window.wp && window.wp.autosave ) {
|
||||
window.wp.autosave.server.triggerSave();
|
||||
} else {
|
||||
autosave();
|
||||
}
|
||||
|
||||
return true;
|
||||
} );
|
||||
|
||||
$( '.pub-service' ).click( function() {
|
||||
var service = $(this).data( 'service' ),
|
||||
fakebox = '<input id="wpas-submit-' + service + '" type="hidden" value="1" name="wpas[submit][' + service + ']" />';
|
||||
$( '#add-publicize-check' ).append( fakebox );
|
||||
} );
|
||||
} );
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
#publicize {
|
||||
line-height: 1.5;
|
||||
}
|
||||
#publicize ul {
|
||||
margin: 4px 0 4px 6px;
|
||||
}
|
||||
#publicize li {
|
||||
margin: 0;
|
||||
}
|
||||
#publicize textarea {
|
||||
margin: 4px 0 0;
|
||||
width: 100%
|
||||
}
|
||||
#publicize ul.not-connected {
|
||||
list-style: square;
|
||||
padding-left: 1em;
|
||||
}
|
||||
.wpas-disabled {
|
||||
color: #999;
|
||||
}
|
||||
.publicize__notice-warning {
|
||||
display: block;
|
||||
padding: 7px 10px;
|
||||
margin: 5px 0;
|
||||
border-left-width: 4px;
|
||||
border-left-style: solid;
|
||||
font-size: 12px;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.publicize__notice-media-warning {
|
||||
border-right: 1px solid #c3c4c7;
|
||||
border-bottom: 1px solid #c3c4c7;
|
||||
border-top: 1px solid #c3c4c7;
|
||||
}
|
||||
.publicize-external-link {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.publicize-external-link__text {
|
||||
text-decoration: underline;
|
||||
}
|
||||
#publicize-title::before {
|
||||
content: "\f237";
|
||||
font: normal 20px/1 dashicons;
|
||||
speak: none;
|
||||
margin-left: -1px;
|
||||
padding-right: 3px;
|
||||
vertical-align: top;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
color: #8c8f94;
|
||||
}
|
||||
.post-new-php .authorize-link, .post-php .authorize-link {
|
||||
line-height: 1.5em;
|
||||
}
|
||||
.post-new-php .authorize-message, .post-php .authorize-message {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
#poststuff #publicize .updated p {
|
||||
margin: .5em 0;
|
||||
}
|
||||
.wpas-twitter-length-limit {
|
||||
color: red;
|
||||
}
|
||||
.publicize__notice-warning .dashicons {
|
||||
font-size: 16px;
|
||||
text-decoration: none;
|
||||
}
|
||||
.publicize-placeholders-help {
|
||||
margin: 0.5rem 0 1rem;
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the connection label.
|
||||
*
|
||||
* @param string $service_label Service's human-readable Label ("Facebook", "Twitter", ...).
|
||||
* @param string $display_name Connection's human-readable Username ("@jetpack", ...).
|
||||
* @return string
|
||||
*/
|
||||
private function connection_label( $service_label, $display_name ) {
|
||||
return sprintf(
|
||||
/* translators: %1$s: Service Name (Facebook, Twitter, ...), %2$s: Username on Service (@jetpack, ...) */
|
||||
__( '%1$s: %2$s', 'jetpack-publicize-pkg' ),
|
||||
$service_label,
|
||||
$display_name
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls the metabox that is displayed on the post page
|
||||
* Allows the user to customize the message that will be sent out to the social network, as well as pick which
|
||||
* networks to publish to. Also displays the character counter and some other information.
|
||||
*/
|
||||
public function post_page_metabox() {
|
||||
global $post;
|
||||
|
||||
if ( ! $this->publicize->post_type_is_publicizeable( $post->post_type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$connections_data = $this->publicize->get_filtered_connection_data();
|
||||
|
||||
if ( ! is_array( $connections_data ) ) {
|
||||
$connections_data = array();
|
||||
}
|
||||
?>
|
||||
<div id="publicize" class="misc-pub-section misc-pub-section-last">
|
||||
<span id="publicize-title">
|
||||
<?php
|
||||
esc_html_e( 'Jetpack Social:', 'jetpack-publicize-pkg' );
|
||||
|
||||
if ( ! empty( $connections_data ) ) :
|
||||
$publicize_form = $this->get_metabox_form_connected( $connections_data );
|
||||
|
||||
$labels = array();
|
||||
|
||||
foreach ( $connections_data as $connection_data ) {
|
||||
if ( ! $connection_data['enabled'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$labels[] = sprintf(
|
||||
'<strong>%s</strong>',
|
||||
esc_html( $this->connection_label( $connection_data['service_label'], $connection_data['display_name'] ) )
|
||||
);
|
||||
}
|
||||
|
||||
?>
|
||||
<?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- labels are already escaped above ?>
|
||||
<span id="publicize-defaults"><?php echo implode( ', ', $labels ); ?></span>
|
||||
<a href="#" id="publicize-form-edit"><?php esc_html_e( 'Edit', 'jetpack-publicize-pkg' ); ?></a> <a href="<?php echo esc_url( $this->publicize->publicize_connections_url() ); ?>" rel="noopener noreferrer" target="_blank"><?php esc_html_e( 'Settings', 'jetpack-publicize-pkg' ); ?></a><br />
|
||||
<?php
|
||||
else :
|
||||
$publicize_form = '';
|
||||
?>
|
||||
<strong><?php esc_html_e( 'Not Connected', 'jetpack-publicize-pkg' ); ?></strong>
|
||||
<a href="<?php echo esc_url( $this->publicize->publicize_connections_url() ); ?>" rel="noopener noreferrer" target="_blank"><?php esc_html_e( 'Settings', 'jetpack-publicize-pkg' ); ?></a><br />
|
||||
<?php
|
||||
|
||||
endif;
|
||||
?>
|
||||
</span>
|
||||
<?php
|
||||
/**
|
||||
* Fires right before rendering the Publicize form in the Classic
|
||||
* Editor.
|
||||
*
|
||||
* @since 0.14.0
|
||||
*/
|
||||
do_action( 'publicize_classic_editor_form_before' );
|
||||
|
||||
/**
|
||||
* Filter the Publicize details form.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @since-jetpack 2.0.0
|
||||
*
|
||||
* @param string $publicize_form Publicize Details form appearing above Publish button in the editor.
|
||||
*/
|
||||
echo apply_filters( 'publicize_form', $publicize_form ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Parts of the form are escaped individually in the code above.
|
||||
|
||||
/**
|
||||
* Fires right after rendering the Publicize form in the Classic
|
||||
* Editor.
|
||||
*
|
||||
* @since 0.14.0
|
||||
*/
|
||||
do_action( 'publicize_classic_editor_form_after' );
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML content for connections form.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @since-jetpack 6.7.0
|
||||
*
|
||||
* @global WP_Post $post The current post instance being published.
|
||||
*
|
||||
* @param array $connections_data Array of connections.
|
||||
* @return array {
|
||||
* Array of content for generating connection form.
|
||||
*
|
||||
* @type string HTML content of form
|
||||
* @type array {
|
||||
* Array of connection labels for active connections only.
|
||||
*
|
||||
* @type string Connection label string.
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
private function get_metabox_form_connected( $connections_data ) {
|
||||
global $post;
|
||||
|
||||
ob_start();
|
||||
|
||||
?>
|
||||
<div id="publicize-form" class="hide-if-js">
|
||||
<ul>
|
||||
<?php
|
||||
|
||||
foreach ( $connections_data as $connection_data ) {
|
||||
?>
|
||||
|
||||
<li>
|
||||
<label
|
||||
for="wpas-submit-<?php echo esc_attr( $connection_data['connection_id'] ); ?>"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="wpas[submit][<?php echo esc_attr( $connection_data['connection_id'] ); ?>]"
|
||||
id="wpas-submit-<?php echo esc_attr( $connection_data['connection_id'] ); ?>"
|
||||
class="wpas-submit-<?php echo esc_attr( $connection_data['service_name'] ); ?>"
|
||||
value="1"
|
||||
data-id="<?php echo esc_attr( $connection_data['connection_id'] ); ?>"
|
||||
<?php
|
||||
checked( true, $connection_data['enabled'] );
|
||||
?>
|
||||
/>
|
||||
<?php echo esc_html( $this->connection_label( $connection_data['service_label'], $connection_data['display_name'] ) ); ?>
|
||||
|
||||
</label>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
$title = get_post_meta( $post->ID, $this->publicize->POST_MESS, true );
|
||||
if ( ! $title ) {
|
||||
$title = '';
|
||||
}
|
||||
|
||||
$is_social_note = 'jetpack-social-note' === get_post_type( $post->ID );
|
||||
|
||||
$is_post_published = 'publish' === get_post_status( $post->ID );
|
||||
|
||||
$templates_enabled = Current_Plan::supports( 'social-message-templates' );
|
||||
|
||||
$placeholders = $this->get_message_placeholders();
|
||||
|
||||
?>
|
||||
|
||||
</ul>
|
||||
|
||||
<?php if ( ! $is_social_note ) : ?>
|
||||
<label for="wpas-title"><?php esc_html_e( 'Custom Message:', 'jetpack-publicize-pkg' ); ?></label>
|
||||
<?php if ( ! $templates_enabled ) : ?>
|
||||
<span id="wpas-title-counter" class="alignright hide-if-no-js">0</span>
|
||||
<?php endif; ?>
|
||||
<textarea name="wpas_title" id="wpas-title"><?php echo esc_textarea( $title ); ?></textarea>
|
||||
<?php if ( $templates_enabled && ! empty( $placeholders ) ) : ?>
|
||||
<details class="publicize-placeholders-help">
|
||||
<summary><?php esc_html_e( 'Available placeholders', 'jetpack-publicize-pkg' ); ?></summary>
|
||||
<p>
|
||||
<?php esc_html_e( 'Use placeholders to automatically insert post details.', 'jetpack-publicize-pkg' ); ?>
|
||||
</p>
|
||||
<ul>
|
||||
<?php foreach ( $placeholders as $placeholder ) : ?>
|
||||
<li>
|
||||
<code><?php echo esc_html( $placeholder['id'] ); ?></code>
|
||||
— <?php echo esc_html( $placeholder['label'] ); ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</details>
|
||||
<?php endif; ?>
|
||||
<a href="#" class="hide-if-no-js button" id="publicize-form-hide"><?php esc_html_e( 'OK', 'jetpack-publicize-pkg' ); ?></a>
|
||||
<input type="hidden" name="wpas[0]" value="1" />
|
||||
<?php endif; ?>
|
||||
<?php if ( $is_post_published && Current_Plan::supports( 'republicize' ) ) : ?>
|
||||
<button type="button" class="hide-if-no-js button" id="publicize-share-now">
|
||||
<?php esc_html_e( 'Share now', 'jetpack-publicize-pkg' ); ?>
|
||||
</button>
|
||||
<span id="publicize-share-now-notice" class="hidden"></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div id="pub-connection-needs-media"></div>
|
||||
<div id="pub-connection-tests"></div>
|
||||
<?php
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the catalogue of placeholders supported in custom Publicize messages.
|
||||
*
|
||||
* Sourced from WPCOM via `Message_Templates_Placeholders` so the metabox
|
||||
* hint can never drift from the resolver in `Template_Parser`.
|
||||
*
|
||||
* @return array<int, array{id: string, label: string}> Ordered list of placeholders.
|
||||
*/
|
||||
private function get_message_placeholders() {
|
||||
return Message_Templates_Placeholders::get_all();
|
||||
}
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
/**
|
||||
* Publicize_Utils.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize;
|
||||
|
||||
use Automattic\Jetpack\Connection\Manager;
|
||||
use Automattic\Jetpack\Modules;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
|
||||
/**
|
||||
* Publicize_Utils class.
|
||||
*/
|
||||
class Publicize_Utils {
|
||||
|
||||
/**
|
||||
* Whether the current page is the social settings page.
|
||||
*/
|
||||
public static function is_social_settings_page() {
|
||||
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
|
||||
|
||||
return ! empty( $screen ) && 'jetpack_page_jetpack-social' === $screen->base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the current page is the Jetpack settings page.
|
||||
*/
|
||||
public static function is_jetpack_settings_page() {
|
||||
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
|
||||
|
||||
return ! empty( $screen ) && 'toplevel_page_jetpack' === $screen->base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the block editor should have the social features.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function should_block_editor_have_social() {
|
||||
if ( ! is_admin() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
|
||||
|
||||
if ( empty( $screen ) || ! $screen->is_block_editor() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$needs_jetpack_connection = ! ( new Host() )->is_wpcom_platform();
|
||||
|
||||
if ( $needs_jetpack_connection && ! self::is_connected() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$post_type = get_post_type();
|
||||
|
||||
if ( empty( $post_type ) || ! post_type_supports( $post_type, 'publicize' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to check that we have a Jetpack connection.
|
||||
*/
|
||||
public static function is_connected() {
|
||||
|
||||
$connection = new Manager();
|
||||
|
||||
return $connection->is_connected() && $connection->has_connected_user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Publicize module is active.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_publicize_active() {
|
||||
return ( new Modules() )->is_active( 'publicize' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we are on WPCOM.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_wpcom() {
|
||||
return ( new Host() )->is_wpcom_simple();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the method is only called on WPCOM.
|
||||
*
|
||||
* @param string $method The method name.
|
||||
*
|
||||
* @throws \Exception If the method is not called on WPCOM.
|
||||
*/
|
||||
public static function assert_is_wpcom( $method ) {
|
||||
if ( ! self::is_wpcom() ) {
|
||||
throw new \Exception( esc_html( "Method $method can only be called on WordPress.com." ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the new module endpoint is available in the used Jetpack version.
|
||||
* We need the module status in response that's why we do the version check https://github.com/Automattic/jetpack/pull/41461/files#diff-f8e5ef1115599de750b64143dd1901554254eddd95ab4371b6b6b3b2a5914224R638-R642.
|
||||
* More: https://github.com/Automattic/jetpack/pull/41596.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function should_use_jetpack_module_endpoint() {
|
||||
return class_exists( 'Jetpack' ) && defined( 'JETPACK__VERSION' ) && ( version_compare( (string) JETPACK__VERSION, '14.3', '>=' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a warning that a deprecated endpoint was called.
|
||||
*
|
||||
* @param string $function_name The function name.
|
||||
* @param string $version The version in which the endpoint was deprecated.
|
||||
* @param string $deprecated_endpoint The deprecated endpoint.
|
||||
* @param string $alternative_endpoint The alternative endpoint.
|
||||
*/
|
||||
public static function endpoint_deprecated_warning( $function_name, $version, $deprecated_endpoint, $alternative_endpoint = '' ) {
|
||||
|
||||
$messages = array(
|
||||
sprintf(
|
||||
/* translators: %s: REST API endpoint. */
|
||||
esc_html__( '%1$s endpoint has been deprecated.', 'jetpack-publicize-pkg' ),
|
||||
'"' . $deprecated_endpoint . '"'
|
||||
),
|
||||
);
|
||||
|
||||
if ( ! empty( $alternative_endpoint ) ) {
|
||||
$messages[] = sprintf(
|
||||
/* translators: %s: alternative endpoint. */
|
||||
esc_html__( 'Please use %s endpoint instead.', 'jetpack-publicize-pkg' ),
|
||||
'"' . $alternative_endpoint . '"'
|
||||
);
|
||||
}
|
||||
|
||||
$messages[] = esc_html__( 'Please update all the Jetpack plugins to the latest version.', 'jetpack-publicize-pkg' );
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- We have done it above.
|
||||
_doing_it_wrong( esc_html( $function_name ), implode( ' ', $messages ), $version );
|
||||
}
|
||||
}
|
||||
+738
@@ -0,0 +1,738 @@
|
||||
<?php
|
||||
/**
|
||||
* Publicize class.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize;
|
||||
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\Connection\Tokens;
|
||||
use Jetpack_IXR_Client;
|
||||
use Jetpack_Options;
|
||||
use WP_Error;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* Extend the base class with Jetpack-specific functionality.
|
||||
*/
|
||||
class Publicize extends Publicize_Base {
|
||||
|
||||
const JETPACK_SOCIAL_CONNECTIONS_TRANSIENT = 'jetpack_social_connections';
|
||||
|
||||
/**
|
||||
* Transitory storage of connection testing results.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $test_connection_results = array();
|
||||
|
||||
/**
|
||||
* Property to store the results of fetching the connections.
|
||||
*
|
||||
* @var null|array
|
||||
*/
|
||||
private $current_connections = null;
|
||||
|
||||
/**
|
||||
* Add hooks.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
add_filter( 'jetpack_xmlrpc_unauthenticated_methods', array( $this, 'register_update_publicize_connections_xmlrpc_method' ) );
|
||||
|
||||
add_action( 'load-settings_page_sharing', array( $this, 'admin_page_load' ), 9 );
|
||||
|
||||
add_action( 'load-settings_page_sharing', array( $this, 'force_user_connection' ) );
|
||||
|
||||
add_filter( 'jetpack_published_post_flags', array( $this, 'set_post_flags' ), 10, 2 );
|
||||
|
||||
add_action( 'wp_insert_post', array( $this, 'save_publicized' ), 11, 2 );
|
||||
|
||||
add_action( 'connection_disconnected', array( $this, 'add_disconnect_notice' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a notice when a connection has been disconnected.
|
||||
*/
|
||||
public function add_disconnect_notice() {
|
||||
add_action( 'admin_notices', array( $this, 'display_disconnected' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Force user connection before showing the Publicize UI.
|
||||
*/
|
||||
public function force_user_connection() {
|
||||
global $current_user;
|
||||
|
||||
$user_token = ( new Tokens() )->get_access_token( $current_user->ID );
|
||||
$is_user_connected = $user_token && ! is_wp_error( $user_token );
|
||||
|
||||
// If the user is already connected via Jetpack, then we're good.
|
||||
if ( $is_user_connected ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If they're not connected, then remove the Publicize UI and tell them they need to connect first.
|
||||
global $publicize_ui;
|
||||
remove_action( 'pre_admin_screen_sharing', array( $publicize_ui, 'admin_page' ) );
|
||||
|
||||
// Do we really need `admin_styles`? With the new admin UI, it's breaking some bits.
|
||||
// Jetpack::init()->admin_styles();.
|
||||
add_action( 'pre_admin_screen_sharing', array( $this, 'admin_page_warning' ), 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a warning when Publicize does not have a connection.
|
||||
*/
|
||||
public function admin_page_warning() {
|
||||
$jetpack = \Jetpack::init();
|
||||
$blog_name = get_bloginfo( 'blogname' );
|
||||
if ( empty( $blog_name ) ) {
|
||||
$blog_name = home_url( '/' );
|
||||
}
|
||||
|
||||
?>
|
||||
<div id="message" class="updated jetpack-message jp-connect">
|
||||
<div class="jetpack-wrap-container">
|
||||
<div class="jetpack-text-container">
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s is the name of the blog */
|
||||
esc_html( wptexturize( __( "To use Jetpack Social, you'll need to link your %s account to your WordPress.com account using the link below.", 'jetpack-publicize-pkg' ) ) ),
|
||||
'<strong>' . esc_html( $blog_name ) . '</strong>'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<p><?php echo esc_html( wptexturize( __( "If you don't have a WordPress.com account yet, you can sign up for free in just a few seconds.", 'jetpack-publicize-pkg' ) ) ); ?></p>
|
||||
</div>
|
||||
<div class="jetpack-install-container">
|
||||
<p class="submit"><a
|
||||
href="<?php echo esc_url( $jetpack->build_connect_url( false, menu_page_url( 'sharing', false ) ) ); ?>"
|
||||
class="button-connector"
|
||||
id="wpcom-connect"><?php esc_html_e( 'Link account with WordPress.com', 'jetpack-publicize-pkg' ); ?></a>
|
||||
</p>
|
||||
<p class="jetpack-install-blurb">
|
||||
<?php jetpack_render_tos_blurb(); ?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a Publicize Connection.
|
||||
*
|
||||
* @param string $service_name 'facebook', 'twitter', etc.
|
||||
* @param string $connection_id Connection ID.
|
||||
* @param false|int $_blog_id The blog ID. Use false (default) for the current blog.
|
||||
* @param false|int $_user_id The user ID. Use false (default) for the current user.
|
||||
* @param bool $force_delete Whether to skip permissions checks.
|
||||
* @return false|void False on failure. Void on success.
|
||||
*/
|
||||
public function disconnect( $service_name, $connection_id, $_blog_id = false, $_user_id = false, $force_delete = false ) {
|
||||
return Keyring_Helper::disconnect( $service_name, $connection_id, $_blog_id, $_user_id, $force_delete );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set updated Publicize connections in a transient.
|
||||
*
|
||||
* @param mixed $publicize_connections Updated connections.
|
||||
* @return true
|
||||
*/
|
||||
public function receive_updated_publicize_connections( $publicize_connections ) {
|
||||
|
||||
// Populate the cache with the new data.
|
||||
Connections::get_all( array( 'ignore_cache' => true ) );
|
||||
|
||||
$expiry = 3600 * 4;
|
||||
if ( ! set_transient( self::JETPACK_SOCIAL_CONNECTIONS_TRANSIENT, $publicize_connections, $expiry ) ) {
|
||||
// If the transient has beeen set in another request, the call to set_transient can fail. If so,
|
||||
// we can delete the transient and try again.
|
||||
$this->clear_connections_transient();
|
||||
set_transient( self::JETPACK_SOCIAL_CONNECTIONS_TRANSIENT, $publicize_connections, $expiry );
|
||||
}
|
||||
// Regardless of whether the transient is set ok, let's set and use the local property for this request.
|
||||
$this->current_connections = $publicize_connections;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add method to update Publicize connections.
|
||||
*
|
||||
* @param array $methods Array of registered methods.
|
||||
* @return array
|
||||
*/
|
||||
public function register_update_publicize_connections_xmlrpc_method( $methods ) {
|
||||
return array_merge(
|
||||
$methods,
|
||||
array(
|
||||
'jetpack.updatePublicizeConnections' => array( $this, 'receive_updated_publicize_connections' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all connections.
|
||||
*
|
||||
* Google Plus is no longer a functional service, so we remove it from the list.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_connections() {
|
||||
$this->refresh_connections();
|
||||
|
||||
$connections = $this->current_connections;
|
||||
|
||||
if ( empty( $connections ) ) {
|
||||
$connections = array();
|
||||
}
|
||||
|
||||
if ( isset( $connections['google_plus'] ) ) {
|
||||
unset( $connections['google_plus'] );
|
||||
}
|
||||
return $connections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connections for a specific service.
|
||||
*
|
||||
* @param string $service_name 'facebook', 'twitter', etc.
|
||||
* @param false|int $_blog_id The blog ID. Use false (default) for the current blog.
|
||||
* @param false|int $_user_id The user ID. Use false (default) for the current user.
|
||||
* @return false|object[]|array[]
|
||||
*/
|
||||
public function get_connections( $service_name, $_blog_id = false, $_user_id = false ) {
|
||||
if ( false === $_user_id ) {
|
||||
$_user_id = $this->user_id();
|
||||
}
|
||||
|
||||
$connections = $this->get_all_connections();
|
||||
$connections_to_return = array();
|
||||
|
||||
if ( ! empty( $connections ) && is_array( $connections ) ) {
|
||||
if ( ! empty( $connections[ $service_name ] ) ) {
|
||||
foreach ( $connections[ $service_name ] as $id => $connection ) {
|
||||
if ( $this->is_global_connection( $connection ) || $_user_id === (int) $connection['connection_data']['user_id'] ) {
|
||||
$connections_to_return[ $id ] = $connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $connections_to_return;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all connections for a specific user.
|
||||
*
|
||||
* @param array $args Arguments to run operations such as force refresh and connection test results.
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_connections_for_user( $args = array() ) {
|
||||
if ( ( isset( $args['clear_cache'] ) && $args['clear_cache'] )
|
||||
|| ( isset( $args['test_connections'] ) && $args['test_connections'] ) ) {
|
||||
$this->clear_connections_transient();
|
||||
}
|
||||
$connections = $this->get_all_connections();
|
||||
|
||||
$connections_to_return = array();
|
||||
if ( ! empty( $connections ) ) {
|
||||
foreach ( (array) $connections as $service_name => $connections_for_service ) {
|
||||
foreach ( $connections_for_service as $id => $connection ) {
|
||||
$user_id = (int) $connection['connection_data']['user_id'];
|
||||
if ( $user_id === 0 || $this->user_id() === $user_id ) {
|
||||
$connections_to_return[ $service_name ][ $id ] = $connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $connections_to_return;
|
||||
}
|
||||
|
||||
/**
|
||||
* To add the connection test results to the connections.
|
||||
*
|
||||
* @param array $connections The Jetpack Social connections.
|
||||
|
||||
* @return array
|
||||
*/
|
||||
public function add_connection_test_results( $connections ) {
|
||||
$path = sprintf( '/sites/%d/publicize/connection-test-results', absint( Jetpack_Options::get_option( 'id' ) ) );
|
||||
$response = Client::wpcom_json_api_request_as_user( $path, '2', array(), null, 'wpcom' );
|
||||
$connection_results = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
$connection_results_map = array();
|
||||
|
||||
foreach ( $connection_results as $connection_result ) {
|
||||
$connection_results_map[ $connection_result['connection_id'] ] = $connection_result['test_success'] ? 'ok' : 'broken';
|
||||
}
|
||||
foreach ( $connections as $key => $connection ) {
|
||||
if ( isset( $connection_results_map[ $connection['connection_id'] ] ) ) {
|
||||
$connections[ $key ]['status'] = $connection_results_map[ $connection['connection_id'] ];
|
||||
}
|
||||
}
|
||||
|
||||
return $connections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a connections for a user.
|
||||
*
|
||||
* @param int $connection_id The connection_id.
|
||||
|
||||
* @return array
|
||||
*/
|
||||
public function get_connection_for_user( $connection_id ) {
|
||||
foreach ( $this->get_all_connections_for_user() as $connection ) {
|
||||
if ( (int) $connection['connection_id'] === (int) $connection_id ) {
|
||||
return $connection;
|
||||
}
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of a connection.
|
||||
*
|
||||
* @param array $connection The connection.
|
||||
* @return string
|
||||
*/
|
||||
public function get_connection_id( $connection ) {
|
||||
return $connection['connection_data']['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique ID of a connection.
|
||||
*
|
||||
* @param array $connection The connection.
|
||||
* @return string
|
||||
*/
|
||||
public function get_connection_unique_id( $connection ) {
|
||||
return $connection['connection_data']['token_id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the meta of a connection.
|
||||
*
|
||||
* @param array $connection The connection.
|
||||
* @return array
|
||||
*/
|
||||
public function get_connection_meta( $connection ) {
|
||||
$connection['user_id'] = $connection['connection_data']['user_id']; // Allows for shared connections.
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show error on settings page if applicable.
|
||||
*/
|
||||
public function admin_page_load() {
|
||||
$action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
if ( 'error' === $action ) {
|
||||
add_action( 'pre_admin_screen_sharing', array( $this, 'display_connection_error' ), 9 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an error message.
|
||||
*/
|
||||
public function display_connection_error() {
|
||||
$code = false;
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
$service = isset( $_GET['service'] ) ? sanitize_text_field( wp_unslash( $_GET['service'] ) ) : null;
|
||||
$publicize_error = isset( $_GET['publicize_error'] ) ? sanitize_text_field( wp_unslash( $_GET['publicize_error'] ) ) : null;
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
if ( $service ) {
|
||||
/* translators: %s is the name of the Jetpack Social service (e.g. Facebook, Twitter) */
|
||||
$error = sprintf( __( 'There was a problem connecting to %s to create an authorized connection. Please try again in a moment.', 'jetpack-publicize-pkg' ), self::get_service_label( $service ) );
|
||||
} elseif ( $publicize_error ) {
|
||||
$code = strtolower( $publicize_error );
|
||||
switch ( $code ) {
|
||||
case '400':
|
||||
$error = __( 'An invalid request was made. This normally means that something intercepted or corrupted the request from your server to the Jetpack Server. Try again and see if it works this time.', 'jetpack-publicize-pkg' );
|
||||
break;
|
||||
case 'secret_mismatch':
|
||||
$error = __( 'We could not verify that your server is making an authorized request. Please try again, and make sure there is nothing interfering with requests from your server to the Jetpack Server.', 'jetpack-publicize-pkg' );
|
||||
break;
|
||||
case 'empty_blog_id':
|
||||
$error = __( 'No blog_id was included in your request. Please try disconnecting Jetpack from WordPress.com and then reconnecting it. Once you have done that, try connecting Jetpack Social again.', 'jetpack-publicize-pkg' );
|
||||
break;
|
||||
case 'empty_state':
|
||||
/* translators: %s is the URL of the Jetpack admin page */
|
||||
$error = sprintf( __( 'No user information was included in your request. Please make sure that your user account has connected to Jetpack. Connect your user account by going to the <a href="%s">Jetpack page</a> within wp-admin.', 'jetpack-publicize-pkg' ), \Jetpack::admin_url() );
|
||||
break;
|
||||
default:
|
||||
$error = __( 'Something which should never happen, happened. Sorry about that. If you try again, maybe it will work.', 'jetpack-publicize-pkg' );
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$error = __( 'There was a problem connecting with Jetpack Social. Please try again in a moment.', 'jetpack-publicize-pkg' );
|
||||
}
|
||||
// Using the same formatting/style as Jetpack::admin_notices() error.
|
||||
?>
|
||||
<div id="message" class="jetpack-message jetpack-err">
|
||||
<div class="squeezer">
|
||||
<h2>
|
||||
<?php
|
||||
echo wp_kses(
|
||||
$error,
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => true,
|
||||
),
|
||||
'code' => true,
|
||||
'strong' => true,
|
||||
'br' => true,
|
||||
'b' => true,
|
||||
)
|
||||
);
|
||||
?>
|
||||
</h2>
|
||||
<?php if ( $code ) : ?>
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s is the name of the error */
|
||||
esc_html__( 'Error code: %s', 'jetpack-publicize-pkg' ),
|
||||
esc_html( stripslashes( $code ) )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a message that the connection has been removed.
|
||||
*/
|
||||
public function display_disconnected() {
|
||||
echo "<div class='updated'>\n";
|
||||
echo '<p>' . esc_html( __( 'That connection has been removed.', 'jetpack-publicize-pkg' ) ) . "</p>\n";
|
||||
echo "</div>\n\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* If applicable, globalize a connection.
|
||||
*
|
||||
* @param string $connection_id Connection ID.
|
||||
*/
|
||||
public function globalization( $connection_id ) {
|
||||
if ( isset( $_REQUEST['global'] ) && 'on' === $_REQUEST['global'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nonce check happens earlier in the process before we get here
|
||||
if ( ! current_user_can( $this->GLOBAL_CAP ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
return;
|
||||
}
|
||||
|
||||
$this->globalize_connection( $connection_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Globalize a connection.
|
||||
*
|
||||
* @param string $connection_id Connection ID.
|
||||
*/
|
||||
public function globalize_connection( $connection_id ) {
|
||||
$xml = new Jetpack_IXR_Client();
|
||||
$xml->query( 'jetpack.globalizePublicizeConnection', $connection_id, 'globalize' );
|
||||
|
||||
if ( ! $xml->isError() ) {
|
||||
$response = $xml->getResponse();
|
||||
$this->receive_updated_publicize_connections( $response );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unglobalize a connection.
|
||||
*
|
||||
* @param string $connection_id Connection ID.
|
||||
*/
|
||||
public function unglobalize_connection( $connection_id ) {
|
||||
$xml = new Jetpack_IXR_Client();
|
||||
$xml->query( 'jetpack.globalizePublicizeConnection', $connection_id, 'unglobalize' );
|
||||
|
||||
if ( ! $xml->isError() ) {
|
||||
$response = $xml->getResponse();
|
||||
$this->receive_updated_publicize_connections( $response );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs a fresh copy of the publicize connections data, if the cache is busted.
|
||||
*/
|
||||
public function refresh_connections() {
|
||||
if ( null !== $this->current_connections ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$connections = get_transient( self::JETPACK_SOCIAL_CONNECTIONS_TRANSIENT );
|
||||
if ( false === $connections ) {
|
||||
$xml = new Jetpack_IXR_Client();
|
||||
$xml->query( 'jetpack.fetchPublicizeConnections' );
|
||||
if ( ! $xml->isError() ) {
|
||||
$response = $xml->getResponse();
|
||||
$this->receive_updated_publicize_connections( $response );
|
||||
}
|
||||
return;
|
||||
}
|
||||
$this->current_connections = $connections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the transient.
|
||||
*/
|
||||
public function clear_connections_transient() {
|
||||
delete_transient( self::JETPACK_SOCIAL_CONNECTIONS_TRANSIENT );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Publicize connect URL from Keyring.
|
||||
*
|
||||
* @param string $service_name Name of the service to get connect URL for.
|
||||
* @param string $for What the URL is for. Default 'publicize'.
|
||||
* @return string
|
||||
*/
|
||||
public function connect_url( $service_name, $for = 'publicize' ) {
|
||||
return Keyring_Helper::connect_url( $service_name, $for );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Publicize refresh URL from Keyring.
|
||||
*
|
||||
* @param string $service_name Name of the service to get refresh URL for.
|
||||
* @param string $for What the URL is for. Default 'publicize'.
|
||||
* @return string
|
||||
*/
|
||||
public function refresh_url( $service_name, $for = 'publicize' ) {
|
||||
return Keyring_Helper::refresh_url( $service_name, $for );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Publicize disconnect URL from Keyring.
|
||||
*
|
||||
* @param string $service_name Name of the service to get disconnect URL for.
|
||||
* @param mixed $id ID of the conenction to disconnect.
|
||||
* @return string
|
||||
*/
|
||||
public function disconnect_url( $service_name, $id ) {
|
||||
return Keyring_Helper::disconnect_url( $service_name, $id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get social networks, either all available or only those that the site is connected to.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @since-jetpack 2.0.0
|
||||
*
|
||||
* @since-jetpack 6.6.0 Removed Path. Service closed October 2018.
|
||||
*
|
||||
* @param string $filter Select the list of services that will be returned. Defaults to 'all', accepts 'connected'.
|
||||
* @param false|int $_blog_id Get services for a specific blog by ID, or set to false for current blog. Default false.
|
||||
* @param false|int $_user_id Get services for a specific user by ID, or set to false for current user. Default false.
|
||||
* @return array List of social networks.
|
||||
*/
|
||||
public function get_services( $filter = 'all', $_blog_id = false, $_user_id = false ) {
|
||||
$services = array(
|
||||
'facebook' => array(),
|
||||
'twitter' => array(),
|
||||
'linkedin' => array(),
|
||||
'tumblr' => array(),
|
||||
'mastodon' => array(),
|
||||
'instagram-business' => array(),
|
||||
'nextdoor' => array(),
|
||||
'threads' => array(),
|
||||
'bluesky' => array(),
|
||||
);
|
||||
|
||||
if ( 'all' === $filter ) {
|
||||
return $services;
|
||||
}
|
||||
|
||||
$connected_services = array();
|
||||
foreach ( $services as $service_name => $empty ) {
|
||||
$connections = $this->get_connections( $service_name, $_blog_id, $_user_id );
|
||||
if ( $connections ) {
|
||||
$connected_services[ $service_name ] = $connections;
|
||||
}
|
||||
}
|
||||
return $connected_services;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific connection. Stub.
|
||||
*
|
||||
* @param string $service_name 'facebook', 'twitter', etc.
|
||||
* @param string $connection_id Connection ID.
|
||||
* @param false|int $_blog_id The blog ID. Use false (default) for the current blog.
|
||||
* @param false|int $_user_id The user ID. Use false (default) for the current user.
|
||||
* @return void
|
||||
*/
|
||||
public function get_connection( $service_name, $connection_id, $_blog_id = false, $_user_id = false ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
// Stub.
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag a post for Publicize after publishing.
|
||||
*
|
||||
* @param string $new_status New status of the post.
|
||||
* @param string $old_status Old status of the post.
|
||||
* @param WP_Post $post Post object.
|
||||
*/
|
||||
public function flag_post_for_publicize( $new_status, $old_status, $post ) {
|
||||
if ( ! $post instanceof \WP_Post || ! $this->post_type_is_publicizeable( $post->post_type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$should_publicize = $this->should_submit_post_pre_checks( $post );
|
||||
|
||||
if ( 'publish' === $new_status && 'publish' !== $old_status ) {
|
||||
/**
|
||||
* Determines whether a post being published gets publicized.
|
||||
*
|
||||
* Side-note: Possibly our most alliterative filter name.
|
||||
*
|
||||
* @since 0.1.0 No longer defaults to true. Adds checks to not publicize based on different contexts.
|
||||
* @since-jetpack 4.1.0
|
||||
*
|
||||
* @param bool $should_publicize Should the post be publicized? Default to true.
|
||||
* @param WP_POST $post Current Post object.
|
||||
*/
|
||||
$should_publicize = apply_filters( 'publicize_should_publicize_published_post', $should_publicize, $post );
|
||||
|
||||
if ( $should_publicize ) {
|
||||
update_post_meta( $post->ID, $this->PENDING, true ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a connection.
|
||||
*
|
||||
* @param string $service_name Name of the service.
|
||||
* @param array $connection Connection to be tested.
|
||||
*/
|
||||
public function test_connection( $service_name, $connection ) {
|
||||
$id = $this->get_connection_id( $connection );
|
||||
|
||||
if ( array_key_exists( $id, $this->test_connection_results ) ) {
|
||||
return $this->test_connection_results[ $id ];
|
||||
}
|
||||
|
||||
$xml = new Jetpack_IXR_Client();
|
||||
$xml->query( 'jetpack.testPublicizeConnection', $id );
|
||||
|
||||
// Bail if all is well.
|
||||
if ( ! $xml->isError() ) {
|
||||
$this->test_connection_results[ $id ] = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
$xml_response = $xml->getResponse();
|
||||
$connection_test_message = $xml_response['faultString'];
|
||||
$connection_error_code = ( empty( $xml_response['faultCode'] ) || ! is_int( $xml_response['faultCode'] ) )
|
||||
? -1
|
||||
: $xml_response['faultCode'];
|
||||
|
||||
// Set up refresh if the user can.
|
||||
$user_can_refresh = current_user_can( $this->GLOBAL_CAP ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
if ( $user_can_refresh ) {
|
||||
/* translators: %s is the name of a social media service */
|
||||
$refresh_text = sprintf( _x( 'Refresh connection with %s', 'Refresh connection with {social media service}', 'jetpack-publicize-pkg' ), $this->get_service_label( $service_name ) );
|
||||
$refresh_url = $this->refresh_url( $service_name );
|
||||
}
|
||||
|
||||
$error_data = array(
|
||||
'user_can_refresh' => $user_can_refresh,
|
||||
'refresh_text' => $refresh_text ?? null,
|
||||
'refresh_url' => $refresh_url ?? null,
|
||||
);
|
||||
|
||||
$this->test_connection_results[ $id ] = new WP_Error( $connection_error_code, $connection_test_message, $error_data );
|
||||
|
||||
return $this->test_connection_results[ $id ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if post has already been shared by Publicize in the past.
|
||||
*
|
||||
* Jetpack uses two methods:
|
||||
* 1. A POST_DONE . 'all' postmeta flag, or
|
||||
* 2. if the post has already been published.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @since-jetpack 6.7.0
|
||||
*
|
||||
* @param integer $post_id Optional. Post ID to query connection status for: will use current post if missing.
|
||||
*
|
||||
* @return bool True if post has already been shared by Publicize, false otherwise.
|
||||
*/
|
||||
public function post_is_done_sharing( $post_id = null ) {
|
||||
// Defaults to current post if $post_id is null.
|
||||
$post = get_post( $post_id );
|
||||
if ( null === $post ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return 'publish' === $post->post_status || get_post_meta( $post->ID, $this->POST_DONE . 'all', true ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a flag locally to indicate that this post has already been Publicized via the selected
|
||||
* connections.
|
||||
*
|
||||
* @param int $post_ID Post ID.
|
||||
* @param WP_Post $post Post object.
|
||||
*/
|
||||
public function save_publicized( $post_ID, $post = null ) {
|
||||
if ( null === $post ) {
|
||||
return;
|
||||
}
|
||||
// Only do this when a post transitions to being published.
|
||||
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
if ( get_post_meta( $post->ID, $this->PENDING, false ) && $this->post_type_is_publicizeable( $post->post_type ) ) {
|
||||
delete_post_meta( $post->ID, $this->PENDING );
|
||||
update_post_meta( $post->ID, $this->POST_DONE . 'all', true );
|
||||
}
|
||||
// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
}
|
||||
|
||||
/**
|
||||
* Set post flags for Publicize.
|
||||
*
|
||||
* @param array $flags List of flags.
|
||||
* @param WP_Post $post Post object.
|
||||
* @return array
|
||||
*/
|
||||
public function set_post_flags( $flags, $post ) {
|
||||
$flags['publicize_post'] = false;
|
||||
if ( ! $this->post_type_is_publicizeable( $post->post_type ) ) {
|
||||
return $flags;
|
||||
}
|
||||
|
||||
$should_publicize = $this->should_submit_post_pre_checks( $post );
|
||||
|
||||
/** This filter is already documented in modules/publicize/publicize-jetpack.php */
|
||||
if ( ! apply_filters( 'publicize_should_publicize_published_post', $should_publicize, $post ) ) {
|
||||
return $flags;
|
||||
}
|
||||
|
||||
$connected_services = $this->get_all_connections();
|
||||
|
||||
if ( empty( $connected_services ) ) {
|
||||
return $flags;
|
||||
}
|
||||
|
||||
$flags['publicize_post'] = true;
|
||||
|
||||
return $flags;
|
||||
}
|
||||
}
|
||||
+633
@@ -0,0 +1,633 @@
|
||||
<?php
|
||||
/**
|
||||
* The Publicize Rest Controller class.
|
||||
* Registers the REST routes for Publicize.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize;
|
||||
|
||||
use Automattic\Jetpack\Connection\Rest_Authentication;
|
||||
use Automattic\Jetpack\Publicize\REST_API\Proxy_Requests;
|
||||
use Jetpack_Options;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* Registers the REST routes for Search.
|
||||
*/
|
||||
class REST_Controller {
|
||||
/**
|
||||
* Whether it's run on WPCOM.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $is_wpcom;
|
||||
|
||||
/**
|
||||
* Social Product Slugs
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const JETPACK_SOCIAL_V1_YEARLY = 'jetpack_social_v1_yearly';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param bool $is_wpcom - Whether it's run on WPCOM.
|
||||
*/
|
||||
public function __construct( $is_wpcom = false ) {
|
||||
$this->is_wpcom = $is_wpcom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the REST routes on the `rest_api_init` hook.
|
||||
*
|
||||
* Instantiated here, rather than eagerly, so the controller class only loads
|
||||
* on requests that reach `rest_api_init`. Static so the callback can be
|
||||
* unregistered.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public static function register() {
|
||||
( new self() )->register_rest_routes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the REST routes for Social.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
public function register_rest_routes() {
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/publicize/connection-test-results',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_publicize_connection_test_results' ),
|
||||
'permission_callback' => array( $this, 'require_author_privilege_callback' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/publicize/connections',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_publicize_connections' ),
|
||||
'permission_callback' => array( $this, 'require_author_privilege_callback' ),
|
||||
)
|
||||
);
|
||||
|
||||
// Get current social product from the product's endpoint.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/social-product-info',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_social_product_info' ),
|
||||
'permission_callback' => array( $this, 'require_admin_privilege_callback' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/publicize/(?P<postId>\d+)',
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'share_post' ),
|
||||
'permission_callback' => array( $this, 'require_author_privilege_callback' ),
|
||||
'args' => array(
|
||||
'message' => array(
|
||||
'description' => __( 'The message to share.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'validate_callback' => function ( $param ) {
|
||||
return is_string( $param );
|
||||
},
|
||||
'sanitize_callback' => 'sanitize_textarea_field',
|
||||
),
|
||||
'skipped_connections' => array(
|
||||
'description' => __( 'Array of external connection IDs to skip sharing.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'array',
|
||||
'required' => false,
|
||||
'validate_callback' => function ( $param ) {
|
||||
return is_array( $param );
|
||||
},
|
||||
'sanitize_callback' => function ( $param ) {
|
||||
if ( ! is_array( $param ) ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
esc_html__( 'The skipped_connections argument must be an array of connection IDs.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
return array_map( 'absint', $param );
|
||||
},
|
||||
),
|
||||
'async' => array(
|
||||
'description' => __( 'Whether to share the post asynchronously.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'boolean',
|
||||
'default' => false,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Create a Jetpack Social connection.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/social/connections',
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'create_publicize_connection' ),
|
||||
'permission_callback' => array( $this, 'require_author_privilege_callback' ),
|
||||
'schema' => array( $this, 'get_jetpack_social_connections_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
// Update a Jetpack Social connection.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/social/connections/(?P<connection_id>\d+)',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( $this, 'update_publicize_connection' ),
|
||||
'permission_callback' => array( $this, 'update_connection_permission_check' ),
|
||||
'schema' => array( $this, 'get_jetpack_social_connections_update_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
// Delete a Jetpack Social connection.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/social/connections/(?P<connection_id>\d+)',
|
||||
array(
|
||||
'methods' => WP_REST_Server::DELETABLE,
|
||||
'callback' => array( $this, 'delete_publicize_connection' ),
|
||||
'permission_callback' => array( $this, 'manage_connection_permission_check' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/social/sync-shares/post/(?P<id>\d+)',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( $this, 'update_post_shares' ),
|
||||
'permission_callback' => array( Rest_Authentication::class, 'is_signed_with_blog_token' ),
|
||||
'args' => array(
|
||||
'meta' => array(
|
||||
'type' => 'object',
|
||||
'required' => true,
|
||||
'properties' => array(
|
||||
'_publicize_shares' => array(
|
||||
'type' => 'array',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/social/share-status/(?P<post_id>\d+)',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_post_share_status' ),
|
||||
'permission_callback' => array( $this, 'require_author_privilege_callback' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage connection permission check
|
||||
*
|
||||
* @param WP_REST_Request $request The request object, which includes the parameters.
|
||||
*
|
||||
* @return bool True if the user can manage the connection, false otherwise.
|
||||
*/
|
||||
public function manage_connection_permission_check( WP_REST_Request $request ) {
|
||||
|
||||
if ( current_user_can( 'edit_others_posts' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Publicize instance.
|
||||
*
|
||||
* @var Publicize $publicize Publicize instance.
|
||||
*/
|
||||
global $publicize;
|
||||
|
||||
$connection = $publicize->get_connection_for_user( $request->get_param( 'connection_id' ) );
|
||||
|
||||
$owns_connection = isset( $connection['user_id'] ) && get_current_user_id() === (int) $connection['user_id'];
|
||||
|
||||
return $owns_connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update connection permission check.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object, which includes the parameters.
|
||||
*
|
||||
* @return bool True if the user can update the connection, false otherwise.
|
||||
*/
|
||||
public function update_connection_permission_check( WP_REST_Request $request ) {
|
||||
|
||||
// If the user cannot manage the connection, they can't update it either.
|
||||
if ( ! $this->manage_connection_permission_check( $request ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the connection is being marked/unmarked as shared.
|
||||
if ( $request->has_param( 'shared' ) ) {
|
||||
// Only editors and above can mark a connection as shared.
|
||||
return current_user_can( 'edit_others_posts' );
|
||||
}
|
||||
|
||||
return $this->require_author_privilege_callback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Only administrators can access the API.
|
||||
*
|
||||
* @return bool|WP_Error True if a blog token was used to sign the request, WP_Error otherwise.
|
||||
*/
|
||||
public function require_admin_privilege_callback() {
|
||||
return current_user_can( 'manage_options' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Only Authors can access the API.
|
||||
*
|
||||
* @return bool|WP_Error True if a blog token was used to sign the request, WP_Error otherwise.
|
||||
*/
|
||||
public function require_author_privilege_callback() {
|
||||
return current_user_can( 'publish_posts' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the JSON schema for creating a jetpack social connection.
|
||||
*
|
||||
* @return array Schema data.
|
||||
*/
|
||||
public function get_jetpack_social_connections_schema() {
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'jetpack-social-connection',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'keyring_connection_ID' => array(
|
||||
'description' => __( 'Keyring connection ID', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
),
|
||||
'external_user_ID' => array(
|
||||
'description' => __( 'External User Id - in case of services like Facebook', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'shared' => array(
|
||||
'description' => __( 'Whether the connection is shared with other users', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return rest_default_additional_properties_to_false( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the JSON schema for updating a jetpack social connection.
|
||||
*
|
||||
* @return array Schema data.
|
||||
*/
|
||||
public function get_jetpack_social_connections_update_schema() {
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'jetpack-social-connection',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'external_user_ID' => array(
|
||||
'description' => __( 'External User Id - in case of services like Facebook', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'shared' => array(
|
||||
'description' => __( 'Whether the connection is shared with other users', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return rest_default_additional_properties_to_false( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current Publicize connections, with the resolt of testing them, for the site.
|
||||
*
|
||||
* GET `jetpack/v4/publicize/connection-test-results`
|
||||
*
|
||||
* @deprecated 0.61.1
|
||||
*/
|
||||
public function get_publicize_connection_test_results() {
|
||||
|
||||
Publicize_Utils::endpoint_deprecated_warning(
|
||||
__METHOD__,
|
||||
'jetpack-14.4, jetpack-social-6.2.0',
|
||||
'jetpack/v4/publicize/connection-test-results',
|
||||
'wpcom/v2/publicize/connections?test_connections=1'
|
||||
);
|
||||
|
||||
$proxy = new Proxy_Requests( 'publicize/connections' );
|
||||
|
||||
$request = new WP_REST_Request( 'GET' );
|
||||
|
||||
$request->set_param( 'test_connections', '1' );
|
||||
|
||||
return rest_ensure_response( $proxy->proxy_request_to_wpcom_as_user( $request ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current Publicize connections for the site.
|
||||
*
|
||||
* GET `jetpack/v4/publicize/connections`
|
||||
*
|
||||
* @deprecated 0.61.1
|
||||
*
|
||||
* @param WP_REST_Request $request The request object, which includes the parameters.
|
||||
*/
|
||||
public function get_publicize_connections( $request ) {
|
||||
|
||||
Publicize_Utils::endpoint_deprecated_warning(
|
||||
__METHOD__,
|
||||
'jetpack-14.4, jetpack-social-6.2.0',
|
||||
'jetpack/v4/publicize/connections',
|
||||
'wpcom/v2/publicize/connections?test_connections=1'
|
||||
);
|
||||
|
||||
if ( $request->get_param( 'test_connections' ) ) {
|
||||
|
||||
$proxy = new Proxy_Requests( 'publicize/connections' );
|
||||
|
||||
return rest_ensure_response( $proxy->proxy_request_to_wpcom_as_user( $request ) );
|
||||
}
|
||||
|
||||
return rest_ensure_response( Connections::get_all_for_user() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a publicize connection
|
||||
*
|
||||
* @deprecated 0.61.1
|
||||
*
|
||||
* @param WP_REST_Request $request The request object, which includes the parameters.
|
||||
* @return WP_REST_Response|WP_Error True if the request was successful, or a WP_Error otherwise.
|
||||
*/
|
||||
public function create_publicize_connection( $request ) {
|
||||
|
||||
Publicize_Utils::endpoint_deprecated_warning(
|
||||
__METHOD__,
|
||||
'jetpack-14.4, jetpack-social-6.2.0',
|
||||
'jetpack/v4/social/connections',
|
||||
'wpcom/v2/publicize/connections'
|
||||
);
|
||||
|
||||
$proxy = new Proxy_Requests( 'publicize/connections' );
|
||||
|
||||
return rest_ensure_response(
|
||||
$proxy->proxy_request_to_wpcom_as_user( $request, '', array( 'timeout' => 120 ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the WPCOM endpoint to update the publicize connection.
|
||||
*
|
||||
* POST jetpack/v4/social/connections/{connection_id}
|
||||
*
|
||||
* @deprecated 0.61.1
|
||||
*
|
||||
* @param WP_REST_Request $request The request object, which includes the parameters.
|
||||
*/
|
||||
public function update_publicize_connection( $request ) {
|
||||
|
||||
Publicize_Utils::endpoint_deprecated_warning(
|
||||
__METHOD__,
|
||||
'jetpack-14.4, jetpack-social-6.2.0',
|
||||
'jetpack/v4/social/connections/:connection_id',
|
||||
'wpcom/v2/publicize/connections/:connection_id'
|
||||
);
|
||||
|
||||
$proxy = new Proxy_Requests( 'publicize/connections' );
|
||||
|
||||
$path = $request->get_param( 'connection_id' );
|
||||
|
||||
return rest_ensure_response(
|
||||
$proxy->proxy_request_to_wpcom_as_user( $request, $path, array( 'timeout' => 120 ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the WPCOM endpoint to delete the publicize connection.
|
||||
*
|
||||
* DELETE jetpack/v4/social/connections/{connection_id}
|
||||
*
|
||||
* @deprecated 0.61.1
|
||||
*
|
||||
* @param WP_REST_Request $request The request object, which includes the parameters.
|
||||
*/
|
||||
public function delete_publicize_connection( $request ) {
|
||||
|
||||
Publicize_Utils::endpoint_deprecated_warning(
|
||||
__METHOD__,
|
||||
'jetpack-14.4, jetpack-social-6.2.0',
|
||||
'jetpack/v4/social/connections/:connection_id',
|
||||
'wpcom/v2/publicize/connections/:connection_id'
|
||||
);
|
||||
|
||||
$proxy = new Proxy_Requests( 'publicize/connections' );
|
||||
|
||||
$path = $request->get_param( 'connection_id' );
|
||||
|
||||
return rest_ensure_response(
|
||||
$proxy->proxy_request_to_wpcom_as_user( $request, $path, array( 'timeout' => 120 ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets information about the current social product plans.
|
||||
*
|
||||
* @deprecated 0.63.0 Swapped to using the /my-jetpack/v1/site/products endpoint instead.
|
||||
*
|
||||
* @return string|WP_Error A JSON object of the current social product being if the request was successful, or a WP_Error otherwise.
|
||||
*/
|
||||
public static function get_social_product_info() {
|
||||
Publicize_Utils::endpoint_deprecated_warning(
|
||||
__METHOD__,
|
||||
'jetpack-14.6, jetpack-social-6.4.0',
|
||||
'jetpack/v4/social-product-info',
|
||||
'my-jetpack/v1/site/products?products=social'
|
||||
);
|
||||
|
||||
$request_url = 'https://public-api.wordpress.com/rest/v1.1/products?locale=' . get_user_locale() . '&type=jetpack';
|
||||
$wpcom_request = wp_remote_get( esc_url_raw( $request_url ) );
|
||||
$response_code = wp_remote_retrieve_response_code( $wpcom_request );
|
||||
|
||||
if ( 200 !== $response_code ) {
|
||||
// Something went wrong so we'll just return the response without caching.
|
||||
return new WP_Error(
|
||||
'failed_to_fetch_data',
|
||||
esc_html__( 'Unable to fetch the requested data.', 'jetpack-publicize-pkg' ),
|
||||
array(
|
||||
'status' => $response_code,
|
||||
'request' => $wpcom_request,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$products = json_decode( wp_remote_retrieve_body( $wpcom_request ) );
|
||||
return array(
|
||||
'v1' => $products->{self::JETPACK_SOCIAL_V1_YEARLY},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the WPCOM endpoint to reshare the post.
|
||||
*
|
||||
* POST jetpack/v4/publicize/(?P<postId>\d+)
|
||||
*
|
||||
* @deprecated 0.61.2
|
||||
*
|
||||
* @param WP_REST_Request $request The request object, which includes the parameters.
|
||||
*/
|
||||
public function share_post( $request ) {
|
||||
$post_id = $request->get_param( 'postId' );
|
||||
|
||||
Publicize_Utils::endpoint_deprecated_warning(
|
||||
__METHOD__,
|
||||
'jetpack-14.4.1, jetpack-social-6.2.0',
|
||||
'jetpack/v4/publicize/:postId',
|
||||
'wpcom/v2/publicize/share-post/:postId'
|
||||
);
|
||||
|
||||
$proxy = new Proxy_Requests( 'publicize/share-post' );
|
||||
|
||||
return rest_ensure_response(
|
||||
$proxy->proxy_request_to_wpcom_as_user( $request, $post_id )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward remote response to client with error handling.
|
||||
*
|
||||
* @param array|WP_Error $response - Response from WPCOM.
|
||||
*/
|
||||
public function make_proper_response( $response ) {
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
$status_code = wp_remote_retrieve_response_code( $response );
|
||||
|
||||
if ( 200 === $status_code ) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
isset( $body['error'] ) ? 'remote-error-' . $body['error'] : 'remote-error',
|
||||
$body['message'] ?? 'unknown remote error',
|
||||
array( 'status' => $status_code )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get blog id
|
||||
*/
|
||||
protected function get_blog_id() {
|
||||
return $this->is_wpcom ? get_current_blog_id() : Jetpack_Options::get_option( 'id' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the post with information about shares.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
*/
|
||||
public function update_post_shares( $request ) {
|
||||
|
||||
Publicize_Utils::endpoint_deprecated_warning(
|
||||
__METHOD__,
|
||||
'jetpack-14.6, jetpack-social-6.4.0',
|
||||
'jetpack/v4/social/sync-shares/post/:id',
|
||||
'wpcom/v2/publicize/share-status/sync'
|
||||
);
|
||||
|
||||
$request_body = $request->get_json_params();
|
||||
|
||||
$post_id = $request->get_param( 'id' );
|
||||
$post_meta = $request_body['meta'];
|
||||
$post = get_post( $post_id );
|
||||
|
||||
if ( $post && 'publish' === $post->post_status && isset( $post_meta[ Share_Status::SHARES_META_KEY ] ) ) {
|
||||
update_post_meta( $post_id, Share_Status::SHARES_META_KEY, $post_meta[ Share_Status::SHARES_META_KEY ] );
|
||||
$urls = array();
|
||||
foreach ( $post_meta[ Share_Status::SHARES_META_KEY ] as $share ) {
|
||||
if ( isset( $share['status'] ) && 'success' === $share['status'] ) {
|
||||
$urls[] = array(
|
||||
'url' => $share['message'],
|
||||
'service' => $share['service'],
|
||||
);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Fires after Publicize Shares post meta has been saved.
|
||||
*
|
||||
* @param array $urls {
|
||||
* An array of social media shares.
|
||||
* @type array $url URL to the social media post.
|
||||
* @type string $service Social media service shared to.
|
||||
* }
|
||||
*/
|
||||
do_action( 'jetpack_publicize_share_urls_saved', $urls );
|
||||
return rest_ensure_response( new WP_REST_Response() );
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'rest_cannot_edit',
|
||||
__( 'Failed to update the post meta', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the share status for a post.
|
||||
*
|
||||
* GET `jetpack/v4/social/share-status/<post_id>`
|
||||
*
|
||||
* @deprecated 0.63.0
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*/
|
||||
public function get_post_share_status( WP_REST_Request $request ) {
|
||||
$post_id = $request->get_param( 'post_id' );
|
||||
|
||||
Publicize_Utils::endpoint_deprecated_warning(
|
||||
__METHOD__,
|
||||
'jetpack-14.6, jetpack-social-6.4.0',
|
||||
'jetpack/v4/social/share-status/:postId',
|
||||
'wpcom/v2/publicize/share-status'
|
||||
);
|
||||
|
||||
return rest_ensure_response( Share_Status::get_post_share_status( $post_id ) );
|
||||
}
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
/**
|
||||
* Publicize Services class.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize;
|
||||
|
||||
use Automattic\Jetpack\Publicize\REST_API\Proxy_Requests;
|
||||
use WP_REST_Request;
|
||||
|
||||
/**
|
||||
* Publicize Services class.
|
||||
*/
|
||||
class Services {
|
||||
|
||||
const SERVICES_TRANSIENT = 'jetpack_social_services_list_v2';
|
||||
|
||||
/**
|
||||
* Get the available publicize services. Meant to be called directly only on WPCOM.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function wpcom_get_all() {
|
||||
// Ensure that we are on WPCOM.
|
||||
Publicize_Utils::assert_is_wpcom( __METHOD__ );
|
||||
|
||||
require_lib( 'external-connections' );
|
||||
|
||||
$external_connections = \WPCOM_External_Connections::init();
|
||||
|
||||
$services = $external_connections->get_external_services_list( 'publicize', get_current_blog_id() );
|
||||
|
||||
$items = array();
|
||||
|
||||
foreach ( $services as $service ) {
|
||||
// Set the fields as per the schema in Services_Controller.
|
||||
$items[] = array(
|
||||
'id' => $service['ID'],
|
||||
'description' => $service['description'],
|
||||
'label' => $service['label'],
|
||||
'status' => $service['status'] ?? 'ok',
|
||||
'supports' => array(
|
||||
'additional_users' => $service['multiple_external_user_ID_support'],
|
||||
'additional_users_only' => $service['external_users_only'],
|
||||
),
|
||||
'url' => $service['connect_URL'],
|
||||
);
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all services.
|
||||
*
|
||||
* @param array $args Arguments
|
||||
* - 'ignore_cache': bool Whether to ignore the cache and fetch the connections from the API.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_all( $args = array() ) {
|
||||
|
||||
if ( Publicize_Utils::is_wpcom() ) {
|
||||
$services = self::wpcom_get_all();
|
||||
} else {
|
||||
|
||||
$ignore_cache = $args['ignore_cache'] ?? false;
|
||||
|
||||
$services = get_transient( self::SERVICES_TRANSIENT );
|
||||
|
||||
if ( $ignore_cache || false === $services ) {
|
||||
$services = self::fetch_and_cache_services();
|
||||
}
|
||||
// This is here for backwards compatibility
|
||||
// TODO Remove this array_map() call after April 2025 release of Jetpack.
|
||||
return array_map(
|
||||
function ( $service ) {
|
||||
global $publicize;
|
||||
|
||||
return array_merge(
|
||||
$service,
|
||||
array(
|
||||
'ID' => $service['id'],
|
||||
'connect_URL' => $publicize->connect_url( $service['id'], 'connect' ),
|
||||
'external_users_only' => $service['supports']['additional_users_only'],
|
||||
'multiple_external_user_ID_support' => $service['supports']['additional_users'],
|
||||
)
|
||||
);
|
||||
},
|
||||
$services
|
||||
);
|
||||
}
|
||||
|
||||
return $services;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch services from the REST API and cache them.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function fetch_and_cache_services() {
|
||||
$proxy = new Proxy_Requests( 'publicize/services' );
|
||||
|
||||
$request = new WP_REST_Request( 'GET' );
|
||||
|
||||
$response = $proxy->proxy_request_to_wpcom_as_user( $request );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
// @todo log error.
|
||||
return array();
|
||||
}
|
||||
|
||||
if ( is_array( $response ) ) {
|
||||
/**
|
||||
* Let us set the connect URL to null.
|
||||
*
|
||||
* Reason:
|
||||
* We do not want to cache the connect URL, as it's user-specific,
|
||||
* but the services are for all users.
|
||||
* The intention is to get the connect URL on demand via an API call when needed.
|
||||
*/
|
||||
$services = array_map(
|
||||
function ( $service ) {
|
||||
return array_merge(
|
||||
$service,
|
||||
array(
|
||||
'url' => null,
|
||||
)
|
||||
);
|
||||
},
|
||||
$response
|
||||
);
|
||||
|
||||
if ( ! set_transient( self::SERVICES_TRANSIENT, $services, DAY_IN_SECONDS ) ) {
|
||||
// If the transient has beeen set in another request, the call to set_transient can fail.
|
||||
// If so, we can delete the transient and try again.
|
||||
self::clear_cache();
|
||||
|
||||
set_transient( self::SERVICES_TRANSIENT, $services, DAY_IN_SECONDS );
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the services cache.
|
||||
*/
|
||||
public static function clear_cache() {
|
||||
delete_transient( self::SERVICES_TRANSIENT );
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* Publicize Share Status class.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize;
|
||||
|
||||
/**
|
||||
* Publicize Services class.
|
||||
*/
|
||||
class Share_Status {
|
||||
const SHARES_META_KEY = '_publicize_shares';
|
||||
|
||||
/**
|
||||
* Gets the share status for a post.
|
||||
*
|
||||
* @param int $post_id The post ID.
|
||||
* @param bool $filter_by_access Whether to filter the shares by access.
|
||||
*/
|
||||
public static function get_post_share_status( $post_id, $filter_by_access = true ) {
|
||||
|
||||
$post = get_post( $post_id );
|
||||
|
||||
if ( empty( $post ) || ! metadata_exists( 'post', $post_id, self::SHARES_META_KEY ) ) {
|
||||
return array(
|
||||
'done' => false,
|
||||
'shares' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
$shares = get_post_meta( $post_id, self::SHARES_META_KEY, true );
|
||||
|
||||
// If the data is in an associative array format, we fetch it without true to get all the shares.
|
||||
// This is needed to support the old WPCOM format.
|
||||
if ( isset( $shares ) && is_array( $shares ) && ! array_is_list( $shares ) ) {
|
||||
$shares = get_post_meta( $post_id, self::SHARES_META_KEY, false );
|
||||
}
|
||||
|
||||
// Better safe than sorry.
|
||||
if ( empty( $shares ) ) {
|
||||
$shares = array();
|
||||
}
|
||||
|
||||
if ( $filter_by_access ) {
|
||||
// The site could have multiple admins, editors and authors connected. Load shares information that only the current user has access to.
|
||||
$connection_ids = wp_list_pluck( Connections::get_all_for_user(), 'connection_id', 'connection_id' );
|
||||
|
||||
$shares = array_filter(
|
||||
$shares,
|
||||
function ( $share ) use ( $connection_ids ) {
|
||||
return isset( $share['connection_id'] ) && isset( $connection_ids[ $share['connection_id'] ] );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
usort(
|
||||
$shares,
|
||||
function ( $a, $b ) {
|
||||
return $b['timestamp'] - $a['timestamp'];
|
||||
}
|
||||
);
|
||||
|
||||
$shares = array_values( $shares );
|
||||
|
||||
return array(
|
||||
'shares' => $shares,
|
||||
'done' => true,
|
||||
);
|
||||
}
|
||||
}
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
<?php
|
||||
/**
|
||||
* Social Admin Page class.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize;
|
||||
|
||||
use Automattic\Jetpack\Admin_UI\Admin_Menu;
|
||||
use Automattic\Jetpack\Assets;
|
||||
use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\Current_Plan;
|
||||
use Automattic\Jetpack\Publicize\Publicize_Utils as Utils;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
|
||||
/**
|
||||
* The class to handle the Social Admin Page.
|
||||
*/
|
||||
class Social_Admin_Page {
|
||||
|
||||
/**
|
||||
* Nonce action used when refreshing plan data.
|
||||
*/
|
||||
public const REFRESH_PLAN_NONCE_ACTION = 'jetpack_social_refresh_plan_data';
|
||||
|
||||
/**
|
||||
* Filter name that gates the wp-build–based dashboard.
|
||||
*
|
||||
* When this filter returns true, "Jetpack > Social" renders the new
|
||||
* wp-build dashboard (Overview + Settings tabs) instead of the legacy
|
||||
* single-page React app.
|
||||
*/
|
||||
const MODERNIZATION_FILTER = 'rsm_jetpack_ui_modernization_social';
|
||||
|
||||
/**
|
||||
* The instance of the class.
|
||||
*
|
||||
* @var Social_Admin_Page
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* Initialize the class.
|
||||
*
|
||||
* @return Social_Admin_Page
|
||||
*/
|
||||
public static function init() {
|
||||
if ( ! isset( self::$instance ) ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*/
|
||||
private function __construct() {
|
||||
// Defer wp-build loading to admin_menu (priority 1) so the modernization
|
||||
// filter — which third parties typically register from a plugins_loaded
|
||||
// or init callback (e.g. via Code Snippets) — has been applied before we
|
||||
// read it, and so the wp-build render function is defined before
|
||||
// `add_menu` (priority 10) reads `function_exists()`.
|
||||
add_action( 'admin_menu', array( __CLASS__, 'maybe_load_wp_build' ), 1 );
|
||||
add_action( 'admin_menu', array( $this, 'add_menu' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load wp-build for the Social admin page when modernization is enabled.
|
||||
*
|
||||
* Hooked to `admin_menu` priority 1 so the modernization filter has been
|
||||
* registered by any opt-in code (mu-plugins, snippets, themes) before we
|
||||
* read it, and so the wp-build render function and enqueue hook are in
|
||||
* place before `add_menu()` runs at the default priority.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function maybe_load_wp_build() {
|
||||
if ( ! self::is_modernized() || ! self::is_social_admin_request() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::load_wp_build();
|
||||
add_action( 'current_screen', array( __CLASS__, 'alias_screen_id_for_wp_build' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the admin menu.
|
||||
*/
|
||||
public function add_menu() {
|
||||
|
||||
// Remove the old Social menu item, if it exists.
|
||||
Admin_Menu::remove_menu( 'jetpack-social' );
|
||||
|
||||
// If this isn't an admin (or someone with the capability to change the module status )
|
||||
// and Publicize is inactive, then don't render the admin page.
|
||||
if ( ! current_user_can( 'manage_options' ) && ! Utils::is_publicize_active() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't need Jetpack connection on WP.com.
|
||||
$needs_site_connection = ! ( new Host() )->is_wpcom_platform() && ! ( new Connection_Manager() )->is_connected();
|
||||
|
||||
/**
|
||||
* If the Jetpack Social plugin is not active,
|
||||
* we want to hide the menu if the site is not connected.
|
||||
*/
|
||||
if ( ! defined( 'JETPACK_SOCIAL_PLUGIN_DIR' ) && $needs_site_connection ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$callback = self::is_wp_build_dashboard_active()
|
||||
? 'jetpack_social_jetpack_social_dashboard_wp_admin_render_page'
|
||||
: array( $this, 'render' );
|
||||
|
||||
$page_suffix = Admin_Menu::add_menu(
|
||||
/** "Jetpack Social" is a product name, do not translate. */
|
||||
'Jetpack Social',
|
||||
'Social',
|
||||
'publish_posts',
|
||||
'jetpack-social',
|
||||
$callback,
|
||||
4
|
||||
);
|
||||
|
||||
add_action( 'load-' . $page_suffix, array( $this, 'admin_init' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the admin resources.
|
||||
*/
|
||||
public function admin_init() {
|
||||
// Refresh data if coming from purchase to ensure it is up to date
|
||||
// without making API calls on every admin page load.
|
||||
if ( isset( $_GET['refresh_plan_data'] ) ) {
|
||||
check_admin_referer( self::REFRESH_PLAN_NONCE_ACTION );
|
||||
if ( apply_filters( 'jetpack_social_should_refresh_plan_data', true ) ) {
|
||||
Current_Plan::refresh_from_wpcom();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use priority 20 to ensure that we can dequeue the old Social assets.
|
||||
*/
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ), 20 );
|
||||
|
||||
// Initialize the media library for the social image generator.
|
||||
wp_enqueue_media();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the admin page.
|
||||
*/
|
||||
public function render() {
|
||||
?>
|
||||
<div id="jetpack-social-root"></div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue admin scripts and styles.
|
||||
*/
|
||||
public function enqueue_admin_scripts() {
|
||||
// This callback is registered via `load-{$page_suffix}` in `add_menu()`,
|
||||
// so it only fires on the Social admin page — no need to re-check the page here.
|
||||
//
|
||||
// Gate on `is_wp_build_dashboard_active()` (not `is_modernized()` alone) so
|
||||
// this mirrors the exact decision `add_menu()` made when choosing the menu
|
||||
// callback: when modernization is on AND the wp-build render function is defined
|
||||
// (i.e. the chassis was actually loaded), skip the legacy bundle entirely.
|
||||
if ( self::is_wp_build_dashboard_active() ) {
|
||||
// wp-build manages its own enqueue pipeline. The legacy script,
|
||||
// localized config, and media-library bootstrap are intentionally
|
||||
// skipped here.
|
||||
//
|
||||
// The chassis reads site-connection state via `useConnection()`, whose
|
||||
// store has no REST resolver — it must be hydrated inline. The wp-build
|
||||
// boot registers a classic `…-prerequisites` script that loads before the
|
||||
// chassis module, so attach the connection initial state there.
|
||||
if ( wp_script_is( 'jetpack-social-dashboard-wp-admin-prerequisites', 'registered' ) ) {
|
||||
Connection_Initial_State::render_script( 'jetpack-social-dashboard-wp-admin-prerequisites' );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Publicize_Assets::register_wp_build_polyfills();
|
||||
|
||||
// Dequeue the old Social assets.
|
||||
wp_dequeue_script( 'jetpack-social' );
|
||||
wp_dequeue_style( 'jetpack-social' );
|
||||
|
||||
Assets::register_script(
|
||||
'social-admin-page',
|
||||
'../build/social-admin-page.js',
|
||||
__FILE__,
|
||||
array(
|
||||
'in_footer' => true,
|
||||
'textdomain' => 'jetpack-publicize-pkg',
|
||||
'enqueue' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the wp-build entry file and register its polyfills.
|
||||
*
|
||||
* Only called on `?page=jetpack-social` admin requests when the
|
||||
* modernization filter is enabled. Keeps wp-build off every other request.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function load_wp_build() {
|
||||
$build_index = dirname( __DIR__ ) . '/build/build.php';
|
||||
|
||||
if ( ! file_exists( $build_index ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
require_once $build_index;
|
||||
|
||||
// The wp-build dashboard (unlike the Social bundles) uses the full polyfill set:
|
||||
// the @wordpress/boot|route|a11y modules, wp-notices, wp-views, etc.
|
||||
if ( ! class_exists( '\Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
\Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills::register(
|
||||
'jetpack-social',
|
||||
array_merge(
|
||||
\Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills::SCRIPT_HANDLES,
|
||||
\Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills::MODULE_IDS
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias the current screen ID to satisfy wp-build's auto-generated enqueue check.
|
||||
*
|
||||
* The wp-build `<page>-wp-admin` enqueue callback fires only when the screen ID
|
||||
* matches the wp-build page slug (`jetpack-social-dashboard`). Our wp-admin menu
|
||||
* slug stays `jetpack-social`, so we mutate the screen object in place to make
|
||||
* the check pass without changing the user-facing URL.
|
||||
*
|
||||
* Hooked only when modernization is on AND we're on the Social admin page,
|
||||
* so this never affects any other request.
|
||||
*
|
||||
* @param \WP_Screen|null $screen The current screen object (passed by WP).
|
||||
* @return void
|
||||
*/
|
||||
public static function alias_screen_id_for_wp_build( $screen ) {
|
||||
if ( ! is_object( $screen ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$screen->id = 'jetpack-social-dashboard';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when the wp-build modernization filter is enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function is_modernized() {
|
||||
return (bool) apply_filters( self::MODERNIZATION_FILTER, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when the wp-build dashboard is the page that will actually render.
|
||||
*
|
||||
* This is the single source of truth shared by `add_menu()` (which picks the
|
||||
* menu callback) and `enqueue_admin_scripts()` (which decides whether to skip
|
||||
* the legacy bundle). `maybe_load_wp_build()` only loads the wp-build entry —
|
||||
* and therefore only defines its render function — when modernization is on AND
|
||||
* we are on the Social admin page. Checking `function_exists()` here captures
|
||||
* both conditions in one place, so the callback and the enqueue gate can never
|
||||
* diverge and leave an empty `#jetpack-social-root`.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function is_wp_build_dashboard_active() {
|
||||
return self::is_modernized() && function_exists( 'jetpack_social_jetpack_social_dashboard_wp_admin_render_page' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when the current request targets the Social admin page.
|
||||
*
|
||||
* Used to scope wp-build loading to the one page that needs it. The
|
||||
* `$_GET['page']` value is populated by wp-admin/admin.php before any of
|
||||
* our hooks fire, so this check is reliable from the constructor onwards.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function is_social_admin_request() {
|
||||
if ( ! is_admin() || ! isset( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return false;
|
||||
}
|
||||
|
||||
return sanitize_text_field( wp_unslash( $_GET['page'] ) ) === 'jetpack-social'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
}
|
||||
}
|
||||
+496
@@ -0,0 +1,496 @@
|
||||
<?php
|
||||
/**
|
||||
* Settings class.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize\Jetpack_Social_Settings;
|
||||
|
||||
use Automattic\Jetpack\Modules;
|
||||
use Automattic\Jetpack\Publicize\Social_Image_Generator\Templates;
|
||||
|
||||
/**
|
||||
* This class is used to get and update Jetpack_Social_Settings.
|
||||
* Currently supported features:
|
||||
* - Social Image Generator
|
||||
* - UTM Settings
|
||||
* - Social Notes
|
||||
*
|
||||
* @phan-constructor-used-for-side-effects
|
||||
*/
|
||||
class Settings {
|
||||
/**
|
||||
* Name of the database option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const OPTION_PREFIX = 'jetpack_social_';
|
||||
const IMAGE_GENERATOR_SETTINGS = 'image_generator_settings';
|
||||
|
||||
const DEFAULT_IMAGE_GENERATOR_SETTINGS = array(
|
||||
'enabled' => false,
|
||||
'template' => Templates::DEFAULT_TEMPLATE,
|
||||
);
|
||||
|
||||
const UTM_SETTINGS = 'utm_settings';
|
||||
|
||||
const DEFAULT_UTM_SETTINGS = array(
|
||||
'enabled' => false,
|
||||
);
|
||||
|
||||
const NOTES_CONFIG = 'notes_config';
|
||||
|
||||
const DEFAULT_NOTES_CONFIG = array(
|
||||
'append_link' => true,
|
||||
);
|
||||
|
||||
const MESSAGE_TEMPLATE = 'message_template';
|
||||
|
||||
/**
|
||||
* Default global message template.
|
||||
*/
|
||||
const DEFAULT_MESSAGE_TEMPLATE = "{title}\n\n{excerpt}\n\n{url}";
|
||||
|
||||
/**
|
||||
* Storage cap for message templates, in characters. Real-world templates are a few hundred characters at most.
|
||||
*/
|
||||
const MESSAGE_TEMPLATE_MAX_LENGTH = 8000;
|
||||
|
||||
// Legacy named options.
|
||||
const JETPACK_SOCIAL_NOTE_CPT_ENABLED = 'jetpack-social-note';
|
||||
const JETPACK_SOCIAL_SHOW_PRICING_PAGE = 'jetpack-social_show_pricing_page';
|
||||
const NOTES_FLUSH_REWRITE_RULES_FLUSHED = 'jetpack_social_rewrite_rules_flushed';
|
||||
|
||||
/**
|
||||
* Whether the actions have been hooked into.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $actions_hooked_in = false;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
if ( ! self::$actions_hooked_in ) {
|
||||
add_action( 'rest_api_init', array( $this, 'register_settings' ) );
|
||||
add_action( 'admin_init', array( $this, 'register_settings' ) );
|
||||
|
||||
self::$actions_hooked_in = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate old options to the new settings. Previously SIG settings were stored in the
|
||||
* jetpack_social_image_generator_settings option. Now they are stored in the jetpack_social_settings.
|
||||
*
|
||||
* TODO: Work out if this is possible on plugin upgrade
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function migrate_old_option() {
|
||||
// Delete the old options if they exist.
|
||||
if ( get_option( 'jetpack_social_settings' ) ) {
|
||||
delete_option( 'jetpack_social_settings' );
|
||||
}
|
||||
if ( get_option( 'jetpack_social_autoconvert_images' ) ) {
|
||||
delete_option( 'jetpack_social_autoconvert_images' );
|
||||
}
|
||||
|
||||
$sig_settings = get_option( 'jetpack_social_image_generator_settings' );
|
||||
// If the option is not set, we don't need to migrate.
|
||||
if ( false === $sig_settings ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$enabled = false;
|
||||
$template = Templates::DEFAULT_TEMPLATE;
|
||||
|
||||
if ( isset( $sig_settings['defaults']['template'] ) ) {
|
||||
$template = $sig_settings['defaults']['template'];
|
||||
}
|
||||
|
||||
if ( isset( $sig_settings['enabled'] ) ) {
|
||||
$enabled = $sig_settings['enabled'];
|
||||
}
|
||||
|
||||
if ( ! isset( $sig_settings['template'] ) ) {
|
||||
update_option(
|
||||
self::OPTION_PREFIX . self::IMAGE_GENERATOR_SETTINGS,
|
||||
array(
|
||||
'enabled' => $enabled,
|
||||
'template' => $template,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the settings.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_settings() {
|
||||
|
||||
register_setting(
|
||||
'jetpack_social',
|
||||
self::OPTION_PREFIX . self::IMAGE_GENERATOR_SETTINGS,
|
||||
array(
|
||||
'type' => 'object',
|
||||
'default' => array(
|
||||
'enabled' => false,
|
||||
'template' => Templates::DEFAULT_TEMPLATE,
|
||||
),
|
||||
'show_in_rest' => array(
|
||||
'schema' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'enabled' => array(
|
||||
'type' => 'boolean',
|
||||
),
|
||||
'template' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'font' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'default_image_id' => array(
|
||||
'type' => 'number',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_setting(
|
||||
'jetpack_social',
|
||||
self::OPTION_PREFIX . self::UTM_SETTINGS,
|
||||
array(
|
||||
'type' => 'boolean',
|
||||
'default' => array(
|
||||
'enabled' => false,
|
||||
),
|
||||
'show_in_rest' => array(
|
||||
'schema' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'enabled' => array(
|
||||
'type' => 'boolean',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_setting(
|
||||
'jetpack_social',
|
||||
self::JETPACK_SOCIAL_SHOW_PRICING_PAGE,
|
||||
array(
|
||||
'type' => 'boolean',
|
||||
'default' => true,
|
||||
'show_in_rest' => array(
|
||||
'schema' => array(
|
||||
'type' => 'boolean',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_setting(
|
||||
'jetpack_social',
|
||||
self::JETPACK_SOCIAL_NOTE_CPT_ENABLED,
|
||||
array(
|
||||
'type' => 'boolean',
|
||||
'default' => false,
|
||||
'show_in_rest' => array(
|
||||
'schema' => array(
|
||||
'type' => 'boolean',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_setting(
|
||||
'jetpack_social',
|
||||
self::OPTION_PREFIX . self::NOTES_CONFIG,
|
||||
array(
|
||||
'type' => 'object',
|
||||
'default' => self::DEFAULT_NOTES_CONFIG,
|
||||
'show_in_rest' => array(
|
||||
'schema' => array(
|
||||
'type' => 'object',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'properties' => array(
|
||||
'append_link' => array(
|
||||
'type' => 'boolean',
|
||||
),
|
||||
'link_format' => array(
|
||||
'type' => 'string',
|
||||
'enum' => array( 'full_url', 'shortlink', 'permashortcitation' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_setting(
|
||||
'jetpack_social',
|
||||
self::OPTION_PREFIX . self::MESSAGE_TEMPLATE,
|
||||
array(
|
||||
'type' => 'string',
|
||||
'default' => self::DEFAULT_MESSAGE_TEMPLATE,
|
||||
'sanitize_callback' => array( __CLASS__, 'sanitize_message_template' ),
|
||||
'show_in_rest' => array(
|
||||
'schema' => array(
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
add_filter( 'rest_pre_update_setting', array( $this, 'update_settings' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a user-authored message template string.
|
||||
*
|
||||
* @param mixed $value The raw setting input. Non-string values coerce to ''.
|
||||
* @return string The sanitised template.
|
||||
*/
|
||||
public static function sanitize_message_template( $value ) {
|
||||
if ( ! is_string( $value ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$value = sanitize_textarea_field( $value );
|
||||
|
||||
if ( mb_strlen( $value, 'UTF-8' ) > self::MESSAGE_TEMPLATE_MAX_LENGTH ) {
|
||||
$value = mb_substr( $value, 0, self::MESSAGE_TEMPLATE_MAX_LENGTH, 'UTF-8' );
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the image generator settings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_image_generator_settings() {
|
||||
return get_option( self::OPTION_PREFIX . self::IMAGE_GENERATOR_SETTINGS, self::DEFAULT_IMAGE_GENERATOR_SETTINGS );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if the UTM params is enabled.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_utm_settings() {
|
||||
return get_option( self::OPTION_PREFIX . self::UTM_SETTINGS, self::DEFAULT_UTM_SETTINGS );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the global message template.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_message_template() {
|
||||
return (string) get_option( self::OPTION_PREFIX . self::MESSAGE_TEMPLATE, self::DEFAULT_MESSAGE_TEMPLATE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the social notes config.
|
||||
*
|
||||
* @return array The social notes config.
|
||||
*/
|
||||
public function get_social_notes_config() {
|
||||
return get_option( self::OPTION_PREFIX . self::NOTES_CONFIG, self::DEFAULT_NOTES_CONFIG );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the pricing page should be displayed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function should_show_pricing_page() {
|
||||
return (bool) get_option( self::JETPACK_SOCIAL_SHOW_PRICING_PAGE, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if the social notes feature is enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_social_notes_enabled() {
|
||||
return (bool) get_option( self::JETPACK_SOCIAL_NOTE_CPT_ENABLED, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current settings.
|
||||
*
|
||||
* @param bool $with_available Whether to include the available status of the features.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_settings( $with_available = false ) {
|
||||
$this->migrate_old_option();
|
||||
|
||||
$settings = array(
|
||||
'socialImageGeneratorSettings' => $this->get_image_generator_settings(),
|
||||
);
|
||||
|
||||
// The feature cannot be enabled without Publicize.
|
||||
if ( ! ( new Modules() )->is_active( 'publicize' ) ) {
|
||||
$settings['socialImageGeneratorSettings']['enabled'] = false;
|
||||
}
|
||||
|
||||
if ( $with_available ) {
|
||||
$settings['socialImageGeneratorSettings']['available'] = $this->is_sig_available();
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the initial state.
|
||||
* Deprecated method, stub left here to avoid fatal.
|
||||
*
|
||||
* @deprecated 0.62.0
|
||||
*/
|
||||
public function get_initial_state() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the settings.
|
||||
*
|
||||
* @param bool $updated The updated settings.
|
||||
* @param string $name The name of the setting.
|
||||
* @param mixed $value The value of the setting.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function update_settings( $updated, $name, $value ) {
|
||||
|
||||
// Social Image Generator.
|
||||
if ( self::OPTION_PREFIX . self::IMAGE_GENERATOR_SETTINGS === $name ) {
|
||||
return $this->update_social_image_generator_settings( $value );
|
||||
}
|
||||
|
||||
// UTM Settings.
|
||||
if ( self::OPTION_PREFIX . self::UTM_SETTINGS === $name ) {
|
||||
$current_utm_settings = $this->get_utm_settings();
|
||||
|
||||
if ( empty( $current_utm_settings ) || ! is_array( $current_utm_settings ) ) {
|
||||
$current_utm_settings = self::DEFAULT_UTM_SETTINGS;
|
||||
}
|
||||
|
||||
return update_option( self::OPTION_PREFIX . self::UTM_SETTINGS, array_replace_recursive( $current_utm_settings, $value ) );
|
||||
}
|
||||
|
||||
// Social Notes.
|
||||
if ( self::JETPACK_SOCIAL_NOTE_CPT_ENABLED === $name ) {
|
||||
// Delete this option, so the rules get flushed in maybe_flush_rewrite_rules when the CPT is registered.
|
||||
delete_option( self::NOTES_FLUSH_REWRITE_RULES_FLUSHED );
|
||||
return update_option( self::JETPACK_SOCIAL_NOTE_CPT_ENABLED, (bool) $value );
|
||||
}
|
||||
if ( self::OPTION_PREFIX . self::NOTES_CONFIG === $name ) {
|
||||
$old_config = $this->get_social_notes_config();
|
||||
$new_config = array_merge( $old_config, $value );
|
||||
return update_option( self::OPTION_PREFIX . self::NOTES_CONFIG, $new_config );
|
||||
}
|
||||
|
||||
if ( self::JETPACK_SOCIAL_SHOW_PRICING_PAGE === $name ) {
|
||||
return update_option( self::JETPACK_SOCIAL_SHOW_PRICING_PAGE, (int) $value );
|
||||
}
|
||||
|
||||
return $updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the social image generator settings.
|
||||
*
|
||||
* @param array $new_setting The new settings.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function update_social_image_generator_settings( $new_setting ) {
|
||||
$this->migrate_old_option();
|
||||
$sig_settings = get_option( self::OPTION_PREFIX . self::IMAGE_GENERATOR_SETTINGS );
|
||||
|
||||
if ( empty( $sig_settings ) || ! is_array( $sig_settings ) ) {
|
||||
$sig_settings = self::DEFAULT_IMAGE_GENERATOR_SETTINGS;
|
||||
}
|
||||
|
||||
return update_option( self::OPTION_PREFIX . self::IMAGE_GENERATOR_SETTINGS, array_replace_recursive( $sig_settings, $new_setting ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if SIG is available.
|
||||
*
|
||||
* @return bool True if SIG is available, false otherwise.
|
||||
*/
|
||||
public function is_sig_available() {
|
||||
global $publicize;
|
||||
|
||||
if ( ! $publicize ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $publicize->has_social_image_generator_feature();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default template.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function sig_get_default_template() {
|
||||
$this->migrate_old_option();
|
||||
$sig_settings = get_option( self::OPTION_PREFIX . self::IMAGE_GENERATOR_SETTINGS );
|
||||
if ( empty( $sig_settings ) || ! is_array( $sig_settings ) ) {
|
||||
$sig_settings = self::DEFAULT_IMAGE_GENERATOR_SETTINGS;
|
||||
}
|
||||
return $sig_settings['template'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default image ID.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function sig_get_default_image_id() {
|
||||
$this->migrate_old_option();
|
||||
$sig_settings = get_option( self::OPTION_PREFIX . self::IMAGE_GENERATOR_SETTINGS );
|
||||
if ( empty( $sig_settings ) || ! is_array( $sig_settings ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( isset( $sig_settings['default_image_id'] ) ) {
|
||||
return $sig_settings['default_image_id'];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default font.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function sig_get_default_font() {
|
||||
$this->migrate_old_option();
|
||||
$sig_settings = get_option( self::OPTION_PREFIX . self::IMAGE_GENERATOR_SETTINGS );
|
||||
if ( empty( $sig_settings ) || ! is_array( $sig_settings ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $sig_settings['font'] ?? '';
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/**
|
||||
* Base Controller class.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize\REST_API;
|
||||
|
||||
use Automattic\Jetpack\Publicize\Connections;
|
||||
use Automattic\Jetpack\Publicize\Publicize_Utils;
|
||||
use WP_Error;
|
||||
use WP_REST_Controller;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
/**
|
||||
* Base controller for Publicize endpoints.
|
||||
*/
|
||||
abstract class Base_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Whether to allow requests as blog.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $allow_requests_as_blog = false;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->wpcom_is_wpcom_only_endpoint = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the request is authorized for the blog.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function is_authorized_blog_request() {
|
||||
if ( Publicize_Utils::is_wpcom() && is_jetpack_site( get_current_blog_id() ) ) {
|
||||
|
||||
$jp_auth_endpoint = new \WPCOM_REST_API_V2_Endpoint_Jetpack_Auth();
|
||||
|
||||
return $jp_auth_endpoint->is_jetpack_authorized_for_site() === true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out data based on ?_fields= request parameter
|
||||
*
|
||||
* @param array $item Item to prepare.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
*
|
||||
* @return WP_REST_Response filtered item
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
|
||||
$response_data = array();
|
||||
foreach ( $item as $field => $value ) {
|
||||
if ( rest_is_field_included( $field, $fields ) ) {
|
||||
$response_data[ $field ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return rest_ensure_response( $response_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that user can access Publicize data
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
protected function publicize_permissions_check() {
|
||||
|
||||
global $publicize;
|
||||
|
||||
if ( ! $publicize ) {
|
||||
return new WP_Error(
|
||||
'publicize_not_available',
|
||||
__( 'Sorry, Jetpack Social is not available on your site right now.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
if ( $this->allow_requests_as_blog && self::is_authorized_blog_request() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
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 Jetpack Social data on this site.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the request is allowed to manage (update/delete) a connection.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return bool True if the request can manage connection, false otherwise.
|
||||
*/
|
||||
protected function manage_connection_permission_check( $request ) {
|
||||
// Editors and above can manage any connection.
|
||||
if ( current_user_can( 'edit_others_posts' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$connection_id = $request->get_param( 'connection_id' );
|
||||
|
||||
$connection = Connections::get_by_id( $connection_id );
|
||||
|
||||
return Connections::user_owns_connection( $connection );
|
||||
}
|
||||
}
|
||||
+526
@@ -0,0 +1,526 @@
|
||||
<?php
|
||||
/**
|
||||
* The Publicize Connections Controller class.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize\REST_API;
|
||||
|
||||
use Automattic\Jetpack\Connection\Traits\WPCOM_REST_API_Proxy_Request;
|
||||
use Automattic\Jetpack\Publicize\Connections;
|
||||
use Automattic\Jetpack\Publicize\Jetpack_Social_Settings\Settings;
|
||||
use Automattic\Jetpack\Publicize\Publicize_Utils;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use WP_REST_Server;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Connections Controller class.
|
||||
*
|
||||
* @phan-constructor-used-for-side-effects
|
||||
*/
|
||||
class Connections_Controller extends Base_Controller {
|
||||
|
||||
use WPCOM_REST_API_Proxy_Request;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
$this->base_api_path = 'wpcom';
|
||||
$this->version = 'v2';
|
||||
|
||||
$this->namespace = "{$this->base_api_path}/{$this->version}";
|
||||
$this->rest_base = 'publicize/connections';
|
||||
|
||||
$this->allow_requests_as_blog = true;
|
||||
|
||||
add_action( 'rest_api_init', array( $this, 'register_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the routes.
|
||||
*/
|
||||
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_permissions_check' ),
|
||||
'args' => array(
|
||||
'test_connections' => array(
|
||||
'type' => 'boolean',
|
||||
'description' => __( 'Whether to test connections.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'create_item' ),
|
||||
'permission_callback' => array( $this, 'create_item_permissions_check' ),
|
||||
'args' => array(
|
||||
'keyring_connection_ID' => array(
|
||||
'description' => __( 'Keyring connection ID.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
),
|
||||
'external_user_ID' => array(
|
||||
'description' => __( 'External User Id - in case of services like Facebook.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'shared' => array(
|
||||
'description' => __( 'Whether the connection is shared with other users.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<connection_id>[0-9]+)',
|
||||
array(
|
||||
'args' => array(
|
||||
'connection_id' => array(
|
||||
'description' => __( 'Unique identifier for the connection.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( $this, 'update_item' ),
|
||||
'permission_callback' => array( $this, 'update_item_permissions_check' ),
|
||||
'args' => array(
|
||||
'shared' => array(
|
||||
'description' => __( 'Whether the connection is shared with other users.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::DELETABLE,
|
||||
'callback' => array( $this, 'delete_item' ),
|
||||
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
|
||||
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema for the endpoint.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
$deprecated_fields = array(
|
||||
'id' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Unique identifier for the Jetpack Social connection.', 'jetpack-publicize-pkg' ) . ' ' . sprintf(
|
||||
/* translators: %s is the new field name */
|
||||
__( 'Deprecated in favor of %s.', 'jetpack-publicize-pkg' ),
|
||||
'connection_id'
|
||||
),
|
||||
),
|
||||
'username' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Username of the connected account.', 'jetpack-publicize-pkg' ) . ' ' . sprintf(
|
||||
/* translators: %s is the new field name */
|
||||
__( 'Deprecated in favor of %s.', 'jetpack-publicize-pkg' ),
|
||||
'external_handle'
|
||||
),
|
||||
),
|
||||
'profile_display_name' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'The name to display in the profile of the connected account.', 'jetpack-publicize-pkg' ) . ' ' . sprintf(
|
||||
/* translators: %s is the new field name */
|
||||
__( 'Deprecated in favor of %s.', 'jetpack-publicize-pkg' ),
|
||||
'display_name'
|
||||
),
|
||||
),
|
||||
'global' => array(
|
||||
'type' => 'boolean',
|
||||
'description' => __( 'Is this connection available to all users?', 'jetpack-publicize-pkg' ) . ' ' . sprintf(
|
||||
/* translators: %s is the new field name */
|
||||
__( 'Deprecated in favor of %s.', 'jetpack-publicize-pkg' ),
|
||||
'shared'
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'jetpack-publicize-connection',
|
||||
'type' => 'object',
|
||||
'properties' => array_merge(
|
||||
$deprecated_fields,
|
||||
self::get_the_item_schema()
|
||||
),
|
||||
);
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the schema for the connection item.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_the_item_schema() {
|
||||
return array(
|
||||
'connection_id' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Connection ID of the connected account.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'display_name' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Display name of the connected account.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'external_handle' => array(
|
||||
'type' => array( 'string', 'null' ),
|
||||
'description' => __( 'The external handle or username of the connected account.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'external_id' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'The external ID of the connected account.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'profile_link' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Profile link of the connected account.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'profile_picture' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'URL of the profile picture of the connected account.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'service_label' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Human-readable label for the Jetpack Social service.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'service_name' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Alphanumeric identifier for the Jetpack Social service.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'shared' => array(
|
||||
'type' => 'boolean',
|
||||
'description' => __( 'Whether the connection is shared with other users.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'status' => array(
|
||||
'type' => array( 'string', 'null' ),
|
||||
'description' => __( 'The connection status.', 'jetpack-publicize-pkg' ),
|
||||
'enum' => array(
|
||||
'ok',
|
||||
'broken',
|
||||
'must_reauth',
|
||||
null,
|
||||
),
|
||||
),
|
||||
'template' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Per-connection message template override. Empty string means fall back to the global template.', 'jetpack-publicize-pkg' ),
|
||||
'default' => '',
|
||||
'maxLength' => Settings::MESSAGE_TEMPLATE_MAX_LENGTH,
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => array( Settings::class, 'sanitize_message_template' ),
|
||||
),
|
||||
),
|
||||
'wpcom_user_id' => array(
|
||||
'type' => 'integer',
|
||||
'description' => __( 'wordpress.com ID of the user the connection belongs to.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the request has access to connectoins list.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public function get_items_permissions_check( $request ) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return $this->publicize_permissions_check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of connected Publicize connections.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
*
|
||||
* @return WP_REST_Response suitable for 1-page collection
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
if ( Publicize_Utils::is_wpcom() ) {
|
||||
$args = array(
|
||||
'context' => self::is_authorized_blog_request() ? 'blog' : 'user',
|
||||
'test_connections' => $request->get_param( 'test_connections' ),
|
||||
);
|
||||
|
||||
$connections = Connections::wpcom_get_connections( $args );
|
||||
} else {
|
||||
$connections = $this->proxy_request_to_wpcom_as_user( $request );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $connections ) ) {
|
||||
return $connections;
|
||||
}
|
||||
|
||||
$items = array();
|
||||
|
||||
foreach ( $connections as $item ) {
|
||||
$data = $this->prepare_item_for_response( $item, $request );
|
||||
|
||||
$items[] = $this->prepare_response_for_collection( $data );
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $items );
|
||||
$response->header( 'X-WP-Total', (string) count( $items ) );
|
||||
$response->header( 'X-WP-TotalPages', '1' );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to create a connection.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
|
||||
*/
|
||||
public function create_item_permissions_check( $request ) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
$permissions = parent::publicize_permissions_check();
|
||||
|
||||
if ( is_wp_error( $permissions ) ) {
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
return current_user_can( 'publish_posts' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new connection.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function create_item( $request ) {
|
||||
if ( Publicize_Utils::is_wpcom() ) {
|
||||
|
||||
$input = array(
|
||||
'keyring_connection_ID' => $request->get_param( 'keyring_connection_ID' ),
|
||||
'shared' => $request->get_param( 'shared' ),
|
||||
);
|
||||
|
||||
$external_user_id = $request->get_param( 'external_user_ID' );
|
||||
if ( ! empty( $external_user_id ) ) {
|
||||
$input['external_user_ID'] = $external_user_id;
|
||||
}
|
||||
|
||||
$result = Connections::wpcom_create_connection( $input );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$connection = Connections::get_by_id( $result );
|
||||
|
||||
$response = $this->prepare_item_for_response( $connection, $request );
|
||||
$response = rest_ensure_response( $response );
|
||||
|
||||
$response->set_status( 201 );
|
||||
|
||||
return $response;
|
||||
|
||||
}
|
||||
|
||||
$response = $this->proxy_request_to_wpcom_as_user( $request, '', array( 'timeout' => 120 ) );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return new WP_Error(
|
||||
'jp_connection_update_failed',
|
||||
__( 'Something went wrong while creating a connection.', 'jetpack-publicize-pkg' ),
|
||||
$response->get_error_message()
|
||||
);
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $response );
|
||||
|
||||
$response->set_status( 201 );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to update a connection.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
|
||||
*/
|
||||
public function update_item_permissions_check( $request ) {
|
||||
$permissions = parent::publicize_permissions_check();
|
||||
|
||||
if ( is_wp_error( $permissions ) ) {
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
// If the user cannot manage the connection, they can't update it either.
|
||||
if ( ! $this->manage_connection_permission_check( $request ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_edit',
|
||||
__( 'Sorry, you are not allowed to update this connection.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
// If the connection is being marked/unmarked as shared.
|
||||
if ( $request->has_param( 'shared' ) ) {
|
||||
// Only editors and above can mark a connection as shared.
|
||||
return current_user_can( 'edit_others_posts' );
|
||||
}
|
||||
|
||||
return current_user_can( 'publish_posts' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a connection.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function update_item( $request ) {
|
||||
$connection_id = $request->get_param( 'connection_id' );
|
||||
|
||||
if ( Publicize_Utils::is_wpcom() ) {
|
||||
|
||||
$input = array(
|
||||
'shared' => $request->get_param( 'shared' ),
|
||||
);
|
||||
|
||||
if ( $request->has_param( 'template' ) ) {
|
||||
require_lib( 'publicize/util/message-templates' );
|
||||
|
||||
$template_value = Settings::sanitize_message_template( $request->get_param( 'template' ) );
|
||||
|
||||
/**
|
||||
* Only gate non-empty values. Clearing an existing override
|
||||
* must be allowed regardless of plan — otherwise users who
|
||||
* downgrade can't remove a previously-set template.
|
||||
*/
|
||||
if ( '' !== $template_value && ! \Publicize\can_use_per_connection_templates() ) {
|
||||
return new WP_Error(
|
||||
'rest_forbidden_per_connection_template',
|
||||
__( 'Per-connection message templates require an upgraded plan.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
$input['template'] = $template_value;
|
||||
}
|
||||
|
||||
$result = Connections::wpcom_update_connection( $connection_id, $input );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$connection = Connections::get_by_id( $connection_id );
|
||||
|
||||
$response = $this->prepare_item_for_response( $connection, $request );
|
||||
$response = rest_ensure_response( $response );
|
||||
|
||||
$response->set_status( 201 );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response = $this->proxy_request_to_wpcom_as_user( $request, $connection_id, array( 'timeout' => 120 ) );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return new WP_Error(
|
||||
'jp_connection_updation_failed',
|
||||
__( 'Something went wrong while updating the connection.', 'jetpack-publicize-pkg' ),
|
||||
$response->get_error_message()
|
||||
);
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $response );
|
||||
|
||||
$response->set_status( 201 );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to delete a connection.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
|
||||
*/
|
||||
public function delete_item_permissions_check( $request ) {
|
||||
$permissions = parent::publicize_permissions_check();
|
||||
|
||||
if ( is_wp_error( $permissions ) ) {
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
return $this->manage_connection_permission_check( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a connection.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function delete_item( $request ) {
|
||||
$connection_id = $request->get_param( 'connection_id' );
|
||||
|
||||
if ( Publicize_Utils::is_wpcom() ) {
|
||||
|
||||
$result = Connections::wpcom_delete_connection( $connection_id );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $result );
|
||||
|
||||
$response->set_status( 201 );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response = $this->proxy_request_to_wpcom_as_user( $request, $connection_id, array( 'timeout' => 120 ) );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return new WP_Error(
|
||||
'jp_connection_deletion_failed',
|
||||
__( 'Something went wrong while deleting the connection.', 'jetpack-publicize-pkg' ),
|
||||
$response->get_error_message()
|
||||
);
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $response );
|
||||
|
||||
$response->set_status( 201 );
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
+714
@@ -0,0 +1,714 @@
|
||||
<?php
|
||||
/**
|
||||
* Registers the API field for Publicize connections.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize\REST_API;
|
||||
|
||||
use Automattic\Jetpack\Current_Plan;
|
||||
use Automattic\Jetpack\Publicize\Publicize_Base;
|
||||
use WP_Error;
|
||||
use WP_Post;
|
||||
use WP_REST_Request;
|
||||
|
||||
/**
|
||||
* The class to register the field and augment requests
|
||||
* to Publicize supported post types.
|
||||
*
|
||||
* @phan-constructor-used-for-side-effects
|
||||
*/
|
||||
class Connections_Post_Field {
|
||||
|
||||
const FIELD_NAME = 'jetpack_publicize_connections';
|
||||
|
||||
/**
|
||||
* Array of post IDs that have been updated.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $meta_saved = array();
|
||||
|
||||
/**
|
||||
* Used to memoize the updates for a given post.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $memoized_updates = array();
|
||||
|
||||
/**
|
||||
* Requested connections for a new post being created, kept until the post
|
||||
* ID is known so the skip meta can be persisted before Publicize runs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $new_post_connections = array();
|
||||
|
||||
/**
|
||||
* The request that is creating a new post, kept alongside
|
||||
* $new_post_connections so per-connection overrides can be saved.
|
||||
*
|
||||
* @var WP_REST_Request|null
|
||||
*/
|
||||
private $new_post_request = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'rest_api_init', array( $this, 'register_fields' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the jetpack_publicize_connections field. Called
|
||||
* automatically on `rest_api_init()`.
|
||||
*/
|
||||
public function register_fields() {
|
||||
$post_types = get_post_types_by_support( 'publicize' );
|
||||
foreach ( $post_types 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' );
|
||||
}
|
||||
|
||||
// We use these hooks and not the update_callback because we must updateth meta
|
||||
// before we set the post as published, otherwise the wrong connections could be used.
|
||||
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 );
|
||||
|
||||
register_rest_field(
|
||||
$post_type,
|
||||
self::FIELD_NAME,
|
||||
array(
|
||||
'get_callback' => array( $this, 'get' ),
|
||||
'schema' => $this->get_schema(),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema for the endpoint.
|
||||
*/
|
||||
private function post_connection_schema() {
|
||||
$connection_fields = Connections_Controller::get_the_item_schema();
|
||||
$deprecated_fields = array(
|
||||
'id' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Unique identifier for the Jetpack Social connection.', 'jetpack-publicize-pkg' ) . ' ' . sprintf(
|
||||
/* translators: %s is the new field name */
|
||||
__( 'Deprecated in favor of %s.', 'jetpack-publicize-pkg' ),
|
||||
'connection_id'
|
||||
),
|
||||
),
|
||||
'username' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Username of the connected account.', 'jetpack-publicize-pkg' ) . ' ' . sprintf(
|
||||
/* translators: %s is the new field name */
|
||||
__( 'Deprecated in favor of %s.', 'jetpack-publicize-pkg' ),
|
||||
'external_handle'
|
||||
),
|
||||
),
|
||||
'can_disconnect' => array(
|
||||
'description' => __( 'Whether the current user can disconnect this connection.', 'jetpack-publicize-pkg' ) . ' ' . __( 'Deprecated.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'boolean',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
);
|
||||
|
||||
return array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'jetpack-publicize-post-connection',
|
||||
'type' => 'object',
|
||||
'properties' => array_merge(
|
||||
$deprecated_fields,
|
||||
$connection_fields,
|
||||
array(
|
||||
'enabled' => array(
|
||||
'description' => __( 'Whether to share to this connection.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'boolean',
|
||||
'context' => array( 'edit' ),
|
||||
),
|
||||
'message' => array(
|
||||
'description' => __( 'Custom message to use for this connection instead of the global message.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'edit' ),
|
||||
),
|
||||
'attached_media' => array(
|
||||
'description' => __( 'Custom media to attach for this connection instead of the global media.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'array',
|
||||
'context' => array( 'edit' ),
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array( 'type' => 'number' ),
|
||||
'url' => array( 'type' => 'string' ),
|
||||
'type' => array( 'type' => 'string' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'media_source' => array(
|
||||
'type' => 'string',
|
||||
'enum' => array(
|
||||
'featured-image',
|
||||
'sig',
|
||||
'media-library',
|
||||
'upload-video',
|
||||
'none',
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission check, based on module availability and user capabilities.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public function permission_check( $post_id ) {
|
||||
global $publicize;
|
||||
|
||||
if ( ! $publicize ) {
|
||||
return new WP_Error(
|
||||
'publicize_not_available',
|
||||
__( 'Sorry, Jetpack Social is not available on your site right now.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
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 Jetpack Social data for this post.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The field's wrapped getter. Does permission checks and output preparation.
|
||||
*
|
||||
* This cannot be extended: implement `->get()` instead.
|
||||
*
|
||||
* @param mixed $post_array Probably an array. Whatever the endpoint returns.
|
||||
* @param string $field_name Should always match `->field_name`.
|
||||
* @param WP_REST_Request $request WP API request.
|
||||
* @param string $object_type Should always match `->object_type`.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get( $post_array, $field_name, $request, $object_type ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable, Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
global $publicize;
|
||||
|
||||
$post_id = $post_array['id'] ?? 0;
|
||||
$full_schema = $this->get_schema();
|
||||
$permission_check = $this->permission_check( $post_id );
|
||||
if ( is_wp_error( $permission_check ) ) {
|
||||
return $full_schema['default'];
|
||||
}
|
||||
|
||||
$schema = $full_schema['items'];
|
||||
$properties = array_keys( $schema['properties'] );
|
||||
$connections = $publicize->get_filtered_connection_data( $post_id );
|
||||
|
||||
if ( $publicize && $publicize->has_paid_features() ) {
|
||||
// Check if per-network customization is enabled.
|
||||
$customize_per_network = get_post_meta( $post_id, Publicize_Base::POST_CUSTOMIZE_PER_NETWORK, true );
|
||||
// Get per-connection overrides from post meta.
|
||||
$connection_overrides = get_post_meta( $post_id, Publicize_Base::POST_CONNECTION_OVERRIDES, true );
|
||||
|
||||
if ( ! is_array( $connection_overrides ) ) {
|
||||
$connection_overrides = array();
|
||||
}
|
||||
} else {
|
||||
$customize_per_network = false;
|
||||
$connection_overrides = array();
|
||||
}
|
||||
|
||||
$message_templates_enabled = Current_Plan::supports( 'social-message-templates' );
|
||||
|
||||
$output_connections = array();
|
||||
foreach ( $connections as $connection ) {
|
||||
$output_connection = array();
|
||||
foreach ( $properties as $property ) {
|
||||
if ( isset( $connection[ $property ] ) ) {
|
||||
$output_connection[ $property ] = $connection[ $property ];
|
||||
}
|
||||
}
|
||||
|
||||
// Default `message` to the connection's own template when set
|
||||
if ( $message_templates_enabled && ! empty( $output_connection['template'] ) ) {
|
||||
$output_connection['message'] = $output_connection['template'];
|
||||
}
|
||||
|
||||
// Merge per-connection overrides if global flag is enabled.
|
||||
$connection_id = $connection['connection_id'] ?? '';
|
||||
if ( $customize_per_network && ! empty( $connection_id ) && isset( $connection_overrides[ $connection_id ] ) ) {
|
||||
$override = $connection_overrides[ $connection_id ];
|
||||
if ( isset( $override['message'] ) ) {
|
||||
$output_connection['message'] = $override['message'];
|
||||
}
|
||||
if ( isset( $override['attached_media'] ) ) {
|
||||
$output_connection['attached_media'] = $override['attached_media'];
|
||||
}
|
||||
if ( isset( $override['media_source'] ) ) {
|
||||
$output_connection['media_source'] = $override['media_source'];
|
||||
}
|
||||
}
|
||||
|
||||
$output_connections[] = $output_connection;
|
||||
}
|
||||
|
||||
// TODO: Work out if this is necessary. We shouldn't be creating an invalid value here.
|
||||
$is_valid = rest_validate_value_from_schema( $output_connections, $full_schema, self::FIELD_NAME );
|
||||
if ( is_wp_error( $is_valid ) ) {
|
||||
return $is_valid;
|
||||
}
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
return $this->filter_response_by_context( $output_connections, $full_schema, $context );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 API request.
|
||||
*
|
||||
* @return object|WP_Error Filtered $post
|
||||
*/
|
||||
public function rest_pre_insert( $post, $request ) {
|
||||
$request_connections = ! empty( $request['jetpack_publicize_connections'] ) ? $request['jetpack_publicize_connections'] : array();
|
||||
|
||||
$permission_check = $this->permission_check( empty( $post->ID ) ? 0 : $post->ID );
|
||||
if ( is_wp_error( $permission_check ) ) {
|
||||
return empty( $request_connections ) ? $post : $permission_check;
|
||||
}
|
||||
// memoize.
|
||||
$this->get_meta_to_update( $request_connections, $post->ID ?? 0 );
|
||||
|
||||
if ( isset( $post->ID ) ) {
|
||||
// Set the meta before we mark the post as published so that publicize works as expected.
|
||||
// If this is not the case post end up on social media when they are marked as skipped.
|
||||
$this->update( $request_connections, $post, $request );
|
||||
} else {
|
||||
/*
|
||||
* A brand new post has no ID yet, so we can't persist the skip meta here.
|
||||
* Persist it as soon as the post is inserted, before Publicize processes
|
||||
* the publish transition (priority 10). Otherwise a new published post is
|
||||
* shared to every connection regardless of the requested `enabled` state.
|
||||
*/
|
||||
$this->new_post_connections = $request_connections;
|
||||
$this->new_post_request = $request;
|
||||
add_action( 'transition_post_status', array( $this, 'set_meta_for_new_post' ), 1, 3 );
|
||||
}
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist the Publicize skip meta for a newly created post.
|
||||
*
|
||||
* Runs on `transition_post_status` before Publicize flags the post for
|
||||
* sharing, using the memoized data calculated in rest_pre_insert(). This is
|
||||
* the create-time counterpart to the in-place update() done for existing posts.
|
||||
*
|
||||
* @param string $new_status New post status.
|
||||
* @param string $old_status Old post status.
|
||||
* @param WP_Post $post Post object.
|
||||
*/
|
||||
public function set_meta_for_new_post( $new_status, $old_status, $post ) {
|
||||
if ( ! isset( $this->memoized_updates[0] ) || wp_is_post_revision( $post->ID ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// One-shot: the first non-revision transition is the post we created.
|
||||
remove_action( 'transition_post_status', array( $this, 'set_meta_for_new_post' ), 1 );
|
||||
|
||||
// Move the memoized data to the real post ID now that we know it.
|
||||
$this->memoized_updates[ $post->ID ] = $this->memoized_updates[0];
|
||||
unset( $this->memoized_updates[0] );
|
||||
|
||||
$this->update( $this->new_post_connections, $post, $this->new_post_request );
|
||||
|
||||
$this->new_post_connections = array();
|
||||
$this->new_post_request = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* After creating a new post, update our cached data to reflect
|
||||
* the new post ID.
|
||||
*
|
||||
* @param WP_Post $post Post data to update.
|
||||
* @param WP_REST_Request $request API request.
|
||||
* @param bool $is_new Is this a new post.
|
||||
*/
|
||||
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( $this->memoized_updates[0] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->memoized_updates[ $post->ID ] = $this->memoized_updates[0];
|
||||
unset( $this->memoized_updates[0] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of meta data to update per post ID.
|
||||
*
|
||||
* @param array $requested_connections Publicize connections to update.
|
||||
* Items are either `{ id: (string) }` or `{ service_name: (string) }`.
|
||||
* @param int $post_id Post ID.
|
||||
*/
|
||||
protected function get_meta_to_update( $requested_connections, $post_id = 0 ) {
|
||||
global $publicize;
|
||||
|
||||
if ( ! $publicize || ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Return memoized data first: a new post is already 'publish' by the time
|
||||
// we persist its skip meta, so the publish check below must not discard it.
|
||||
if ( isset( $this->memoized_updates[ $post_id ] ) ) {
|
||||
return $this->memoized_updates[ $post_id ];
|
||||
}
|
||||
|
||||
$post = get_post( $post_id );
|
||||
if ( isset( $post->post_status ) && 'publish' === $post->post_status ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$available_connections = $publicize->get_filtered_connection_data( $post_id );
|
||||
|
||||
$changed_connections = array();
|
||||
|
||||
// Build lookup mappings.
|
||||
$available_connections_by_connection_id = array();
|
||||
$available_connections_by_service_name = array();
|
||||
foreach ( $available_connections as $available_connection ) {
|
||||
$available_connections_by_connection_id[ $available_connection['connection_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) }.
|
||||
// If the service is not available, it will be skipped.
|
||||
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 ) {
|
||||
if ( $requested_connection['connection_id'] === $available_connection['connection_id'] ) {
|
||||
$changed_connections[ $available_connection['connection_id'] ] = $requested_connection['enabled'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle { id: $id, enabled: (bool) }
|
||||
// These override the service_name settings.
|
||||
foreach ( $requested_connections as $requested_connection ) {
|
||||
if ( ! isset( $requested_connection['connection_id'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! isset( $available_connections_by_connection_id[ $requested_connection['connection_id'] ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$changed_connections[ $requested_connection['connection_id'] ] = $requested_connection['enabled'];
|
||||
}
|
||||
|
||||
// Set all changed connections to their new value.
|
||||
foreach ( $changed_connections as $id => $enabled ) {
|
||||
$connection = $available_connections_by_connection_id[ $id ];
|
||||
|
||||
if ( $connection['done'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$available_connections_by_connection_id[ $id ]['enabled'] = $enabled;
|
||||
}
|
||||
|
||||
$meta_to_update = array();
|
||||
// For all connections, ensure correct post_meta.
|
||||
foreach ( $available_connections_by_connection_id as $connection_id => $available_connection ) {
|
||||
if ( $available_connection['enabled'] ) {
|
||||
$meta_to_update[ $publicize->POST_SKIP_PUBLICIZE . $connection_id ] = null; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
} else {
|
||||
$meta_to_update[ $publicize->POST_SKIP_PUBLICIZE . $connection_id ] = 1; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
}
|
||||
}
|
||||
|
||||
$this->memoized_updates[ $post_id ] = $meta_to_update;
|
||||
|
||||
return $meta_to_update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the connections slated to be shared to.
|
||||
*
|
||||
* @param array $requested_connections Publicize connections to update.
|
||||
* Items are either `{ id: (string) }` or `{ service_name: (string) }`.
|
||||
* @param WP_Post $post Post data.
|
||||
* @param WP_REST_Request $request API request.
|
||||
*/
|
||||
public function update( $requested_connections, $post, $request = null ) {
|
||||
global $publicize;
|
||||
|
||||
if ( isset( $this->meta_saved[ $post->ID ] ) ) { // Make sure we only save it once - per request.
|
||||
return;
|
||||
}
|
||||
foreach ( $this->get_meta_to_update( $requested_connections, $post->ID ) as $meta_key => $meta_value ) {
|
||||
if ( null === $meta_value ) {
|
||||
delete_post_meta( $post->ID, $meta_key );
|
||||
} else {
|
||||
update_post_meta( $post->ID, $meta_key, $meta_value );
|
||||
}
|
||||
}
|
||||
|
||||
// Save per-connection overrides.
|
||||
if ( $publicize && $publicize->has_paid_features() ) {
|
||||
$this->save_connection_overrides( $requested_connections, $post->ID, $request );
|
||||
}
|
||||
|
||||
$this->meta_saved[ $post->ID ] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save per-connection customization overrides.
|
||||
*
|
||||
* Extracts message and attached_media from each connection and persists
|
||||
* them to post meta when per-network customization is enabled.
|
||||
*
|
||||
* @param array $requested_connections Array of connection data from the request.
|
||||
* @param int $post_id Post ID.
|
||||
* @param WP_REST_Request $request API request.
|
||||
*/
|
||||
private function save_connection_overrides( $requested_connections, $post_id, $request = null ) {
|
||||
// Check if per-network customization is enabled - prefer request value over database.
|
||||
$customize_per_network = null;
|
||||
if ( $request && isset( $request['meta'][ Publicize_Base::POST_CUSTOMIZE_PER_NETWORK ] ) ) {
|
||||
$customize_per_network = $request['meta'][ Publicize_Base::POST_CUSTOMIZE_PER_NETWORK ];
|
||||
}
|
||||
if ( null === $customize_per_network ) {
|
||||
$customize_per_network = get_post_meta( $post_id, Publicize_Base::POST_CUSTOMIZE_PER_NETWORK, true );
|
||||
}
|
||||
|
||||
// If customization is disabled, remove any existing overrides.
|
||||
if ( ! $customize_per_network ) {
|
||||
delete_post_meta( $post_id, Publicize_Base::POST_CONNECTION_OVERRIDES );
|
||||
return;
|
||||
}
|
||||
|
||||
// If the request does not have connections, skip.
|
||||
if ( ! isset( $request[ self::FIELD_NAME ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$overrides = array();
|
||||
|
||||
foreach ( $requested_connections as $connection ) {
|
||||
// Only process if connection has a connection_id.
|
||||
if ( empty( $connection['connection_id'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only save if connection has custom message or attached_media.
|
||||
if ( ! isset( $connection['message'] ) && ! isset( $connection['attached_media'] ) && ! isset( $connection['media_source'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$connection_id = $connection['connection_id'];
|
||||
$overrides[ $connection_id ] = array();
|
||||
|
||||
// Save message (can be empty to use empty message).
|
||||
if ( isset( $connection['message'] ) ) {
|
||||
$overrides[ $connection_id ]['message'] = sanitize_textarea_field( $connection['message'] );
|
||||
}
|
||||
|
||||
// Save attached_media (can be empty array to clear media).
|
||||
if ( isset( $connection['attached_media'] ) ) {
|
||||
$overrides[ $connection_id ]['attached_media'] = $this->sanitize_attached_media( $connection['attached_media'] );
|
||||
}
|
||||
|
||||
// Save media_source (can be empty to use default).
|
||||
if ( isset( $connection['media_source'] ) ) {
|
||||
$overrides[ $connection_id ]['media_source'] = sanitize_text_field( $connection['media_source'] );
|
||||
}
|
||||
}
|
||||
|
||||
// Only save if there are overrides, otherwise delete the meta.
|
||||
if ( ! empty( $overrides ) ) {
|
||||
update_post_meta( $post_id, Publicize_Base::POST_CONNECTION_OVERRIDES, $overrides );
|
||||
} else {
|
||||
delete_post_meta( $post_id, Publicize_Base::POST_CONNECTION_OVERRIDES );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize attached media array.
|
||||
*
|
||||
* @param array $attached_media Array of media items.
|
||||
* @return array Sanitized array of media items.
|
||||
*/
|
||||
private function sanitize_attached_media( $attached_media ) {
|
||||
if ( empty( $attached_media ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$sanitized = array();
|
||||
|
||||
foreach ( $attached_media as $media_item ) {
|
||||
if ( ! is_array( $media_item ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sanitized_item = array();
|
||||
|
||||
if ( isset( $media_item['id'] ) ) {
|
||||
$sanitized_item['id'] = absint( $media_item['id'] );
|
||||
}
|
||||
|
||||
if ( isset( $media_item['url'] ) ) {
|
||||
$sanitized_item['url'] = esc_url_raw( $media_item['url'] );
|
||||
}
|
||||
|
||||
if ( isset( $media_item['type'] ) ) {
|
||||
$sanitized_item['type'] = sanitize_text_field( $media_item['type'] );
|
||||
}
|
||||
|
||||
if ( ! empty( $sanitized_item ) ) {
|
||||
$sanitized[] = $sanitized_item;
|
||||
}
|
||||
}
|
||||
|
||||
return $sanitized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Value passed to API request.
|
||||
* @param array $schema Schema to validate against.
|
||||
* @param string $context REST API Request context.
|
||||
*
|
||||
* @return mixed Filtered $value
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that our request matches its expected context.
|
||||
*
|
||||
* @param array $schema Schema to validate against.
|
||||
* @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 );
|
||||
}
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* The Publicize Keyring Result Controller class.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize\REST_API;
|
||||
|
||||
use Automattic\Jetpack\Connection\Traits\WPCOM_REST_API_Proxy_Request;
|
||||
use Automattic\Jetpack\Publicize\Publicize_Utils;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use WP_REST_Server;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Keyring Result Controller.
|
||||
*
|
||||
* Returns the verified keyring connection item for a just-completed connect request
|
||||
* (auth_flow=v2). The connect popup no longer posts the result back through
|
||||
* window.opener; instead the same-origin completion page broadcasts the request_id and the
|
||||
* client fetches the result here once.
|
||||
*
|
||||
* @phan-constructor-used-for-side-effects
|
||||
*/
|
||||
class Keyring_Result_Controller extends Base_Controller {
|
||||
|
||||
use WPCOM_REST_API_Proxy_Request;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
$this->base_api_path = 'wpcom';
|
||||
$this->version = 'v2';
|
||||
|
||||
$this->namespace = "{$this->base_api_path}/{$this->version}";
|
||||
$this->rest_base = 'publicize/keyring-result';
|
||||
|
||||
add_action( 'rest_api_init', array( $this, 'register_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_keyring_result' ),
|
||||
'permission_callback' => array( $this, 'get_keyring_result_permissions_check' ),
|
||||
'args' => array(
|
||||
'request_id' => array(
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'description' => __( 'ID of the connect request.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the request has access to the keyring result.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|\WP_Error
|
||||
*/
|
||||
public function get_keyring_result_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return $this->publicize_permissions_check() && (bool) get_current_user_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the keyring result for a completed connect request.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
*
|
||||
* @return WP_REST_Response The response object: { code, data }.
|
||||
*/
|
||||
public function get_keyring_result( $request ) {
|
||||
if ( ! Publicize_Utils::is_wpcom() ) {
|
||||
return rest_ensure_response( $this->proxy_request_to_wpcom_as_user( $request ) );
|
||||
}
|
||||
|
||||
$response = array(
|
||||
'code' => 'unknown',
|
||||
'data' => null,
|
||||
);
|
||||
|
||||
require_lib( 'external-connections' );
|
||||
|
||||
$external_connections = \WPCOM_External_Connections::init();
|
||||
|
||||
$request_id = $request->get_param( 'request_id' );
|
||||
|
||||
// The transient is keyed by current user + request_id, so a hit already proves ownership.
|
||||
$data = $external_connections->get_last_keyring_token_details( $request_id );
|
||||
|
||||
if ( ! $data ) {
|
||||
$response['code'] = 'no_data_found';
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
$token_id = $data['token_id'] ?? null;
|
||||
|
||||
if ( ! $token_id ) {
|
||||
$response['code'] = 'token_id_missing';
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
// On reconnect of a broken connection, re-test so the cached failure is overwritten.
|
||||
$force_connection_test = $external_connections->has_failing_cached_connection_test( $token_id );
|
||||
|
||||
$item = $external_connections->get_keyring_connection_item( $token_id, false, $force_connection_test );
|
||||
|
||||
if ( ! $item ) {
|
||||
$response['code'] = 'token_not_found';
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
$response['code'] = 'success';
|
||||
$response['data'] = $item;
|
||||
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/**
|
||||
* Publicize: Message Templates Placeholders Controller
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize\REST_API;
|
||||
|
||||
use Automattic\Jetpack\Publicize\Message_Templates_Placeholders;
|
||||
use WP_REST_Server;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Publicize: Message Templates Placeholders Controller class.
|
||||
*
|
||||
* @phan-constructor-used-for-side-effects
|
||||
*/
|
||||
class Message_Templates_Placeholders_Controller extends Base_Controller {
|
||||
|
||||
/**
|
||||
* The constructor sets the route namespace, rest_base, and registers our API route and endpoint.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
$this->namespace = 'wpcom/v2';
|
||||
$this->rest_base = 'publicize/message-templates/placeholders';
|
||||
|
||||
$this->allow_requests_as_blog = true;
|
||||
|
||||
add_action( 'rest_api_init', array( $this, 'register_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_placeholders' ),
|
||||
'permission_callback' => array( $this, 'permissions_check' ),
|
||||
'private_site_security_settings' => array(
|
||||
'allow_blog_token_access' => true,
|
||||
),
|
||||
'schema' => array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'publicize-message-templates-placeholders',
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'The placeholder token (e.g. {title}).', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'label' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Human-readable description of the placeholder.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission check.
|
||||
*
|
||||
* @return true|\WP_Error
|
||||
*/
|
||||
public function permissions_check() {
|
||||
return $this->publicize_permissions_check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the placeholder catalogue.
|
||||
*
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
public function get_placeholders() {
|
||||
return rest_ensure_response( Message_Templates_Placeholders::get_all() );
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* Helper class to make proxy requests to wpcom.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize\REST_API;
|
||||
|
||||
use Automattic\Jetpack\Connection\Traits\WPCOM_REST_API_Proxy_Request;
|
||||
|
||||
/**
|
||||
* Helper class to make proxy requests to wpcom.
|
||||
*/
|
||||
class Proxy_Requests {
|
||||
|
||||
use WPCOM_REST_API_Proxy_Request;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $rest_base The rest base.
|
||||
* @param string $base_api_path The base API path.
|
||||
* @param string $version The API version.
|
||||
*/
|
||||
public function __construct( $rest_base, $base_api_path = 'wpcom', $version = 'v2' ) {
|
||||
$this->rest_base = $rest_base;
|
||||
$this->base_api_path = $base_api_path;
|
||||
$this->version = $version;
|
||||
}
|
||||
}
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
/**
|
||||
* Publicize: Render Messages Controller
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize\REST_API;
|
||||
|
||||
use Automattic\Jetpack\Connection\Traits\WPCOM_REST_API_Proxy_Request;
|
||||
use Automattic\Jetpack\Publicize\Publicize_Utils as Utils;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Server;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Publicize: Render Messages Controller class.
|
||||
*
|
||||
* Renders Publicize message templates for a given post and a batch of
|
||||
* connection inputs in a single request, so the block-editor preview can
|
||||
* fetch all enabled connections' previews in one round-trip when the
|
||||
* `social-message-templates` feature is enabled.
|
||||
*
|
||||
* POST takes a JSON body of `{ post_id, items: [...], post_intent: {...} }`
|
||||
* and returns one record per input item, in input order, keyed by the
|
||||
* client-supplied `connection_id`. Body-based POST is used instead of a GET
|
||||
* collection so multi-connection / long-message batches don't hit
|
||||
* infrastructure URL caps.
|
||||
*
|
||||
* @phan-constructor-used-for-side-effects
|
||||
*/
|
||||
class Render_Messages_Controller extends Base_Controller {
|
||||
|
||||
use WPCOM_REST_API_Proxy_Request;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
$this->base_api_path = 'wpcom';
|
||||
$this->version = 'v2';
|
||||
|
||||
$this->namespace = "{$this->base_api_path}/{$this->version}";
|
||||
$this->rest_base = 'publicize/render-messages';
|
||||
|
||||
$this->allow_requests_as_blog = true;
|
||||
|
||||
add_action( 'rest_api_init', array( $this, 'register_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'render_messages' ),
|
||||
'permission_callback' => array( $this, 'permissions_check' ),
|
||||
'private_site_security_settings' => array(
|
||||
'allow_blog_token_access' => true,
|
||||
),
|
||||
'args' => array(
|
||||
'post_id' => array(
|
||||
'description' => __( 'The ID of the post to render the messages for.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
),
|
||||
'items' => array(
|
||||
'description' => __( 'List of per-connection render inputs.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'array',
|
||||
'required' => true,
|
||||
'minItems' => 1,
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'additionalProperties' => false,
|
||||
'required' => array( 'connection_id' ),
|
||||
'properties' => array(
|
||||
'connection_id' => array(
|
||||
'description' => __( 'Publicize connection ID — used to dispatch the renderer and resolve the per-connection template.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'message' => array(
|
||||
'description' => __( 'Optional message override. Empty walks the per-connection / site / network-default chain.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
),
|
||||
'is_social_post' => array(
|
||||
'description' => __( 'Whether the post will be shared as a social post (media attached) rather than a link share.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'boolean',
|
||||
'default' => false,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'post_intent' => array(
|
||||
'description' => __( 'Edited post fields to use when rendering unsaved preview changes.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'object',
|
||||
'default' => array(),
|
||||
'additionalProperties' => false,
|
||||
'properties' => array(
|
||||
'title' => array(
|
||||
'description' => __( 'Edited post title.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
),
|
||||
'excerpt' => array(
|
||||
'description' => __( 'Edited post excerpt.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
),
|
||||
'content' => array(
|
||||
'description' => __( 'Edited post content.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the JSON schema for a single rendered-message item.
|
||||
*
|
||||
* @return array Schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
return array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'publicize-render-messages-item',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'connection_id' => array(
|
||||
'description' => __( 'Connection identifier echoed back from the request.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'rendered_message' => array(
|
||||
'description' => __( 'The rendered message for this item. Absent when the item failed to render.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'error' => array(
|
||||
'description' => __( 'Per-item error. Present only when this item failed to render.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'object',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'properties' => array(
|
||||
'code' => array( 'type' => 'string' ),
|
||||
'message' => array( 'type' => 'string' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission check.
|
||||
*
|
||||
* Preserves the blog-token proxy path via Base_Controller::publicize_permissions_check()
|
||||
* (which returns true for authorized blog requests when allow_requests_as_blog is set),
|
||||
* and enforces post-level `edit_post` capability for regular user requests.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True if authorized, WP_Error otherwise.
|
||||
*/
|
||||
public function permissions_check( $request ) {
|
||||
$base_check = $this->publicize_permissions_check();
|
||||
|
||||
if ( is_wp_error( $base_check ) ) {
|
||||
return $base_check;
|
||||
}
|
||||
|
||||
// Blog-token proxy requests don't have a user context; the publicize check
|
||||
// above already returned true for those.
|
||||
if ( self::is_authorized_blog_request() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$post_id = (int) $request->get_param( 'post_id' );
|
||||
|
||||
if ( ! $post_id || ! current_user_can( 'edit_post', $post_id ) ) {
|
||||
return new WP_Error(
|
||||
'invalid_user_permission_publicize',
|
||||
__( 'Sorry, you are not allowed to access Jetpack Social data for this post.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the messages for the given post and items.
|
||||
*
|
||||
* Top-level errors (feature off, post not found) short-circuit the whole batch.
|
||||
* Per-item failures are returned as `{ id, error: { code, message } }` so a
|
||||
* single bad item never fails the batch.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
* @return mixed The rendered items array, or a WP_Error for top-level failures.
|
||||
*/
|
||||
public function render_messages( $request ) {
|
||||
if ( Utils::is_wpcom() ) {
|
||||
require_lib( 'publicize/util/message-templates' );
|
||||
|
||||
if ( ! \Publicize\is_message_templates_enabled() ) {
|
||||
return new WP_Error(
|
||||
'feature_not_enabled',
|
||||
__( 'Publicize message templates are not enabled for this site.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => 403 )
|
||||
);
|
||||
}
|
||||
|
||||
$post_id = (int) $request->get_param( 'post_id' );
|
||||
$items = (array) $request->get_param( 'items' );
|
||||
$intent = (array) $request->get_param( 'post_intent' );
|
||||
|
||||
$post = get_post( $post_id );
|
||||
|
||||
if ( ! $post ) {
|
||||
return new WP_Error(
|
||||
'post_not_found',
|
||||
__( 'Post not found.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
\Publicize\render_messages( $post, $items, $intent )
|
||||
);
|
||||
}
|
||||
|
||||
// Self-hosted Jetpack: proxy the request body to WPCOM.
|
||||
return rest_ensure_response(
|
||||
$this->proxy_request_to_wpcom_as_blog( $request )
|
||||
);
|
||||
}
|
||||
}
|
||||
+678
@@ -0,0 +1,678 @@
|
||||
<?php
|
||||
/**
|
||||
* The Publicize Scheduled Actions Controller class.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize\REST_API;
|
||||
|
||||
use Automattic\Jetpack\Connection\Traits\WPCOM_REST_API_Proxy_Request;
|
||||
use Automattic\Jetpack\Publicize\Connections;
|
||||
use Automattic\Jetpack\Publicize\Publicize_Utils as Utils;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use WP_REST_Server;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Scheduled Actions Controller class.
|
||||
*
|
||||
* @phan-constructor-used-for-side-effects
|
||||
*/
|
||||
class Scheduled_Actions_Controller extends Base_Controller {
|
||||
|
||||
use WPCOM_REST_API_Proxy_Request;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
$this->base_api_path = 'wpcom';
|
||||
$this->version = 'v2';
|
||||
|
||||
$this->namespace = "{$this->base_api_path}/{$this->version}";
|
||||
$this->rest_base = 'publicize/scheduled-actions';
|
||||
|
||||
add_action( 'rest_api_init', array( $this, 'register_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the routes.
|
||||
*/
|
||||
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_permissions_check' ),
|
||||
'args' => array(
|
||||
'post_id' => array(
|
||||
'type' => 'integer',
|
||||
'description' => __( 'The post ID to filter the items by.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'create_item' ),
|
||||
'permission_callback' => array( $this, 'create_item_permissions_check' ),
|
||||
'args' => array(
|
||||
'post_id' => array(
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
),
|
||||
'connection_id' => array(
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
),
|
||||
'message' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'share_date' => array(
|
||||
'type' => 'integer',
|
||||
'description' => sprintf(
|
||||
/* translators: %s is the new field name */
|
||||
__( 'Deprecated in favor of %s.', 'jetpack-publicize-pkg' ),
|
||||
'timestamp'
|
||||
),
|
||||
),
|
||||
'timestamp' => array(
|
||||
'type' => 'integer',
|
||||
'description' => __( 'GMT/UTC Unix timestamp in seconds for the action.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<action_id>\d+)',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_item' ),
|
||||
'permission_callback' => array( $this, 'get_item_permissions_check' ),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( $this, 'update_item' ),
|
||||
'permission_callback' => array( $this, 'update_item_permissions_check' ),
|
||||
'args' => array(
|
||||
'message' => array( 'type' => 'string' ),
|
||||
'share_date' => array(
|
||||
'type' => 'integer',
|
||||
'description' => sprintf(
|
||||
/* translators: %s is the new field name */
|
||||
__( 'Deprecated in favor of %s.', 'jetpack-publicize-pkg' ),
|
||||
'timestamp'
|
||||
),
|
||||
),
|
||||
'timestamp' => array(
|
||||
'type' => 'integer',
|
||||
'description' => __( 'GMT/UTC Unix timestamp in seconds for the action.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::DELETABLE,
|
||||
'callback' => array( $this, 'delete_item' ),
|
||||
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema for the endpoint.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'publicize-scheduled-action',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'blog_id' => array(
|
||||
'type' => 'integer',
|
||||
'description' => __( 'The blog ID that the action belongs to.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'connection_id' => array(
|
||||
'type' => 'integer',
|
||||
'description' => __( 'The publicize connection ID that the action belongs to.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'id' => array(
|
||||
'type' => 'integer',
|
||||
'description' => __( 'Action identifier.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'ID' => array(
|
||||
'type' => 'integer',
|
||||
'description' => __( 'Action identifier.', 'jetpack-publicize-pkg' ) . ' ' . sprintf(
|
||||
/* translators: %s is the new field name */
|
||||
__( 'Deprecated in favor of %s.', 'jetpack-publicize-pkg' ),
|
||||
'id'
|
||||
),
|
||||
),
|
||||
'message' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'The result of the action.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'post_id' => array(
|
||||
'type' => 'integer',
|
||||
'description' => __( 'The post ID that the action belongs to.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'share_date' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'ISO 8601 formatted date for the action.', 'jetpack-publicize-pkg' ) . ' ' . sprintf(
|
||||
/* translators: %s is the new field name */
|
||||
__( 'Deprecated in favor of %s.', 'jetpack-publicize-pkg' ),
|
||||
'timestamp'
|
||||
),
|
||||
),
|
||||
'timestamp' => array(
|
||||
'type' => 'integer',
|
||||
'description' => __( 'GMT/UTC Unix timestamp in seconds for the action.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'wpcom_user_id' => array(
|
||||
'type' => 'integer',
|
||||
'description' => __( 'wordpress.com ID of the user who created the action.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user has the basic permissions to access the Publicize scheduled actions.
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public function basic_permissions_check() {
|
||||
if ( ! current_user_can( 'edit_posts' ) ) {
|
||||
return false;
|
||||
}
|
||||
return $this->publicize_permissions_check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user has the basic permissions
|
||||
* required to perform CRUD operations on an item related to a post
|
||||
*
|
||||
* @param int $post_id The post ID.
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public function basic_post_permissions_check( $post_id ) {
|
||||
|
||||
if ( ! get_post( $post_id ) ) {
|
||||
return new WP_Error(
|
||||
'post_not_found',
|
||||
__( 'Post not found.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure that the user can edit the post.
|
||||
if ( ! current_user_can( 'edit_post', $post_id ) ) {
|
||||
return new WP_Error(
|
||||
'rest_forbidden',
|
||||
__( 'Sorry, you are not allowed to view or scheduled shares for that post.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => 403 )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the request has access to connectoins list.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public function get_items_permissions_check( $request ) {
|
||||
$basic_permissions = $this->basic_permissions_check();
|
||||
|
||||
if ( is_wp_error( $basic_permissions ) || ! $basic_permissions ) {
|
||||
return $basic_permissions;
|
||||
}
|
||||
|
||||
$post_id = $request->get_param( 'post_id' );
|
||||
|
||||
/**
|
||||
* The post_id is optional only for editors and above.
|
||||
* It means that authors can view the scheduled shares
|
||||
* only for the post they can edit but
|
||||
* cannot view all the scheduled shares for the site.
|
||||
*/
|
||||
if ( ! $post_id && ! current_user_can( 'edit_others_posts' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_forbidden',
|
||||
__( 'You must pass a post ID to list scheduled shares.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
if ( $post_id ) {
|
||||
return $this->basic_post_permissions_check( $post_id );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of Publicize scheduled actions
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error The response
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
|
||||
if ( Utils::is_wpcom() ) {
|
||||
$post_id = $request->get_param( 'post_id' );
|
||||
|
||||
require_lib( 'publicize/class.publicize-actions' );
|
||||
|
||||
if ( $post_id ) {
|
||||
$scheduled_actions = \Publicize_Actions::get_scheduled_actions_by_blog_and_post_id(
|
||||
get_current_blog_id(),
|
||||
$post_id
|
||||
);
|
||||
} else {
|
||||
$scheduled_actions = \Publicize_Actions::get_scheduled_actions_by_blog_id(
|
||||
get_current_blog_id()
|
||||
);
|
||||
}
|
||||
|
||||
if ( is_wp_error( $scheduled_actions ) ) {
|
||||
return $scheduled_actions;
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
$this->prepare_items_for_response( $scheduled_actions, $request )
|
||||
);
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
$this->proxy_request_to_wpcom_as_user( $request )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to create a connection.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return bool|WP_Error True if the request has access to create items, WP_Error object otherwise.
|
||||
*/
|
||||
public function create_item_permissions_check( $request ) {
|
||||
$basic_permissions = $this->basic_permissions_check();
|
||||
|
||||
if ( is_wp_error( $basic_permissions ) || ! $basic_permissions ) {
|
||||
return $basic_permissions;
|
||||
}
|
||||
|
||||
$post_id = $request->get_param( 'post_id' );
|
||||
|
||||
$basic_post_permissions = $this->basic_post_permissions_check( $post_id );
|
||||
|
||||
if ( is_wp_error( $basic_post_permissions ) || ! $basic_post_permissions ) {
|
||||
return $basic_post_permissions;
|
||||
}
|
||||
|
||||
$post = get_post( $post_id );
|
||||
|
||||
// Ensure that the post is published.
|
||||
if ( 'publish' !== $post->post_status ) {
|
||||
return new WP_Error(
|
||||
'post_not_published',
|
||||
__( 'The post must be published to schedule it for sharing.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to validate the passed connection_id
|
||||
* to ensure that it's valid and the user has access to the connection.
|
||||
*/
|
||||
$connection = Connections::get_by_id( (string) $request->get_param( 'connection_id' ) );
|
||||
|
||||
if ( ! $connection ) {
|
||||
return new WP_Error(
|
||||
'connection_not_found',
|
||||
__( 'That connection does not exist.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
if ( current_user_can( 'edit_others_posts' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user is not an editor or above, they can create
|
||||
* actions only for the connections they have access to.
|
||||
* So, we need to check if the user has access to the connection
|
||||
* that they are trying to use to create the action.
|
||||
*/
|
||||
if ( ! Connections::is_shared( $connection ) && ! Connections::user_owns_connection( $connection ) ) {
|
||||
return new WP_Error(
|
||||
'rest_forbidden',
|
||||
__( 'Sorry, you cannot schedule shares to that connection.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => 403 )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new action.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function create_item( $request ) {
|
||||
|
||||
if ( Utils::is_wpcom() ) {
|
||||
require_lib( 'publicize/class.publicize-actions' );
|
||||
|
||||
$post_id = $request['post_id'];
|
||||
$blog_id = get_current_blog_id();
|
||||
$user_id = get_current_user_id();
|
||||
$connection_id = (int) $request['connection_id'];
|
||||
$message = sanitize_textarea_field( $request['message'] ?? '' );
|
||||
|
||||
$timestamp = time();
|
||||
if ( ! empty( $request['timestamp'] ) ) {
|
||||
$timestamp = (int) $request['timestamp'];
|
||||
} elseif ( ! empty( $request['share_date'] ) ) { // Fallback for deprecated field.
|
||||
$timestamp = $request['share_date']; // Calypso sends this as timestamp.
|
||||
}
|
||||
|
||||
$action = array(
|
||||
'post_id' => $post_id,
|
||||
'blog_id' => $blog_id,
|
||||
'user_id' => $user_id,
|
||||
'connection_id' => $connection_id,
|
||||
'message' => $message,
|
||||
'scheduled_datetime' => $this->format_date_for_db( $timestamp ),
|
||||
);
|
||||
|
||||
$action_id = \Publicize_Actions::add_scheduled_action( $action );
|
||||
if ( is_wp_error( $action_id ) ) {
|
||||
return $action_id;
|
||||
}
|
||||
$action['publicize_scheduled_action_id'] = $action_id;
|
||||
|
||||
$response = rest_ensure_response(
|
||||
$this->prepare_action_for_response( $action )
|
||||
);
|
||||
|
||||
$response->set_status( 201 );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
$this->proxy_request_to_wpcom_as_user( $request )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to read an action.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return bool|WP_Error True if the request has read access for the item, WP_Error object or false otherwise.
|
||||
*/
|
||||
public function get_item_permissions_check( $request ) {
|
||||
$basic_permissions = $this->basic_permissions_check();
|
||||
|
||||
if ( is_wp_error( $basic_permissions ) || ! $basic_permissions ) {
|
||||
return $basic_permissions;
|
||||
}
|
||||
|
||||
if ( ! Utils::is_wpcom() ) {
|
||||
// On Jetpack sites, we need to just check for basic permissions.
|
||||
return true;
|
||||
}
|
||||
|
||||
$action = $this->wpcom_get_action( $request['action_id'] );
|
||||
|
||||
if ( is_wp_error( $action ) ) {
|
||||
return $action;
|
||||
}
|
||||
|
||||
// Ensure that the action is for the current blog.
|
||||
if ( get_current_blog_id() !== $action['blog_id'] ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->basic_post_permissions_check( $action['post_id'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single action.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function get_item( $request ) {
|
||||
$action_id = $request['action_id'];
|
||||
|
||||
if ( Utils::is_wpcom() ) {
|
||||
|
||||
return rest_ensure_response(
|
||||
$this->wpcom_get_action( $action_id )
|
||||
);
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
$this->proxy_request_to_wpcom_as_user( $request, $action_id )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to update an action.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
|
||||
*/
|
||||
public function update_item_permissions_check( $request ) {
|
||||
// If a user can view an item, they can update it.
|
||||
return $this->get_item_permissions_check( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an action.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function update_item( $request ) {
|
||||
|
||||
$action_id = $request['action_id'];
|
||||
|
||||
if ( Utils::is_wpcom() ) {
|
||||
require_lib( 'publicize/class.publicize-actions' );
|
||||
|
||||
$action = $this->wpcom_get_action( $action_id );
|
||||
|
||||
if ( is_wp_error( $action ) ) {
|
||||
return $action;
|
||||
}
|
||||
$action['message'] = ! empty( $request['message'] ) ? sanitize_textarea_field( $request['message'] ) : $action['message'];
|
||||
|
||||
// Retain the original timestamp by default.
|
||||
$timestamp = $action['timestamp'];
|
||||
if ( ! empty( $request['timestamp'] ) ) {
|
||||
$timestamp = (int) $request['timestamp'];
|
||||
} elseif ( ! empty( $request['share_date'] ) ) { // Fallback for deprecated field.
|
||||
$timestamp = strtotime( $request['share_date'] );
|
||||
}
|
||||
$action['scheduled_datetime'] = $this->format_date_for_db( $timestamp );
|
||||
|
||||
$action['publicize_scheduled_action_id'] = $action['id'];
|
||||
|
||||
$save_result = \Publicize_Actions::edit_scheduled_action( $action['id'], $action );
|
||||
if ( is_wp_error( $save_result ) ) {
|
||||
return $save_result;
|
||||
}
|
||||
return rest_ensure_response(
|
||||
$this->prepare_action_for_response( $action )
|
||||
);
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
$this->proxy_request_to_wpcom_as_user( $request, $action_id )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to delete an action.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
|
||||
*/
|
||||
public function delete_item_permissions_check( $request ) {
|
||||
// If a user can update an item, they can delete it.
|
||||
return $this->update_item_permissions_check( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an action.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function delete_item( $request ) {
|
||||
|
||||
$action_id = $request['action_id'];
|
||||
|
||||
if ( Utils::is_wpcom() ) {
|
||||
require_lib( 'publicize/class.publicize-actions' );
|
||||
|
||||
$action = $this->wpcom_get_action( $action_id );
|
||||
if ( is_wp_error( $action ) ) {
|
||||
return $action;
|
||||
}
|
||||
$delete_result = \Publicize_Actions::delete_scheduled_action(
|
||||
$action['id'],
|
||||
$action['blog_id']
|
||||
);
|
||||
if ( is_wp_error( $delete_result ) ) {
|
||||
return $delete_result;
|
||||
}
|
||||
return rest_ensure_response( true );
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
$this->proxy_request_to_wpcom_as_user( $request, $action_id )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out data based on ?_fields= request parameter
|
||||
*
|
||||
* @param array $items Items to prepare.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
*
|
||||
* @return array Items.
|
||||
*/
|
||||
public function prepare_items_for_response( $items, $request ) {
|
||||
|
||||
$output = array();
|
||||
|
||||
foreach ( $items as $raw_item ) {
|
||||
|
||||
$item = $this->prepare_action_for_response( $raw_item );
|
||||
|
||||
$data = $this->prepare_item_for_response( $item, $request );
|
||||
|
||||
$output[] = $this->prepare_response_for_collection( $data );
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a single action for response, setting the correct field names.
|
||||
*
|
||||
* @param array $raw_action Raw action.
|
||||
*
|
||||
* @return array Items.
|
||||
*/
|
||||
public function prepare_action_for_response( $raw_action ) {
|
||||
|
||||
return array(
|
||||
'blog_id' => (int) $raw_action['blog_id'],
|
||||
'connection_id' => (int) $raw_action['connection_id'],
|
||||
'id' => (int) $raw_action['publicize_scheduled_action_id'],
|
||||
'ID' => (int) $raw_action['publicize_scheduled_action_id'],
|
||||
'message' => (string) $raw_action['message'],
|
||||
'post_id' => (int) $raw_action['post_id'],
|
||||
'share_date' => (string) $this->format_date_for_output( $raw_action['scheduled_datetime'] ),
|
||||
'timestamp' => strtotime( $raw_action['scheduled_datetime'] ),
|
||||
'wpcom_user_id' => (int) $raw_action['user_id'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a formatted action by action_id
|
||||
*
|
||||
* @param int $action_id The action ID.
|
||||
* @return WP_Error|array The action
|
||||
*/
|
||||
private function wpcom_get_action( $action_id ) {
|
||||
// Ensure that we are on WPCOM.
|
||||
Utils::assert_is_wpcom( __METHOD__ );
|
||||
|
||||
require_lib( 'publicize/class.publicize-actions' );
|
||||
$action = \Publicize_Actions::get_scheduled_action( $action_id );
|
||||
if ( is_wp_error( $action ) ) {
|
||||
return $action;
|
||||
}
|
||||
if ( ! isset( $action['publicize_scheduled_action_id'] ) ) {
|
||||
return new WP_Error( 'not_found', __( 'Could not find that scheduled action.', 'jetpack-publicize-pkg' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
return $this->prepare_action_for_response( $action );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns ISO 8601 formatted datetime: 2011-12-08T01:15:36-08:00
|
||||
*
|
||||
* @param string $date_gmt GMT datetime string.
|
||||
* @return string
|
||||
*/
|
||||
private function format_date_for_output( $date_gmt ) {
|
||||
// phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
|
||||
return date( 'c', strtotime( $date_gmt ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns SQL formatted datetime from unix timestamp
|
||||
*
|
||||
* @param int $timestamp The timestamp.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function format_date_for_db( $timestamp ) {
|
||||
// Round down to the nearest minute.
|
||||
$floored_timestamp = $timestamp - $timestamp % 60;
|
||||
return gmdate( 'Y-m-d H:i:s', $floored_timestamp );
|
||||
}
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
/**
|
||||
* The Publicize services Controller class.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize\REST_API;
|
||||
|
||||
use Automattic\Jetpack\Connection\Traits\WPCOM_REST_API_Proxy_Request;
|
||||
use Automattic\Jetpack\Publicize\Publicize_Utils;
|
||||
use Automattic\Jetpack\Publicize\Services;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use WP_REST_Server;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Services Controller class.
|
||||
*
|
||||
* @phan-constructor-used-for-side-effects
|
||||
*/
|
||||
class Services_Controller extends Base_Controller {
|
||||
|
||||
use WPCOM_REST_API_Proxy_Request;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
$this->base_api_path = 'wpcom';
|
||||
$this->version = 'v2';
|
||||
|
||||
$this->namespace = "{$this->base_api_path}/{$this->version}";
|
||||
$this->rest_base = 'publicize/services';
|
||||
|
||||
add_action( 'rest_api_init', array( $this, 'register_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the routes.
|
||||
*/
|
||||
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_permissions_check' ),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema for the endpoint.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'jetpack-publicize-service',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Alphanumeric slug for the service.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'description' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Description for the service.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'label' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Human-readable label for the Jetpack Social service.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'status' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Status of the service.', 'jetpack-publicize-pkg' ),
|
||||
'enum' => array( null, 'ok', 'unsupported' ),
|
||||
),
|
||||
'supports' => array(
|
||||
'type' => 'object',
|
||||
'description' => __( 'An object of features that the service supports.', 'jetpack-publicize-pkg' ),
|
||||
'properties' => array(
|
||||
'additional_users' => array(
|
||||
'type' => 'boolean',
|
||||
'description' => __( 'Whether the service is supported for multiple additional user accounts.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'additional_users_only' => array(
|
||||
'type' => 'boolean',
|
||||
'description' => __( 'Whether the service supports only the additional users and not the main user account.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'url' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'URL to use for connecting an account for the service.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the request has access to services list.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public function get_items_permissions_check( $request ) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return $this->publicize_permissions_check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of Publicize services.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
*
|
||||
* @return WP_REST_Response suitable for 1-page collection
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
if ( Publicize_Utils::is_wpcom() ) {
|
||||
|
||||
$items = array();
|
||||
|
||||
foreach ( Services::wpcom_get_all() as $item ) {
|
||||
$data = $this->prepare_item_for_response( $item, $request );
|
||||
|
||||
$items[] = $this->prepare_response_for_collection( $data );
|
||||
}
|
||||
} else {
|
||||
$items = $this->proxy_request_to_wpcom_as_user( $request );
|
||||
|
||||
if ( is_wp_error( $items ) ) {
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $items );
|
||||
$response->header( 'X-WP-Total', (string) count( $items ) );
|
||||
$response->header( 'X-WP-TotalPages', '1' );
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
/**
|
||||
* Publicize: Share post
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize\REST_API;
|
||||
|
||||
use Automattic\Jetpack\Connection\Traits\WPCOM_REST_API_Proxy_Request;
|
||||
use Automattic\Jetpack\Publicize\Publicize_Utils as Utils;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use WP_REST_Server;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Publicize: Share post class.
|
||||
*
|
||||
* @phan-constructor-used-for-side-effects
|
||||
*/
|
||||
class Share_Post_Controller extends Base_Controller {
|
||||
|
||||
use WPCOM_REST_API_Proxy_Request;
|
||||
|
||||
/**
|
||||
* The constructor sets the route namespace, rest_base, and registers our API route and endpoint.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
$this->base_api_path = 'wpcom';
|
||||
$this->version = 'v2';
|
||||
|
||||
$this->namespace = "{$this->base_api_path}/{$this->version}";
|
||||
$this->rest_base = 'publicize/share-post';
|
||||
|
||||
add_action( 'rest_api_init', array( $this, 'register_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
|
||||
$args = array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'share_post' ),
|
||||
'permission_callback' => array( $this, 'permissions_check' ),
|
||||
'args' => array(
|
||||
'message' => array(
|
||||
'description' => __( 'The message to share.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'validate_callback' => function ( $param ) {
|
||||
return is_string( $param );
|
||||
},
|
||||
'sanitize_callback' => 'sanitize_textarea_field',
|
||||
),
|
||||
'skipped_connections' => array(
|
||||
'description' => __( 'Array of external connection IDs to skip sharing.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'array',
|
||||
'required' => false,
|
||||
'validate_callback' => function ( $param ) {
|
||||
return is_array( $param );
|
||||
},
|
||||
'sanitize_callback' => function ( $param ) {
|
||||
if ( ! is_array( $param ) ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
esc_html__( 'The skipped_connections argument must be an array of connection IDs.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
return array_map( 'absint', $param );
|
||||
},
|
||||
),
|
||||
'async' => array(
|
||||
'description' => __( 'Whether to share the post asynchronously.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'boolean',
|
||||
'default' => false,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<postId>\d+)',
|
||||
$args
|
||||
);
|
||||
|
||||
if ( Utils::is_wpcom() ) {
|
||||
// WPCOM Legacy route for backwards compatibility.
|
||||
// TODO: Remove this after April 2025 release of Jetpack.
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/posts/(?P<postId>\d+)/publicize',
|
||||
$args
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the user has proper tokens and permissions to publish posts on this blog.
|
||||
*
|
||||
* @return WP_Error|boolean
|
||||
*/
|
||||
public function permissions_check() {
|
||||
return $this->publicize_permissions_check();
|
||||
}
|
||||
|
||||
/**
|
||||
* If this method callback is executed on WPCOM, we share the post using republicize_post(). If this method callback
|
||||
* is executed on a Jetpack site, we make an API call to WPCOM using wpcom_json_api_request_as_user() and return
|
||||
* the results. In both cases, this file and method are executed, as this file is synced from Jetpack to WPCOM.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response|WP_Error The publicize results, including two arrays: `results` and `errors`
|
||||
*/
|
||||
public function share_post( $request ) {
|
||||
$post_id = $request->get_param( 'postId' );
|
||||
|
||||
if ( ! Utils::is_wpcom() ) {
|
||||
return rest_ensure_response(
|
||||
$this->proxy_request_to_wpcom_as_user( $request, $post_id )
|
||||
);
|
||||
}
|
||||
|
||||
$message = trim( $request->get_param( 'message' ) );
|
||||
$skip_connection_ids = $request->get_param( 'skipped_connections' );
|
||||
$async = (bool) $request->get_param( 'async' );
|
||||
$post = get_post( $post_id );
|
||||
|
||||
if ( empty( $post ) ) {
|
||||
return new WP_Error( 'not_found', __( 'Cannot find that post.', 'jetpack-publicize-pkg' ), array( 'status' => 404 ) );
|
||||
}
|
||||
if ( 'publish' !== $post->post_status ) {
|
||||
return new WP_Error( 'not_published', __( 'Only published posts can be shared.', 'jetpack-publicize-pkg' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
global $publicize;
|
||||
|
||||
// @phan-suppress-next-line PhanUndeclaredMethod - We are on WPCOM where republicize_post is available.
|
||||
$result = $publicize->republicize_post( (int) $post_id, $message, $skip_connection_ids, true, ! $async, get_current_user_id() );
|
||||
if ( false === $result ) {
|
||||
return new WP_Error( 'not_found', __( 'Cannot find that post.', 'jetpack-publicize-pkg' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $result );
|
||||
}
|
||||
}
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
/**
|
||||
* The Jetpack Social Controller class.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize\REST_API;
|
||||
|
||||
use Automattic\Jetpack\Connection\Rest_Authentication;
|
||||
use Automattic\Jetpack\Connection\Traits\WPCOM_REST_API_Proxy_Request;
|
||||
use Automattic\Jetpack\Publicize\Share_Status;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use WP_REST_Server;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Jetpack Social Controller class.
|
||||
*
|
||||
* @phan-constructor-used-for-side-effects
|
||||
*/
|
||||
class Share_Status_Controller extends Base_Controller {
|
||||
|
||||
use WPCOM_REST_API_Proxy_Request;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
$this->base_api_path = 'wpcom';
|
||||
$this->version = 'v2';
|
||||
|
||||
$this->namespace = "{$this->base_api_path}/{$this->version}";
|
||||
$this->rest_base = 'publicize/share-status';
|
||||
|
||||
add_action( 'rest_api_init', array( $this, 'register_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the routes.
|
||||
*/
|
||||
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_permissions_check' ),
|
||||
'args' => array(
|
||||
'post_id' => array(
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
'description' => __( 'The post ID to filter the items by.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/sync',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'receive_share_status' ),
|
||||
'permission_callback' => array( Rest_Authentication::class, 'is_signed_with_blog_token' ),
|
||||
'args' => array(
|
||||
'post_id' => array(
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
'description' => __( 'The post ID to update the data for.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'shares' => array(
|
||||
'type' => 'array',
|
||||
'required' => true,
|
||||
'description' => __( 'The share status items.', 'jetpack-publicize-pkg' ),
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => $this->get_share_item_schema(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Jetpack Social data.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$post_id = $request->get_param( 'post_id' );
|
||||
|
||||
$post = get_post( $post_id );
|
||||
|
||||
if ( empty( $post ) ) {
|
||||
return new WP_Error(
|
||||
'post_not_found',
|
||||
__( 'Cannot find that post.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'publish' !== $post->post_status ) {
|
||||
return new WP_Error(
|
||||
'post_not_published',
|
||||
__( 'Cannot get share status for an unpublished post', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
return rest_ensure_response( Share_Status::get_post_share_status( $post_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the request has access to Jetpack Social data.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public function get_items_permissions_check( $request ) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return $this->publicize_permissions_check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema for a share item.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_share_item_schema() {
|
||||
return array(
|
||||
'status' => array(
|
||||
'description' => __( 'Status of the share.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'message' => array(
|
||||
'description' => __( 'Share message or link.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'timestamp' => array(
|
||||
'description' => __( 'Timestamp of the share.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
'service' => array(
|
||||
'description' => __( 'The service to which it was shared.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'connection_id' => array(
|
||||
'description' => __( 'Connection ID for the share.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
'external_id' => array(
|
||||
'description' => __( 'External ID of the shared post.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'external_name' => array(
|
||||
'description' => __( 'External name of the shared post.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'profile_picture' => array(
|
||||
'description' => __( 'Profile picture URL of the account sharing.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'profile_link' => array(
|
||||
'description' => __( 'Profile link of the sharing account.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'wpcom_user_id' => array(
|
||||
'type' => 'integer',
|
||||
'description' => __( 'wordpress.com ID of the user the connection belongs to.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema for the endpoint.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'jetpack-social-share-status',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'shares' => array(
|
||||
'description' => __( 'List of shares.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => $this->get_share_item_schema(),
|
||||
),
|
||||
),
|
||||
'done' => array(
|
||||
'description' => __( 'Indicates if the process is completed.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive share status from WPCOM.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function receive_share_status( $request ) {
|
||||
|
||||
$post_id = $request->get_param( 'post_id' );
|
||||
$post = get_post( $post_id );
|
||||
|
||||
if ( empty( $post ) ) {
|
||||
return new WP_Error(
|
||||
'post_not_found',
|
||||
__( 'Cannot find that post.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'publish' !== $post->post_status ) {
|
||||
return new WP_Error(
|
||||
'post_not_published',
|
||||
__( 'Cannot update share status for an unpublished post.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
$shares = $request->get_param( 'shares' );
|
||||
|
||||
// This check ensures that the shares data is in the expected format.
|
||||
if ( ! empty( $shares ) && empty( $shares[0]['status'] ) ) {
|
||||
return new WP_Error(
|
||||
'invalid_shares',
|
||||
__( 'Invalid shares data.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
update_post_meta( $post_id, Share_Status::SHARES_META_KEY, $shares );
|
||||
|
||||
$urls = array();
|
||||
|
||||
foreach ( $shares as $share ) {
|
||||
if ( isset( $share['status'] ) && 'success' === $share['status'] ) {
|
||||
$urls[] = array(
|
||||
'url' => $share['message'],
|
||||
'service' => $share['service'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires after Publicize Shares post meta has been saved.
|
||||
*
|
||||
* @param array $urls {
|
||||
* An array of social media shares.
|
||||
* @type array $url URL to the social media post.
|
||||
* @type string $service Social media service shared to.
|
||||
* }
|
||||
*/
|
||||
do_action( 'jetpack_publicize_share_urls_saved', $urls );
|
||||
|
||||
return rest_ensure_response( new WP_REST_Response() );
|
||||
}
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
/**
|
||||
* Publicize: Social Image Generator Controller
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize\REST_API;
|
||||
|
||||
use Automattic\Jetpack\Connection\Traits\WPCOM_REST_API_Proxy_Request;
|
||||
use Automattic\Jetpack\Publicize\Publicize_Utils as Utils;
|
||||
use Automattic\Jetpack\Publicize\Social_Image_Generator as SIG;
|
||||
use Automattic\Jetpack\Publicize\Social_Image_Generator\Templates;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use WP_REST_Server;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Publicize: Social Image Generator Controller class.
|
||||
*
|
||||
* @phan-constructor-used-for-side-effects
|
||||
*/
|
||||
class Social_Image_Generator_Controller extends Base_Controller {
|
||||
|
||||
use WPCOM_REST_API_Proxy_Request;
|
||||
|
||||
/**
|
||||
* The constructor sets the route namespace, rest_base, and registers our API route and endpoint.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
$this->base_api_path = 'wpcom';
|
||||
$this->version = 'v2';
|
||||
|
||||
$this->namespace = "{$this->base_api_path}/{$this->version}";
|
||||
$this->rest_base = 'publicize/social-image-generator';
|
||||
|
||||
$this->allow_requests_as_blog = true;
|
||||
|
||||
add_action( 'rest_api_init', array( $this, 'register_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/generate-token',
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'generate_preview_token' ),
|
||||
'permission_callback' => array( $this, 'permissions_check' ),
|
||||
'private_site_security_settings' => array(
|
||||
'allow_blog_token_access' => true,
|
||||
),
|
||||
'args' => array(
|
||||
'text' => array(
|
||||
'description' => __( 'The text to be used to generate the image.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'image_url' => array(
|
||||
'description' => __( 'The URL of the background image to use when generating the social image.', 'jetpack-publicize-pkg' ),
|
||||
'oneOf' => array(
|
||||
array(
|
||||
'type' => 'string',
|
||||
),
|
||||
array(
|
||||
'type' => 'null',
|
||||
),
|
||||
),
|
||||
),
|
||||
'template' => array(
|
||||
'description' => __( 'The template slug.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
'enum' => Templates::TEMPLATES,
|
||||
),
|
||||
'font' => array(
|
||||
'description' => __( 'The font slug.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
'schema' => array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'publicize-sig-generate-token',
|
||||
'type' => 'string',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/font-options',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_font_options' ),
|
||||
'permission_callback' => array( $this, 'permissions_check' ),
|
||||
'schema' => array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'publicize-sig-font-options',
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Unique identifier for the font.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
'label' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'The font label.', 'jetpack-publicize-pkg' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the user has proper permissions.
|
||||
*
|
||||
* @return boolean|WP_Error True if the user has permissions, WP_Error otherwise.
|
||||
*/
|
||||
public function permissions_check() {
|
||||
$permissions = $this->publicize_permissions_check();
|
||||
|
||||
if ( is_wp_error( $permissions ) ) {
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
// On WPCOM, need to check for the feature.
|
||||
if ( Utils::is_wpcom() ) {
|
||||
require_lib( 'publicize/util/social-image-generator' );
|
||||
|
||||
return \Publicize\Social_Image_Generator\is_enabled();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes the request parameters to the WPCOM endpoint to generate a preview image token.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object, which includes the parameters.
|
||||
* @return WP_REST_Response The response.
|
||||
*/
|
||||
public function generate_preview_token( $request ) {
|
||||
return rest_ensure_response(
|
||||
SIG\fetch_token(
|
||||
$request->get_param( 'text' ),
|
||||
$request->get_param( 'image_url' ),
|
||||
$request->get_param( 'template' ),
|
||||
$request->get_param( 'font' )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the available font options for the social image generator.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object, which includes the parameters.
|
||||
*
|
||||
* @return WP_REST_Response The response containing the font options.
|
||||
*/
|
||||
public function get_font_options( $request ) {
|
||||
if ( Utils::is_wpcom() ) {
|
||||
require_lib( 'publicize/util/social-image-generator' );
|
||||
|
||||
$fonts = \Publicize\Social_Image_Generator\get_font_options();
|
||||
|
||||
$font_options = array();
|
||||
|
||||
foreach ( $fonts as $id => [ 'label' => $label ] ) {
|
||||
$font_options[] = compact( 'id', 'label' );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $font_options );
|
||||
}
|
||||
|
||||
$response = $this->proxy_request_to_wpcom_as_blog(
|
||||
$request,
|
||||
'font-options'
|
||||
);
|
||||
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
/**
|
||||
* PostSettings class.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize\Social_Image_Generator;
|
||||
|
||||
use Automattic\Jetpack\Publicize\Publicize;
|
||||
|
||||
/**
|
||||
* This class is used to get SIG-specific information from a post.
|
||||
*/
|
||||
class Post_Settings {
|
||||
/**
|
||||
* Post to get information from.
|
||||
*
|
||||
* @var int $post_id
|
||||
*/
|
||||
public $post_id;
|
||||
|
||||
/**
|
||||
* The post's settings.
|
||||
*
|
||||
* @var array $settings;
|
||||
*/
|
||||
public $settings;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $post_id Post to get information from.
|
||||
*/
|
||||
public function __construct( $post_id ) {
|
||||
$this->post_id = $post_id;
|
||||
$this->settings = $this->get_settings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SIG settings.
|
||||
*
|
||||
* @param bool $raw Whether to get the raw settings, skipping the defaults.
|
||||
* @return array
|
||||
*/
|
||||
public function get_settings( $raw = false ) {
|
||||
$social_options = $raw
|
||||
? get_metadata_raw( 'post', $this->post_id, Publicize::POST_JETPACK_SOCIAL_OPTIONS, true )
|
||||
: get_post_meta( $this->post_id, Publicize::POST_JETPACK_SOCIAL_OPTIONS, true );
|
||||
|
||||
if ( ! is_array( $social_options ) || empty( $social_options['image_generator_settings'] ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return $social_options['image_generator_settings'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a SIG setting.
|
||||
*
|
||||
* @param string $key The key to update.
|
||||
* @param mixed $value The value to set for the key.
|
||||
* @return bool True if the update was successful.
|
||||
*/
|
||||
public function update_setting( $key, $value ) {
|
||||
$social_options = get_post_meta( $this->post_id, Publicize::POST_JETPACK_SOCIAL_OPTIONS, true );
|
||||
|
||||
if ( empty( $social_options ) ) {
|
||||
$social_options = array();
|
||||
}
|
||||
|
||||
$updated_options = array_replace_recursive( $social_options, array( 'image_generator_settings' => array( $key => $value ) ) );
|
||||
|
||||
$updated = update_post_meta( $this->post_id, Publicize::POST_JETPACK_SOCIAL_OPTIONS, $updated_options );
|
||||
|
||||
if ( $updated ) {
|
||||
$this->settings = $updated_options['image_generator_settings'];
|
||||
}
|
||||
|
||||
return (bool) $updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if SIG is enabled for a specific post.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_enabled() {
|
||||
return ! empty( $this->settings['enabled'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text to use for the generated image.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_custom_text() {
|
||||
if ( ! empty( $this->settings['custom_text'] ) ) {
|
||||
return $this->settings['custom_text'];
|
||||
}
|
||||
|
||||
return get_the_title( $this->post_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the image type for the generated image.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_image_type() {
|
||||
$type = $this->settings['image_type'] ?? null;
|
||||
$featured_image_id = get_post_thumbnail_id( $this->post_id );
|
||||
|
||||
// By default, we use the featured image.
|
||||
if ( empty( $type ) ) {
|
||||
if ( ! empty( $featured_image_id ) ) {
|
||||
return 'featured';
|
||||
} else {
|
||||
return 'default';
|
||||
}
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the image to use for the generated image.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function get_image_url() {
|
||||
$type = $this->get_image_type();
|
||||
|
||||
switch ( $type ) {
|
||||
case 'featured':
|
||||
$image_id = get_post_thumbnail_id( $this->post_id );
|
||||
break;
|
||||
case 'custom':
|
||||
$image_id = $this->settings['image_id'] ?? null;
|
||||
break;
|
||||
case 'none':
|
||||
return null;
|
||||
case 'default':
|
||||
$image_id = $this->settings['default_image_id'] ?? null;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( empty( $image_id ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$url = wp_get_attachment_image_url( $image_id, 'large' );
|
||||
|
||||
if ( ! $url ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template to use for the generated image.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_template() {
|
||||
if ( ! empty( $this->settings['template'] ) ) {
|
||||
return $this->settings['template'];
|
||||
}
|
||||
|
||||
return Templates::DEFAULT_TEMPLATE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the font to use for the text in the generated image.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_font() {
|
||||
return $this->settings['font'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an image token.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_token() {
|
||||
return ! empty( $this->settings['token'] ) ? $this->settings['token'] : '';
|
||||
}
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
/**
|
||||
* Class used to register REST API settings endpoints used by Social Image Generator.
|
||||
*
|
||||
* Flagged to be removed after deprecation.
|
||||
*
|
||||
* @deprecated 0.38.3
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize\Social_Image_Generator;
|
||||
|
||||
use Automattic\Jetpack\Publicize\Jetpack_Social_Settings\Settings as Jetpack_Social_Settings;
|
||||
use WP_Error;
|
||||
use WP_REST_Controller;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use WP_REST_Server;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines our endpoints.
|
||||
*/
|
||||
class REST_Settings_Controller extends WP_REST_Controller {
|
||||
/**
|
||||
* Registers the REST routes on the `rest_api_init` hook.
|
||||
*
|
||||
* Instantiated here, rather than eagerly, so the controller class only loads
|
||||
* on requests that reach `rest_api_init`. Static so the callback can be
|
||||
* unregistered.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register() {
|
||||
( new self() )->register_routes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register REST API endpoints.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/social-image-generator/settings',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_settings' ),
|
||||
'permission_callback' => array( $this, 'settings_permissions_callback' ),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'update_settings' ),
|
||||
'permission_callback' => array( $this, 'settings_permissions_callback' ),
|
||||
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET `/jetpack/v4/social-image-generator/settings`
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function get_settings() {
|
||||
$settings = ( new Jetpack_Social_Settings() )->get_settings();
|
||||
$response = array();
|
||||
$schema = $this->get_item_schema();
|
||||
$properties = array_keys( $schema['properties'] );
|
||||
|
||||
if ( in_array( 'enabled', $properties, true ) ) {
|
||||
$response['enabled'] = $settings['socialImageGeneratorSettings']['enabled'];
|
||||
}
|
||||
|
||||
if ( in_array( 'default_image_id', $properties, true ) ) {
|
||||
$response['default_image_id'] = $settings['socialImageGeneratorSettings']['default_image_id'];
|
||||
}
|
||||
|
||||
if ( in_array( 'defaults', $properties, true ) ) {
|
||||
$response['defaults'] = array( 'template' => $settings['socialImageGeneratorSettings']['template'] );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* POST `/jetpack/v4/social-image-generator/settings`
|
||||
*
|
||||
* @param WP_REST_Request $request The API request.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function update_settings( $request ) {
|
||||
$settings = new Jetpack_Social_Settings();
|
||||
|
||||
if ( isset( $request['enabled'] ) ) {
|
||||
$settings->update_social_image_generator_settings( array( 'enabled' => $request['enabled'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $request['default_image_id'] ) ) {
|
||||
$settings->update_social_image_generator_settings( array( 'default_image_id' => $request['default_image_id'] ) );
|
||||
}
|
||||
|
||||
if ( $request['defaults'] && $request['defaults']['template'] ) {
|
||||
$settings->update_social_image_generator_settings( array( 'template' => $request['defaults']['template'] ) );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $this->get_settings() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the permissions for accessing and updating the settings endpoint.
|
||||
*
|
||||
* @return bool|WP_Error True if user can manage options.
|
||||
*/
|
||||
public function settings_permissions_callback() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_forbidden_context',
|
||||
__( 'Sorry, you are not allowed to access this endpoint.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the Social Image Generator's settings schema, conforming to JSON Schema.
|
||||
*
|
||||
* @return array Schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'social-image-generator-settings',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'enabled' => array(
|
||||
'description' => __( 'Whether or not Social Image Generator is enabled.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'boolean',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'default_image_id' => array(
|
||||
'description' => __( 'The default image ID for the Social Image Generator.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'defaults' => array(
|
||||
'description' => __( 'The default settings for a new generated image.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'properties' => array(
|
||||
'template' => array(
|
||||
'type' => 'string',
|
||||
'enum' => Templates::TEMPLATES,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return rest_default_additional_properties_to_false( $schema );
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
/**
|
||||
* Defines the endpoints used for handling tokens for the Social Image Generator.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize\Social_Image_Generator;
|
||||
|
||||
use Automattic\Jetpack\Publicize\Publicize_Utils as Utils;
|
||||
use WP_Error;
|
||||
use WP_REST_Controller;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* Class used to register token related REST API endpoints used by Social Image Generator.
|
||||
*/
|
||||
class REST_Token_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Registers the REST routes on the `rest_api_init` hook.
|
||||
*
|
||||
* Instantiated here, rather than eagerly, so the controller class only loads
|
||||
* on requests that reach `rest_api_init`. Static so the callback can be
|
||||
* unregistered.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register() {
|
||||
( new self() )->register_routes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register REST API endpoints.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/social-image-generator/generate-preview-token',
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'generate_preview_token' ),
|
||||
'permission_callback' => array( $this, 'permissions_check' ),
|
||||
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes the request parameters to the WPCOM endpoint to generate a preview image token.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object, which includes the parameters.
|
||||
* @return array|WP_Error The token or an error.
|
||||
*/
|
||||
public function generate_preview_token( $request ) {
|
||||
|
||||
Utils::endpoint_deprecated_warning(
|
||||
__METHOD__,
|
||||
'jetpack-14.5, jetpack-social-6.2.3',
|
||||
'jetpack/v4/social-image-generator/generate-preview-token',
|
||||
'wpcom/v2/publicize/social-image-generator/generate-token'
|
||||
);
|
||||
|
||||
$text = $request->get_param( 'text' );
|
||||
$image_url = $request->get_param( 'image_url' );
|
||||
$template = $request->get_param( 'template' );
|
||||
|
||||
return fetch_token( $text, $image_url, $template );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the current user permissions for the endpoints.
|
||||
*
|
||||
* @return bool|WP_Error True if user can manage options.
|
||||
*/
|
||||
public function permissions_check() {
|
||||
if ( ! current_user_can( 'edit_posts' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_forbidden_context',
|
||||
__( 'Sorry, you are not allowed to access this endpoint.', 'jetpack-publicize-pkg' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the JSON schema for the token generation.
|
||||
*
|
||||
* @return array Schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'social-image-generator-token',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'text' => array(
|
||||
'description' => __( 'The text to be used to generate the image.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'context' => array( 'edit' ),
|
||||
),
|
||||
'image_url' => array(
|
||||
'description' => __( 'The URL of the background image to use when generating the social image.', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
'required' => false,
|
||||
'context' => array( 'edit' ),
|
||||
),
|
||||
'template' => array(
|
||||
'description' => __( 'The template slug', 'jetpack-publicize-pkg' ),
|
||||
'type' => 'string',
|
||||
'enum' => Templates::TEMPLATES,
|
||||
'required' => false,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return rest_default_additional_properties_to_false( $schema );
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
/**
|
||||
* Settings class.
|
||||
*
|
||||
* Flagged to be removed after deprecation.
|
||||
*
|
||||
* @deprecated 0.38.3
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize\Social_Image_Generator;
|
||||
|
||||
use Automattic\Jetpack\Publicize\Jetpack_Social_Settings\Settings as Jetpack_Social_Settings;
|
||||
|
||||
/**
|
||||
* This class is used to get and update SIG-specific global settings.
|
||||
*/
|
||||
class Settings {
|
||||
/**
|
||||
* Name of the database option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const OPTION_NAME = 'jetpack_social_image_generator_settings';
|
||||
|
||||
/**
|
||||
* Array with SIG's settings.
|
||||
*
|
||||
* @var array $settings
|
||||
*/
|
||||
public $settings;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->settings = $this->get_settings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all current SIG settings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_settings() {
|
||||
$new_settings = ( new Jetpack_Social_Settings() )->get_settings();
|
||||
|
||||
return array(
|
||||
'enabled' => $new_settings['socialImageGeneratorSettings']['enabled'],
|
||||
'default_image_id' => $new_settings['socialImageGeneratorSettings']['default_image_id'],
|
||||
'defaults' => array(
|
||||
'template' => $new_settings['socialImageGeneratorSettings']['template'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if SIG is available.
|
||||
*
|
||||
* @return bool True if SIG is available, false otherwise.
|
||||
*/
|
||||
public function is_available() {
|
||||
return ( new Jetpack_Social_Settings() )->is_sig_available();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if SIG is enabled.
|
||||
*
|
||||
* @return bool True if SIG is enabled, false otherwise.
|
||||
*/
|
||||
public function is_enabled() {
|
||||
$new_settings = ( new Jetpack_Social_Settings() )->get_settings();
|
||||
|
||||
return $new_settings['socialImageGeneratorSettings']['enabled'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all current defaults.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_defaults() {
|
||||
if ( isset( $this->settings['defaults'] ) ) {
|
||||
return $this->settings['defaults'];
|
||||
}
|
||||
|
||||
return array(
|
||||
'template' => Templates::DEFAULT_TEMPLATE,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current default template.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_default_template() {
|
||||
$defaults = $this->get_defaults();
|
||||
|
||||
return $defaults['template'] ?? Templates::DEFAULT_TEMPLATE;
|
||||
}
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
/**
|
||||
* Setup class.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize\Social_Image_Generator;
|
||||
|
||||
use Automattic\Jetpack\Publicize\Jetpack_Social_Settings\Settings;
|
||||
|
||||
/**
|
||||
* Class for setting up Social Image Generator-related functionality.
|
||||
*/
|
||||
class Setup {
|
||||
/**
|
||||
* Initialise SIG-related functionality.
|
||||
*/
|
||||
public function init() {
|
||||
if ( ! ( new Settings() )->is_sig_available() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Be wary of any code that you add to this file, since this function is called on plugin load.
|
||||
// We're using the `wp_after_insert_post` hook because we need access to the updated post meta. By using the default priority
|
||||
// of 10 we make sure that our code runs before Sync processes the post.
|
||||
add_action( 'wp_after_insert_post', array( $this, 'generate_token_on_save' ), 10, 3 );
|
||||
add_action( 'jetpack_social_sig_warm_image', array( $this, 'warm_social_image' ) );
|
||||
add_action( 'rest_api_init', array( REST_Token_Controller::class, 'register' ) );
|
||||
|
||||
// Flagged to be removed after deprecation.
|
||||
// @deprecated 0.38.3
|
||||
add_action( 'rest_api_init', array( REST_Settings_Controller::class, 'register' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a token from WPCOM to generate the social image for the post, and save it locally.
|
||||
*
|
||||
* @param Post_Settings $post_settings A Post_Settings object that can be used to save the generated token.
|
||||
*/
|
||||
public function generate_token( $post_settings ) {
|
||||
if ( ! $post_settings->is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$token = fetch_token(
|
||||
$post_settings->get_custom_text(),
|
||||
$post_settings->get_image_url(),
|
||||
$post_settings->get_template(),
|
||||
$post_settings->get_font()
|
||||
);
|
||||
|
||||
if ( is_wp_error( $token ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post_settings->update_setting( 'token', sanitize_text_field( $token ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger token generation for a post if SIG is enabled.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param \WP_Post $post The post object being saved.
|
||||
* @param bool $update Whether this is an update to a post.
|
||||
*/
|
||||
public function generate_token_on_save( $post_id, $post, $update ) {
|
||||
if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're not using the block editor for this post, do not continue.
|
||||
if ( ! use_block_editor_for_post( $post ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $publicize;
|
||||
|
||||
if ( ! $publicize->post_type_is_publicizeable( $post->post_type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$settings = new Settings();
|
||||
|
||||
if ( ! $settings->is_sig_available() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( wp_is_post_autosave( $post ) || wp_is_post_revision( $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set SIG to be enabled by default for new posts if the toggle is on.
|
||||
$post_settings = new Post_Settings( $post_id );
|
||||
if (
|
||||
! $update &&
|
||||
'auto-draft' === $post->post_status &&
|
||||
! empty( $settings->get_settings()['socialImageGeneratorSettings']['enabled'] ) &&
|
||||
empty( $post_settings->get_settings( true ) ) &&
|
||||
'jetpack-social-note' !== $post->post_type
|
||||
) {
|
||||
$post_settings->update_setting( 'enabled', true );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $post->post_status === 'auto-draft' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $post_settings->is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->generate_token( $post_settings );
|
||||
|
||||
// Prime the Social Image Generator cache out-of-band right after publish.
|
||||
// SIG renders the preview image on the first request to its URL, so a post
|
||||
// shared immediately after publishing can race that cold render and end up
|
||||
// with no preview image (notably on X, which does not retry). Warming the
|
||||
// URL here means the image is already rendered and edge-cached before any
|
||||
// crawler fetches it. Scheduled rather than inline so it never delays the
|
||||
// publish request itself.
|
||||
if (
|
||||
'publish' === $post->post_status &&
|
||||
! wp_next_scheduled( 'jetpack_social_sig_warm_image', array( $post_id ) )
|
||||
) {
|
||||
wp_schedule_single_event( time(), 'jetpack_social_sig_warm_image', array( $post_id ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Warm the edge cache for a post's generated social image.
|
||||
*
|
||||
* Runs from a scheduled single event (see generate_token_on_save) so it never
|
||||
* blocks the publish request. Issues one blocking request to the same URL the
|
||||
* Open Graph tags expose, which forces the on-demand render and lets the full
|
||||
* response populate the edge cache before a crawler fetches it.
|
||||
*
|
||||
* @param int $post_id Post ID whose social image should be primed.
|
||||
*/
|
||||
public function warm_social_image( $post_id ) {
|
||||
$post_settings = new Post_Settings( $post_id );
|
||||
|
||||
if ( ! $post_settings->is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$image_url = get_image_url( $post_id );
|
||||
|
||||
if ( empty( $image_url ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Blocking so the rendered response travels back through the edge cache and
|
||||
// is stored; redirection is followed to the final image URL the crawler hits.
|
||||
wp_remote_get(
|
||||
$image_url,
|
||||
array(
|
||||
'timeout' => 15,
|
||||
'redirection' => 5,
|
||||
'blocking' => true,
|
||||
'user-agent' => 'WordPress.com Social Image Generator cache warmer',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* Templates class.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize\Social_Image_Generator;
|
||||
|
||||
/**
|
||||
* This class is used to get information about templates.
|
||||
*/
|
||||
class Templates {
|
||||
/**
|
||||
* Available templates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
const TEMPLATES = array( 'highway', 'dois', 'fullscreen', 'edge' );
|
||||
|
||||
/**
|
||||
* Default template for new posts.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DEFAULT_TEMPLATE = 'highway';
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* Utilities.
|
||||
*
|
||||
* @package automattic/jetpack-publicize
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Publicize\Social_Image_Generator;
|
||||
|
||||
use Automattic\Jetpack\Publicize\Publicize_Utils as Utils;
|
||||
use Automattic\Jetpack\Publicize\REST_API\Proxy_Requests;
|
||||
use Automattic\Jetpack\Redirect;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
|
||||
/**
|
||||
* Given a post ID, returns the image URL for the generated image.
|
||||
*
|
||||
* @param int $post_id Post ID to get the URL for.
|
||||
* @return string
|
||||
*/
|
||||
function get_image_url( $post_id ) {
|
||||
$post_settings = new Post_Settings( $post_id );
|
||||
$token = $post_settings->get_token();
|
||||
|
||||
if ( ! $post_settings->is_enabled() || empty( $token ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return add_query_arg(
|
||||
array( 'query' => rawurlencode( 't=' . $token ) ),
|
||||
Redirect::get_url( 'sigenerate', array( 'site' => null ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parameters for the token body.
|
||||
*
|
||||
* @param string $text Text to use in the generated image.
|
||||
* @param string $image_url Image to use in the generated image.
|
||||
* @param string $template Template to use in the generated image.
|
||||
* @param string $font Font to use in the generated image.
|
||||
* @return array
|
||||
*/
|
||||
function get_token_body( $text, $image_url, $template, $font = '' ) {
|
||||
return array(
|
||||
'text' => $text,
|
||||
'image_url' => $image_url,
|
||||
'template' => $template,
|
||||
'font' => $font,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a token from the WPCOM endpoint.
|
||||
*
|
||||
* @param string $text The text that will be displayed on the generated image.
|
||||
* @param string $image_url The background image URL to be used in the generated image.
|
||||
* @param string $template The template slug to use for generating the image.
|
||||
* @param string $font The font slug to use for the text in the image.
|
||||
* @return string|WP_Error The generated token or a WP_Error object if there's been a problem.
|
||||
*/
|
||||
function fetch_token( $text, $image_url, $template, $font = '' ) {
|
||||
|
||||
$args = get_token_body( $text, $image_url, $template, $font );
|
||||
|
||||
if ( Utils::is_wpcom() ) {
|
||||
require_lib( 'social-image-generator-token' );
|
||||
require_lib( 'publicize/util/social-image-generator' );
|
||||
|
||||
if ( ! \Publicize\Social_Image_Generator\is_enabled() ) {
|
||||
return new WP_Error( 'social_image_generator_not_enabled', __( 'Social Image Generator is not enabled.', 'jetpack-publicize-pkg' ) );
|
||||
}
|
||||
|
||||
return \Social_Image_Generator\generate_token( $args );
|
||||
}
|
||||
|
||||
$proxy = new Proxy_Requests( 'publicize/social-image-generator' );
|
||||
|
||||
$request = new WP_REST_Request( 'POST' );
|
||||
|
||||
$request->set_body( wp_json_encode( $args, JSON_UNESCAPED_SLASHES ) );
|
||||
|
||||
return $proxy->proxy_request_to_wpcom_as_blog( $request, 'generate-token' );
|
||||
}
|
||||
Reference in New Issue
Block a user