← All changes
|
_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v3-endpoint-blogging-prompts.php
+584
-0
16.2-beta
→
16.3-beta
View file →
| @@ -1,0 +1,584 @@ | ||
| 1 | +<?php | |
| 2 | +/** | |
| 3 | + * Blogging prompts endpoint for wpcom/v3. | |
| 4 | + * | |
| 5 | + * @package automattic/jetpack | |
| 6 | + */ | |
| 7 | + | |
| 8 | +use Automattic\Jetpack\Connection\Traits\WPCOM_REST_API_Proxy_Request; | |
| 9 | + | |
| 10 | +if ( ! defined( 'ABSPATH' ) ) { | |
| 11 | + exit( 0 ); | |
| 12 | +} | |
| 13 | + | |
| 14 | +/** | |
| 15 | + * REST API endpoint wpcom/v3/sites/%s/blogging-prompts. | |
| 16 | + */ | |
| 17 | +class WPCOM_REST_API_V3_Endpoint_Blogging_Prompts extends WP_REST_Posts_Controller { | |
| 18 | + | |
| 19 | + use WPCOM_REST_API_Proxy_Request; | |
| 20 | + | |
| 21 | + const TEMPLATE_BLOG_ID = 205876834; | |
| 22 | + | |
| 23 | + /** | |
| 24 | + * Whether the endpoint is running on wpcom, or not. | |
| 25 | + * | |
| 26 | + * @var bool | |
| 27 | + */ | |
| 28 | + public $is_wpcom; | |
| 29 | + | |
| 30 | + /** | |
| 31 | + * Day of the year, from 1 to 366, and 0 representing no query. | |
| 32 | + * | |
| 33 | + * Used with yearless dates like `--12-20`, to get prompts by month and day, regardless of year. | |
| 34 | + * | |
| 35 | + * @var integer | |
| 36 | + */ | |
| 37 | + public $day_of_year_query = 0; | |
| 38 | + | |
| 39 | + /** | |
| 40 | + * A year used to force one prompt per day for a specific year. | |
| 41 | + * | |
| 42 | + * @var integer | |
| 43 | + */ | |
| 44 | + public $force_year = 0; | |
| 45 | + | |
| 46 | + /** | |
| 47 | + * Constructor. | |
| 48 | + */ | |
| 49 | + public function __construct() { | |
| 50 | + $this->post_type = 'post'; | |
| 51 | + $this->base_api_path = 'wpcom'; | |
| 52 | + $this->version = 'v3'; | |
| 53 | + $this->namespace = $this->base_api_path . '/' . $this->version; | |
| 54 | + $this->rest_base = 'blogging-prompts'; | |
| 55 | + $this->wpcom_is_wpcom_only_endpoint = true; | |
| 56 | + $this->wpcom_is_site_specific_endpoint = true; | |
| 57 | + $this->is_wpcom = defined( 'IS_WPCOM' ) && IS_WPCOM; | |
| 58 | + | |
| 59 | + add_action( 'rest_api_init', array( $this, 'register_routes' ) ); | |
| 60 | + } | |
| 61 | + | |
| 62 | + /** | |
| 63 | + * Registers the routes for blogging prompts. | |
| 64 | + * | |
| 65 | + * @see register_rest_route() | |
| 66 | + */ | |
| 67 | + public function register_routes() { | |
| 68 | + register_rest_route( | |
| 69 | + $this->namespace, | |
| 70 | + '/' . $this->rest_base, | |
| 71 | + array( | |
| 72 | + array( | |
| 73 | + 'methods' => WP_REST_Server::READABLE, | |
| 74 | + 'callback' => array( $this, 'get_items' ), | |
| 75 | + 'permission_callback' => array( $this, 'permissions_check' ), | |
| 76 | + 'args' => $this->get_collection_params(), | |
| 77 | + ), | |
| 78 | + 'schema' => array( $this, 'get_item_schema' ), | |
| 79 | + ) | |
| 80 | + ); | |
| 81 | + | |
| 82 | + register_rest_route( | |
| 83 | + $this->namespace, | |
| 84 | + '/' . $this->rest_base . '/(?P<id>[\d]+)', | |
| 85 | + array( | |
| 86 | + 'args' => array( | |
| 87 | + 'id' => array( | |
| 88 | + 'description' => __( 'Unique identifier for the prompt.', 'jetpack' ), | |
| 89 | + 'type' => 'integer', | |
| 90 | + ), | |
| 91 | + ), | |
| 92 | + array( | |
| 93 | + 'methods' => WP_REST_Server::READABLE, | |
| 94 | + 'callback' => array( $this, 'get_item' ), | |
| 95 | + 'permission_callback' => array( $this, 'permissions_check' ), | |
| 96 | + ), | |
| 97 | + 'schema' => array( $this, 'get_item_schema' ), | |
| 98 | + ) | |
| 99 | + ); | |
| 100 | + } | |
| 101 | + | |
| 102 | + /** | |
| 103 | + * Retrieves a collection of blogging prompts. | |
| 104 | + * | |
| 105 | + * @param WP_REST_Request $request Full details about the request. | |
| 106 | + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. | |
| 107 | + */ | |
| 108 | + public function get_items( $request ) { | |
| 109 | + if ( ! $this->is_wpcom ) { | |
| 110 | + return $this->proxy_request_to_wpcom( $request, '', 'user', true ); | |
| 111 | + } | |
| 112 | + | |
| 113 | + if ( $request->get_param( 'force_year' ) ) { | |
| 114 | + $this->force_year = $request->get_param( 'force_year' ); | |
| 115 | + } | |
| 116 | + | |
| 117 | + switch_to_blog( self::TEMPLATE_BLOG_ID ); | |
| 118 | + add_action( 'pre_get_posts', array( $this, 'modify_query' ) ); | |
| 119 | + add_filter( 'posts_clauses', array( $this, 'filter_sql' ), 10, 2 ); | |
| 120 | + $items = parent::get_items( $request ); | |
| 121 | + remove_filter( 'posts_clauses', array( $this, 'filter_sql' ) ); | |
| 122 | + remove_action( 'pre_get_posts', array( $this, 'modify_query' ) ); | |
| 123 | + restore_current_blog(); | |
| 124 | + | |
| 125 | + // Reset so a later query in the same request can never inherit this state. | |
| 126 | + $this->day_of_year_query = 0; | |
| 127 | + | |
| 128 | + return $items; | |
| 129 | + } | |
| 130 | + | |
| 131 | + /** | |
| 132 | + * Retrieves a single blogging prompt. | |
| 133 | + * | |
| 134 | + * @param WP_REST_Request $request Full details about the request. | |
| 135 | + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. | |
| 136 | + */ | |
| 137 | + public function get_item( $request ) { | |
| 138 | + if ( ! $this->is_wpcom ) { | |
| 139 | + return $this->proxy_request_to_wpcom( $request, $request->get_param( 'id' ), 'user', true ); | |
| 140 | + } | |
| 141 | + | |
| 142 | + if ( $request->get_param( 'force_year' ) ) { | |
| 143 | + $this->force_year = $request->get_param( 'force_year' ); | |
| 144 | + } | |
| 145 | + | |
| 146 | + switch_to_blog( self::TEMPLATE_BLOG_ID ); | |
| 147 | + $item = parent::get_item( $request ); | |
| 148 | + restore_current_blog(); | |
| 149 | + | |
| 150 | + return $item; | |
| 151 | + } | |
| 152 | + | |
| 153 | + /** | |
| 154 | + * Modify the posts query using the {@see 'pre_get_posts'} hook. | |
| 155 | + * | |
| 156 | + * @param WP_Query $wp_query The WP_Query instance (passed by reference). | |
| 157 | + */ | |
| 158 | + public function modify_query( &$wp_query ) { | |
| 159 | + // parent::get_items() renders each prompt while this hook is still attached, | |
| 160 | + // and rendering can spawn nested WP_Querys (e.g. Gutenberg's wp_global_styles | |
| 161 | + // lookup), so only ever touch the prompts query itself. | |
| 162 | + if ( $this->post_type !== $wp_query->get( 'post_type' ) ) { | |
| 163 | + return; | |
| 164 | + } | |
| 165 | + | |
| 166 | + $date_query = $wp_query->get( 'date_query' ); | |
| 167 | + | |
| 168 | + if ( is_array( $date_query ) ) { | |
| 169 | + $wp_query->set( 'date_query', array_map( array( $this, 'map_date_query' ), $date_query ) ); | |
| 170 | + // Mark the query so filter_sql() only modifies this one. | |
| 171 | + $wp_query->set( 'jetpack_blogging_prompts', true ); | |
| 172 | + } | |
| 173 | + } | |
| 174 | + | |
| 175 | + /** | |
| 176 | + * Modify date_query items when querying prompts. | |
| 177 | + * | |
| 178 | + * @link https://developer.wordpress.org/reference/classes/WP_Query/#date-parameters | |
| 179 | + * | |
| 180 | + * @param array|string|null $date_query Date query. | |
| 181 | + * @return array|string|null Modified date query. | |
| 182 | + */ | |
| 183 | + public function map_date_query( $date_query ) { | |
| 184 | + if ( isset( $date_query['after'] ) ) { | |
| 185 | + // `after` date queries should include posts on the specified date, so force `inclusive` queries. | |
| 186 | + $date_query['inclusive'] = true; | |
| 187 | + | |
| 188 | + // If using a "year-less" date, e.g. `--03-16`, override the date_query, and prepare to modify sql manually. | |
| 189 | + // `after` should be a date string when making API requests, rather than an array. | |
| 190 | + if ( is_string( $date_query['after'] ) && str_starts_with( $date_query['after'], '-' ) ) { | |
| 191 | + $date = date_create_from_format( '--m-d', $date_query['after'] ); | |
| 192 | + | |
| 193 | + if ( false !== $date ) { | |
| 194 | + // PHP day of the year starts with 0; normalize to match SQL DAYOFTHEYEAR which starts with 1. | |
| 195 | + $this->day_of_year_query = $date->format( 'z' ) + 1; | |
| 196 | + | |
| 197 | + // Unset the date query, since we'll by modifying the SQL manually. | |
| 198 | + return null; | |
| 199 | + } | |
| 200 | + } | |
| 201 | + } | |
| 202 | + | |
| 203 | + return $date_query; | |
| 204 | + } | |
| 205 | + | |
| 206 | + /** | |
| 207 | + * Modify post sql for custom date ordering using the {@see 'posts_clauses'} hook. | |
| 208 | + * | |
| 209 | + * @param array $clauses SQL clauses for the current query. | |
| 210 | + * @param WP_Query|null $query The WP_Query instance being filtered. | |
| 211 | + * @return array Modified SQL clauses. | |
| 212 | + */ | |
| 213 | + public function filter_sql( $clauses, $query = null ) { | |
| 214 | + global $wpdb; | |
| 215 | + if ( ! $query instanceof WP_Query || ! $query->get( 'jetpack_blogging_prompts' ) ) { | |
| 216 | + return $clauses; | |
| 217 | + } | |
| 218 | + if ( $this->day_of_year_query > 0 ) { | |
| 219 | + $day = $this->day_of_year_query; | |
| 220 | + $year = $this->force_year ? $this->force_year : wp_date( 'Y' ); | |
| 221 | + | |
| 222 | + // Grab the current sort order, `ASC` or `DESC`, so we can reuse it. | |
| 223 | + $exploded = explode( ' ', $clauses['orderby'] ); | |
| 224 | + $order = end( $exploded ); | |
| 225 | + | |
| 226 | + // Calculate the day of year for each prompt, from 1 to 366, but use the current year so that prompts published | |
| 227 | + // during leap years have the correct day for non-leap years. | |
| 228 | + $fields = $clauses['fields'] . $wpdb->prepare( ", DAYOFYEAR(CONCAT(%d, DATE_FORMAT({$wpdb->posts}.post_date, '-%%m-%%d'))) AS day_of_year", $year ); | |
| 229 | + | |
| 230 | + // When it's not a leap year, exclude posts used for Feb 29th. DAYOFYEAR will return null for Feb 29th on non-leap years. | |
| 231 | + $where = $clauses['where'] . $wpdb->prepare( " AND DAYOFYEAR(CONCAT(%d, DATE_FORMAT({$wpdb->posts}.post_date, '-%%m-%%d'))) IS NOT NULL", $year ); | |
| 232 | + | |
| 233 | + // Order posts regardless of year: get a list of posts for each day, | |
| 234 | + // starting with the query date through the end of the year, then from the start of the year through the day before. | |
| 235 | + $orderby = $wpdb->prepare( | |
| 236 | + 'CASE ' . | |
| 237 | + 'WHEN day_of_year < %d ' . | |
| 238 | + // Push posts from the beginning of the year until the day before to the end. | |
| 239 | + 'THEN day_of_year + 366 ' . | |
| 240 | + // Otherwise order posts from the query date through the end of the year. | |
| 241 | + 'ELSE day_of_year ' . | |
| 242 | + 'END' . | |
| 243 | + // Sort posts for the same day by year, in asc or desc order. | |
| 244 | + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- order string cannot be escaped. | |
| 245 | + ", YEAR({$wpdb->posts}.post_date) " . ( 'DESC' === $order ? 'DESC' : 'ASC' ), | |
| 246 | + $day | |
| 247 | + ); | |
| 248 | + | |
| 249 | + if ( $this->force_year ) { | |
| 250 | + // If we're forcing the year, group by day of year, so that we only get one prompt per day. | |
| 251 | + $clauses['groupby'] = 'day_of_year'; | |
| 252 | + | |
| 253 | + // Ensure we get either to newest or oldest prompt for each day of the year, depending on the sort order. | |
| 254 | + // GROUP BY runs and collects the prompts for each day of the year before ORDER BY is run, so we first need to use MAX/MIN on post_date | |
| 255 | + // to find the most recent/oldest prompt for each day and join the results to the main query. | |
| 256 | + $clauses['join'] = $wpdb->prepare( | |
| 257 | + 'INNER JOIN (' . | |
| 258 | + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- SQL function cannot be escaped. | |
| 259 | + 'SELECT ' . ( 'DESC' === $order ? 'MAX' : 'MIN' ) . "({$wpdb->posts}.post_date) AS post_date, DAYOFYEAR(CONCAT(%d, DATE_FORMAT(post_date, '-%%m-%%d'))) AS day_of_year " . | |
| 260 | + "FROM {$wpdb->posts} " . | |
| 261 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- reuses unmodified existing clause. | |
| 262 | + "WHERE 1=1 {$clauses['where']} " . | |
| 263 | + 'GROUP BY day_of_year' . | |
| 264 | + ") AS newest_prompts ON {$wpdb->posts}.post_date = newest_prompts.post_date", | |
| 265 | + $year | |
| 266 | + ); | |
| 267 | + } | |
| 268 | + | |
| 269 | + $clauses['fields'] = $fields; | |
| 270 | + $clauses['where'] = $where; | |
| 271 | + $clauses['orderby'] = $orderby; | |
| 272 | + } | |
| 273 | + | |
| 274 | + return $clauses; | |
| 275 | + } | |
| 276 | + | |
| 277 | + /** | |
| 278 | + * Prepares a single blogging prompt output for response. | |
| 279 | + * | |
| 280 | + * @param WP_Post $prompt Post object. | |
| 281 | + * @param WP_REST_Request $request Request object. | |
| 282 | + * @return WP_REST_Response Response object. | |
| 283 | + */ | |
| 284 | + public function prepare_item_for_response( $prompt, $request ) { | |
| 285 | + require_once WP_CONTENT_DIR . '/lib/blogging-prompts/answers.php'; | |
| 286 | + require_once WP_CONTENT_DIR . '/lib/blogging-prompts/utils.php'; | |
| 287 | + | |
| 288 | + $fields = $this->get_fields_for_response( $request ); | |
| 289 | + | |
| 290 | + // Base fields for every post. | |
| 291 | + $data = array(); | |
| 292 | + | |
| 293 | + if ( rest_is_field_included( 'id', $fields ) ) { | |
| 294 | + $data['id'] = $prompt->ID; | |
| 295 | + } | |
| 296 | + | |
| 297 | + if ( rest_is_field_included( 'date', $fields ) ) { | |
| 298 | + $data['date'] = $this->prepare_date_response( $prompt->post_date_gmt ); | |
| 299 | + } | |
| 300 | + | |
| 301 | + if ( rest_is_field_included( 'label', $fields ) ) { | |
| 302 | + if ( $this->is_in_bloganuary( $prompt->post_date_gmt ) ) { | |
| 303 | + $data['label'] = __( 'Bloganuary writing prompt', 'jetpack' ); | |
| 304 | + } else { | |
| 305 | + $data['label'] = __( 'Daily writing prompt', 'jetpack' ); | |
| 306 | + } | |
| 307 | + } | |
| 308 | + | |
| 309 | + if ( rest_is_field_included( 'text', $fields ) ) { | |
| 310 | + $text = \BloggingPrompts\prompt_without_blocks( $prompt->post_content ); | |
| 311 | + // Allow translating a variable, since this text is imported from bloggingpromptstemplates.wordpress.com for translation. | |
| 312 | + $translated_text = __( $text, 'jetpack' ); // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText | |
| 313 | + $data['text'] = wp_kses( $translated_text, wp_kses_allowed_html( 'post' ) ); | |
| 314 | + } | |
| 315 | + | |
| 316 | + if ( rest_is_field_included( 'attribution', $fields ) ) { | |
| 317 | + $data['attribution'] = esc_html( get_post_meta( $prompt->ID, 'blogging_prompts_attribution', true ) ); | |
| 318 | + } | |
| 319 | + | |
| 320 | + // Will always be false when requesting as blog. | |
| 321 | + if ( rest_is_field_included( 'answered', $fields ) ) { | |
| 322 | + $data['answered'] = (bool) \A8C\BloggingPrompts\Answers::is_answered_by_user( $prompt->ID, get_current_user_id() ); | |
| 323 | + } | |
| 324 | + | |
| 325 | + if ( rest_is_field_included( 'answered_users_count', $fields ) ) { | |
| 326 | + $data['answered_users_count'] = (int) \A8C\BloggingPrompts\Answers::get_count( $prompt->ID ); | |
| 327 | + } | |
| 328 | + | |
| 329 | + if ( rest_is_field_included( 'answered_users_sample', $fields ) ) { | |
| 330 | + $data['answered_users_sample'] = $this->build_answering_users_sample( $prompt->ID ); | |
| 331 | + } | |
| 332 | + | |
| 333 | + if ( rest_is_field_included( 'answered_link', $fields ) ) { | |
| 334 | + if ( $this->is_in_bloganuary( $prompt->post_date_gmt ) ) { | |
| 335 | + $bloganuary_id = $this->get_bloganuary_id( $prompt->post_date_gmt ); | |
| 336 | + $data['answered_link'] = esc_url( "https://wordpress.com/tag/{$bloganuary_id}" ); | |
| 337 | + } else { | |
| 338 | + $data['answered_link'] = esc_url( "https://wordpress.com/tag/dailyprompt-{$prompt->ID}" ); | |
| 339 | + } | |
| 340 | + } | |
| 341 | + | |
| 342 | + if ( rest_is_field_included( 'answered_link_text', $fields ) ) { | |
| 343 | + $data['answered_link_text'] = __( 'View all responses', 'jetpack' ); | |
| 344 | + } | |
| 345 | + | |
| 346 | + if ( $this->is_in_bloganuary( $prompt->post_date_gmt ) && rest_is_field_included( 'bloganuary_id', $fields ) ) { | |
| 347 | + $data['bloganuary_id'] = $this->get_bloganuary_id( $prompt->post_date_gmt ); | |
| 348 | + } | |
| 349 | + | |
| 350 | + return $data; | |
| 351 | + } | |
| 352 | + | |
| 353 | + /** | |
| 354 | + * Return true if the post is in "Bloganuary" | |
| 355 | + * | |
| 356 | + * @param string $post_date_gmt Unused - Post date in GMT. | |
| 357 | + * @return bool Always returns false as Bloganuary is disabled. | |
| 358 | + */ | |
| 359 | + protected function is_in_bloganuary( $post_date_gmt ) { //phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable | |
| 360 | + | |
| 361 | + /* | |
| 362 | + Disable for January 2025 and beyond (see https://wp.me/p5uIfZ-gxX). | |
| 363 | + Previously, this method would check if the post was published in January: | |
| 364 | + - Extract month from post_date_gmt -- $post_month = gmdate( 'm', strtotime( $post_date_gmt ) ); | |
| 365 | + - Return true if month was '01' -- return $post_month === '01'; | |
| 366 | + */ | |
| 367 | + return false; | |
| 368 | + } | |
| 369 | + | |
| 370 | + /** | |
| 371 | + * Return the bloganuary id of the form `bloganuary-yyyy-dd` | |
| 372 | + * | |
| 373 | + * @param string $post_date_gmt Post date in GMT. | |
| 374 | + * @return string Bloganuary id. | |
| 375 | + */ | |
| 376 | + protected function get_bloganuary_id( $post_date_gmt ) { | |
| 377 | + $post_year_day = gmdate( 'Y-d', strtotime( $post_date_gmt ) ); | |
| 378 | + if ( $this->force_year ) { | |
| 379 | + $post_year_day = $this->force_year . '-' . gmdate( 'd', strtotime( $post_date_gmt ) ); | |
| 380 | + } | |
| 381 | + return 'bloganuary-' . $post_year_day; | |
| 382 | + } | |
| 383 | + | |
| 384 | + /** | |
| 385 | + * Format a date for a blogging prompt, omiting the time. | |
| 386 | + * | |
| 387 | + * @param string $date_gmt Publish datetime of the prompt in GMT, i.e. 0000-00-00 00:00:00. | |
| 388 | + * @param string $date Publish datetime of the prompt, i.e. 0000-00-00 00:00:00. | |
| 389 | + * @return string Publish date of the prompt in YYYY-MM-DD format. | |
| 390 | + */ | |
| 391 | + public function prepare_date_response( $date_gmt, $date = null ) { | |
| 392 | + $post_date = $date ? $date : $date_gmt; | |
| 393 | + $date_obj = date_create( $post_date ); | |
| 394 | + | |
| 395 | + if ( $this->force_year ) { | |
| 396 | + $date_obj->setDate( $this->force_year, (int) $date_obj->format( 'n' ), (int) $date_obj->format( 'j' ) ); | |
| 397 | + | |
| 398 | + // If ascending by day of year, go to the next year when we pass the last day of the year. | |
| 399 | + if ( $date_obj->format( 'm-d' ) === '12-31' ) { | |
| 400 | + $this->force_year += 1; | |
| 401 | + } | |
| 402 | + } | |
| 403 | + | |
| 404 | + return false !== $date_obj ? $date_obj->format( 'Y-m-d' ) : substr( $post_date, 0, 10 ); | |
| 405 | + } | |
| 406 | + | |
| 407 | + /** | |
| 408 | + * Retrieves the query params for blogging prompt collections. | |
| 409 | + * | |
| 410 | + * @return array Query parameters for the collection. | |
| 411 | + */ | |
| 412 | + public function get_collection_params() { | |
| 413 | + $parent_args = parent::get_collection_params(); | |
| 414 | + | |
| 415 | + $args = array( | |
| 416 | + // Modify date args so that will except a YYYY-MM-DD without a time. | |
| 417 | + 'after' => array( | |
| 418 | + 'description' => __( 'Show prompts following a given date.', 'jetpack' ), | |
| 419 | + 'type' => 'string', | |
| 420 | + 'validate_callback' => function ( $param ) { | |
| 421 | + // Allow month and day without year, e.g. `--02-28` | |
| 422 | + if ( str_starts_with( $param, '-' ) ) { | |
| 423 | + return false !== date_create_from_format( '--m-d', $param ); | |
| 424 | + } | |
| 425 | + | |
| 426 | + return false !== date_create( $param ); | |
| 427 | + }, | |
| 428 | + ), | |
| 429 | + 'before' => array( | |
| 430 | + 'description' => __( 'Show prompts before a given date.', 'jetpack' ), | |
| 431 | + 'type' => 'string', | |
| 432 | + 'validate_callback' => function ( $param ) { | |
| 433 | + return false !== date_create( $param ); | |
| 434 | + }, | |
| 435 | + ), | |
| 436 | + 'force_year' => array( | |
| 437 | + 'description' => __( 'Force the returned prompts to be for a specific year. Returns only one prompt for each day.', 'jetpack' ), | |
| 438 | + 'type' => 'integer', | |
| 439 | + 'validate_callback' => function ( $param ) { | |
| 440 | + return is_numeric( $param ) && intval( $param ) > 0 && intval( $param ) < 9999; | |
| 441 | + }, | |
| 442 | + ), | |
| 443 | + ); | |
| 444 | + | |
| 445 | + $args['exclude'] = $parent_args['exclude']; | |
| 446 | + $args['include'] = $parent_args['include']; | |
| 447 | + $args['page'] = $parent_args['page']; | |
| 448 | + $args['per_page'] = $parent_args['per_page']; | |
| 449 | + $args['order'] = $parent_args['order']; | |
| 450 | + $args['order']['default'] = 'asc'; | |
| 451 | + $args['orderby'] = $parent_args['orderby']; | |
| 452 | + $args['search'] = $parent_args['search']; | |
| 453 | + | |
| 454 | + return $args; | |
| 455 | + } | |
| 456 | + | |
| 457 | + /** | |
| 458 | + * Retrieves the blogging prompt's schema, conforming to JSON Schema. | |
| 459 | + * | |
| 460 | + * @return array Item schema data. | |
| 461 | + */ | |
| 462 | + public function get_item_schema() { | |
| 463 | + return array( | |
| 464 | + '$schema' => 'http://json-schema.org/draft-04/schema#', | |
| 465 | + 'title' => 'blogging-prompt', | |
| 466 | + 'type' => 'object', | |
| 467 | + 'properties' => array( | |
| 468 | + 'id' => array( | |
| 469 | + 'description' => __( 'Unique identifier for the post.', 'jetpack' ), | |
| 470 | + 'type' => 'integer', | |
| 471 | + ), | |
| 472 | + 'date' => array( | |
| 473 | + 'description' => __( "The date the post was published, in the site's timezone.", 'jetpack' ), | |
| 474 | + 'type' => 'string', | |
| 475 | + ), | |
| 476 | + 'label' => array( | |
| 477 | + 'description' => __( 'Label for the prompt.', 'jetpack' ), | |
| 478 | + 'type' => 'string', | |
| 479 | + ), | |
| 480 | + 'text' => array( | |
| 481 | + 'description' => __( 'The text of the prompt. May include html tags like <em>.', 'jetpack' ), | |
| 482 | + 'type' => 'string', | |
| 483 | + ), | |
| 484 | + 'attribution' => array( | |
| 485 | + 'description' => __( 'Source of the prompt, if known.', 'jetpack' ), | |
| 486 | + 'type' => 'string', | |
| 487 | + ), | |
| 488 | + 'answered' => array( | |
| 489 | + 'description' => __( 'Whether the user has answered the prompt.', 'jetpack' ), | |
| 490 | + 'type' => 'boolean', | |
| 491 | + ), | |
| 492 | + 'answered_users_count' => array( | |
| 493 | + 'description' => __( 'Number of users who have answered the prompt.', 'jetpack' ), | |
| 494 | + 'type' => 'integer', | |
| 495 | + ), | |
| 496 | + 'answered_users_sample' => array( | |
| 497 | + 'description' => __( 'Sample of users who have answered the prompt.', 'jetpack' ), | |
| 498 | + 'type' => 'array', | |
| 499 | + 'items' => array( | |
| 500 | + 'type' => 'object', | |
| 501 | + 'properties' => array( | |
| 502 | + 'avatar' => array( | |
| 503 | + 'description' => __( "Gravatar URL for the user's avatar image.", 'jetpack' ), | |
| 504 | + 'type' => 'string', | |
| 505 | + 'format' => 'uri', | |
| 506 | + ), | |
| 507 | + ), | |
| 508 | + ), | |
| 509 | + ), | |
| 510 | + 'answered_link' => array( | |
| 511 | + 'description' => __( 'Link to answers for the prompt.', 'jetpack' ), | |
| 512 | + 'type' => 'string', | |
| 513 | + 'format' => 'uri', | |
| 514 | + ), | |
| 515 | + 'answered_link_text' => array( | |
| 516 | + 'description' => __( 'Text for the link to answers for the prompt.', 'jetpack' ), | |
| 517 | + 'type' => 'string', | |
| 518 | + ), | |
| 519 | + 'bloganuary_id' => array( | |
| 520 | + 'description' => __( 'Id used by the bloganuary promotion', 'jetpack' ), | |
| 521 | + 'type' => 'string', | |
| 522 | + ), | |
| 523 | + ), | |
| 524 | + ); | |
| 525 | + } | |
| 526 | + | |
| 527 | + /** | |
| 528 | + * Checks if a given request has access to read blogging prompts for a site. | |
| 529 | + * | |
| 530 | + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. | |
| 531 | + */ | |
| 532 | + public function permissions_check() { | |
| 533 | + if ( current_user_can( 'edit_posts' ) ) { | |
| 534 | + return true; | |
| 535 | + } | |
| 536 | + | |
| 537 | + // Allow "as blog" requests to wpcom so users without accounts can insert the Writing prompt block in the editor. | |
| 538 | + if ( $this->is_wpcom && is_jetpack_site( get_current_blog_id() ) ) { | |
| 539 | + if ( ! class_exists( 'WPCOM_REST_API_V2_Endpoint_Jetpack_Auth' ) ) { | |
| 540 | + require_once dirname( __DIR__ ) . '/rest-api-plugins/endpoints/jetpack-auth.php'; | |
| 541 | + } | |
| 542 | + | |
| 543 | + $jp_auth_endpoint = new WPCOM_REST_API_V2_Endpoint_Jetpack_Auth(); | |
| 544 | + if ( true === $jp_auth_endpoint->is_jetpack_authorized_for_site() ) { | |
| 545 | + return true; | |
| 546 | + } | |
| 547 | + } | |
| 548 | + | |
| 549 | + return new WP_Error( | |
| 550 | + 'rest_cannot_read_prompts', | |
| 551 | + __( 'Sorry, you are not allowed to access blogging prompts on this site.', 'jetpack' ), | |
| 552 | + array( 'status' => rest_authorization_required_code() ) | |
| 553 | + ); | |
| 554 | + } | |
| 555 | + | |
| 556 | + /** | |
| 557 | + * Creates a sample of users who have answered a blogging prompt. | |
| 558 | + * | |
| 559 | + * @param int $prompt_id Prompt ID. | |
| 560 | + * @return array List of users, including a gravatar url for each user. | |
| 561 | + */ | |
| 562 | + protected function build_answering_users_sample( $prompt_id ) { | |
| 563 | + $results = \A8C\BloggingPrompts\Answers::get_sample_users_by( $prompt_id ); | |
| 564 | + | |
| 565 | + if ( ! $results ) { | |
| 566 | + return array(); | |
| 567 | + } | |
| 568 | + | |
| 569 | + $users = array(); | |
| 570 | + | |
| 571 | + foreach ( $results as $user ) { | |
| 572 | + $url = wpcom_get_avatar_url( $user->user_id, 96, 'identicon', false ); | |
| 573 | + if ( has_gravatar( $user->user_id ) && ! empty( $url[0] ) && ! is_wp_error( $url[0] ) ) { | |
| 574 | + $users[] = array( | |
| 575 | + 'avatar' => (string) esc_url_raw( htmlspecialchars_decode( $url[0], ENT_COMPAT ) ), | |
| 576 | + ); | |
| 577 | + } | |
| 578 | + } | |
| 579 | + | |
| 580 | + return array_slice( $users, 0, 3 ); | |
| 581 | + } | |
| 582 | +} | |
| 583 | + | |
| 584 | +wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V3_Endpoint_Blogging_Prompts' ); | |