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' ), ), 'hyperlinks' => array( 'description' => __( 'Editor hyperlinks preserved from content or excerpt placeholders.', 'jetpack-publicize-pkg' ), 'type' => 'array', 'readonly' => true, 'context' => array( 'view', 'edit' ), 'items' => array( 'type' => 'object', 'properties' => array( 'text' => array( 'type' => 'string' ), 'href' => array( 'type' => 'string' ), 'occurrence' => array( 'type' => 'integer' ), ), ), ), '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() ) { if ( ! wpcom_site_has_feature( \WPCOM_Features::SOCIAL_ENHANCED_PUBLISHING ) ) { 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 ) ); } require_lib( 'publicize/util/message-templates' ); 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 ) ); } }