← All changes
|
jetpack_vendor/automattic/jetpack-import/src/endpoints/class-post.php
+303
-0
16.2-beta
→
16.3
View file →
| @@ -1,0 +1,303 @@ | ||
| 1 | +<?php | |
| 2 | +/** | |
| 3 | + * Posts REST route | |
| 4 | + * | |
| 5 | + * @package automattic/jetpack-import | |
| 6 | + */ | |
| 7 | + | |
| 8 | +namespace Automattic\Jetpack\Import\Endpoints; | |
| 9 | + | |
| 10 | +use Automattic\Jetpack\Sync\Settings; | |
| 11 | +use WP_Error; | |
| 12 | +use WP_REST_Request; | |
| 13 | +use WP_REST_Response; | |
| 14 | + | |
| 15 | +if ( ! defined( 'ABSPATH' ) ) { | |
| 16 | + exit( 0 ); | |
| 17 | +} | |
| 18 | + | |
| 19 | +if ( ! function_exists( 'post_exists' ) ) { | |
| 20 | + require_once ABSPATH . 'wp-admin/includes/post.php'; | |
| 21 | +} | |
| 22 | + | |
| 23 | +/** | |
| 24 | + * Class Post | |
| 25 | + */ | |
| 26 | +class Post extends \WP_REST_Posts_Controller { | |
| 27 | + | |
| 28 | + /** | |
| 29 | + * Base class | |
| 30 | + */ | |
| 31 | + use Import; | |
| 32 | + | |
| 33 | + /** | |
| 34 | + * The Import ID add a new item to the schema. | |
| 35 | + */ | |
| 36 | + use Import_ID; | |
| 37 | + | |
| 38 | + /** | |
| 39 | + * Whether the controller supports batching. | |
| 40 | + * | |
| 41 | + * @var array | |
| 42 | + */ | |
| 43 | + protected $allow_batch = array( 'v1' => true ); | |
| 44 | + | |
| 45 | + /** | |
| 46 | + * Constructor. | |
| 47 | + * | |
| 48 | + * @param string $post_type Post type. | |
| 49 | + */ | |
| 50 | + public function __construct( $post_type = 'post' ) { | |
| 51 | + parent::__construct( $post_type ); | |
| 52 | + | |
| 53 | + // @see add_post_meta | |
| 54 | + $this->import_id_meta_type = $post_type; | |
| 55 | + } | |
| 56 | + | |
| 57 | + /** | |
| 58 | + * Adds the schema from additional fields to a schema array. | |
| 59 | + * | |
| 60 | + * The type of object is inferred from the passed schema. | |
| 61 | + * | |
| 62 | + * @param array $schema Schema array. | |
| 63 | + * @return array Modified Schema array. | |
| 64 | + */ | |
| 65 | + public function add_additional_fields_schema( $schema ) { | |
| 66 | + // WXR saves terms as slugs, so we need to overwrite the schema. | |
| 67 | + $schema['properties']['categories']['items']['type'] = 'string'; | |
| 68 | + $schema['properties']['tags']['items']['type'] = 'string'; | |
| 69 | + | |
| 70 | + // Add the import unique ID to the schema. | |
| 71 | + return $this->add_unique_identifier_to_schema( $schema ); | |
| 72 | + } | |
| 73 | + | |
| 74 | + /** | |
| 75 | + * Creates a single post. | |
| 76 | + * | |
| 77 | + * @param WP_REST_Request $request Full details about the request. | |
| 78 | + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. | |
| 79 | + */ | |
| 80 | + public function create_item( $request ) { | |
| 81 | + // Set the WP_IMPORTING constant to prevent sync notifications | |
| 82 | + $this->set_importing(); | |
| 83 | + | |
| 84 | + // Skip if the post already exists. | |
| 85 | + $post_id = \post_exists( | |
| 86 | + $request['title'], | |
| 87 | + '', | |
| 88 | + $request['date'], | |
| 89 | + $this->post_type, | |
| 90 | + $request['status'] | |
| 91 | + ); | |
| 92 | + | |
| 93 | + if ( $post_id ) { | |
| 94 | + return new WP_Error( | |
| 95 | + 'post_exists', | |
| 96 | + __( 'Cannot create existing post.', 'jetpack-import' ), | |
| 97 | + array( | |
| 98 | + 'status' => 409, | |
| 99 | + 'post_id' => $post_id, | |
| 100 | + ) | |
| 101 | + ); | |
| 102 | + } | |
| 103 | + | |
| 104 | + // WXR saves terms as slugs, so we need to convert them to IDs before send the data to the legacy endpoint. | |
| 105 | + foreach ( array( 'categories', 'tags' ) as $taxonomy ) { | |
| 106 | + $request[ $taxonomy ] = $this->extract_terms_ids( $request, $taxonomy ); | |
| 107 | + } | |
| 108 | + | |
| 109 | + $response = parent::create_item( $request ); | |
| 110 | + | |
| 111 | + // Process post metadata. | |
| 112 | + if ( ! is_wp_error( $response ) && isset( $response->data ) && isset( $response->data['id'] ) ) { | |
| 113 | + $this->process_post_meta( $response->data['id'], $request ); | |
| 114 | + } | |
| 115 | + | |
| 116 | + return $this->add_import_id_metadata( $request, $response ); | |
| 117 | + } | |
| 118 | + | |
| 119 | + /** | |
| 120 | + * Extract terms IDs from slugs. | |
| 121 | + * | |
| 122 | + * @param WP_REST_Request $request Full details about the request. | |
| 123 | + * @param string $taxonomy Taxonomy name. | |
| 124 | + * @return array List of terms IDs. | |
| 125 | + */ | |
| 126 | + protected function extract_terms_ids( $request, $taxonomy ) { | |
| 127 | + $ret = is_array( $request[ $taxonomy ] ) ? $request[ $taxonomy ] : array(); | |
| 128 | + | |
| 129 | + if ( ! count( $ret ) ) { | |
| 130 | + return $ret; | |
| 131 | + } | |
| 132 | + | |
| 133 | + $taxonomy_name = $taxonomy === 'tags' ? 'post_tag' : 'category'; | |
| 134 | + | |
| 135 | + // Extract the terms by ID. | |
| 136 | + $ids = $this->get_term_ids_from_slugs( $ret, $taxonomy_name ); | |
| 137 | + | |
| 138 | + // Create missing terms and add their IDs to the $ids array. | |
| 139 | + $ids = $this->create_missing_terms( $ret, $ids, $taxonomy_name ); | |
| 140 | + | |
| 141 | + if ( is_array( $ids ) ) { | |
| 142 | + return $ids; | |
| 143 | + } else { | |
| 144 | + // Flush away any invalid terms. | |
| 145 | + return array(); | |
| 146 | + } | |
| 147 | + } | |
| 148 | + | |
| 149 | + /** | |
| 150 | + * Processes the metadata of a WordPress post when creating it. | |
| 151 | + * | |
| 152 | + * @param int $post_id The post ID. | |
| 153 | + * @param mixed $request An object containing the metadata being added to the post. | |
| 154 | + * @return void | |
| 155 | + */ | |
| 156 | + public function process_post_meta( $post_id, $request ) { | |
| 157 | + $metas = $request->get_param( 'meta' ); | |
| 158 | + | |
| 159 | + if ( empty( $metas ) ) { | |
| 160 | + return; | |
| 161 | + } | |
| 162 | + | |
| 163 | + $meta_keys_array = $this->filter_post_meta_keys( $metas ); | |
| 164 | + // Adding it to the whitelist | |
| 165 | + Settings::update_settings( array( 'post_meta_whitelist' => $meta_keys_array ) ); | |
| 166 | + | |
| 167 | + if ( is_array( $metas ) ) { | |
| 168 | + foreach ( $metas as $meta_key => $meta_value ) { | |
| 169 | + | |
| 170 | + $meta_value = self::unserialize_no_objects( $meta_value ); | |
| 171 | + if ( $meta_key === '_edit_last' ) { | |
| 172 | + update_post_meta( $post_id, $meta_key, $meta_value ); | |
| 173 | + } else { | |
| 174 | + // Add the meta data to the post | |
| 175 | + add_post_meta( $post_id, $meta_key, $meta_value ); | |
| 176 | + } | |
| 177 | + | |
| 178 | + do_action( 'import_post_meta', $post_id, $meta_key, $meta_value ); | |
| 179 | + } | |
| 180 | + } | |
| 181 | + } | |
| 182 | + | |
| 183 | + /** | |
| 184 | + * Restore an imported meta value as plain data. | |
| 185 | + * | |
| 186 | + * Imported meta is plain data (scalars and arrays), so serialized values are decoded without | |
| 187 | + * class instantiation and any residual objects are dropped. This matches maybe_unserialize() | |
| 188 | + * for non-serialized and object-free serialized values. | |
| 189 | + * | |
| 190 | + * @param mixed $value The raw meta value. | |
| 191 | + * @return mixed A plain (object-free) value. | |
| 192 | + */ | |
| 193 | + private static function unserialize_no_objects( $value ) { | |
| 194 | + if ( ! is_string( $value ) || ! is_serialized( $value ) ) { | |
| 195 | + return $value; | |
| 196 | + } | |
| 197 | + | |
| 198 | + // maybe_unserialize() decodes with no `allowed_classes`; call unserialize() directly with classes | |
| 199 | + // disallowed so meta is only ever restored as plain data. The is_serialized() guard above and the | |
| 200 | + // @ (warnings on malformed input) mirror maybe_unserialize()'s own behavior. | |
| 201 | + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize -- allowed_classes => false makes this decode safe. | |
| 202 | + $decoded = @unserialize( trim( $value ), array( 'allowed_classes' => false ) ); | |
| 203 | + | |
| 204 | + return self::strip_objects( $decoded ); | |
| 205 | + } | |
| 206 | + | |
| 207 | + /** | |
| 208 | + * Recursively replace any objects in a decoded value with null. | |
| 209 | + * | |
| 210 | + * @param mixed $value Decoded value. | |
| 211 | + * @return mixed The value with any objects removed. | |
| 212 | + */ | |
| 213 | + private static function strip_objects( $value ) { | |
| 214 | + if ( is_object( $value ) ) { | |
| 215 | + return null; | |
| 216 | + } | |
| 217 | + | |
| 218 | + if ( is_array( $value ) ) { | |
| 219 | + foreach ( $value as $key => $item ) { | |
| 220 | + $value[ $key ] = self::strip_objects( $item ); | |
| 221 | + } | |
| 222 | + } | |
| 223 | + | |
| 224 | + return $value; | |
| 225 | + } | |
| 226 | + | |
| 227 | + /** | |
| 228 | + * Filters an array of post meta keys. | |
| 229 | + * | |
| 230 | + * @param array $metas An array of metas to filter. | |
| 231 | + * @return array The filtered array of meta keys. | |
| 232 | + */ | |
| 233 | + private function filter_post_meta_keys( $metas ) { | |
| 234 | + // Convert array of keys to a plain array of key strings | |
| 235 | + $meta_keys = array_unique( array_keys( $metas ) ); | |
| 236 | + // // Filter the array by removing the excluded keys and any keys that include '_oembed' | |
| 237 | + $filtered_keys = array_filter( | |
| 238 | + $meta_keys, | |
| 239 | + function ( $key ) { | |
| 240 | + // We also don't want to include any oembed post meta because it gets created after a post created | |
| 241 | + return ! str_contains( $key, '_oembed' ); | |
| 242 | + } | |
| 243 | + ); | |
| 244 | + // Return the filtered array | |
| 245 | + return $filtered_keys; | |
| 246 | + } | |
| 247 | + | |
| 248 | + /** | |
| 249 | + * Get term IDs from slugs. | |
| 250 | + * | |
| 251 | + * @param array $term_slugs Array of term slugs. | |
| 252 | + * @param string $taxonomy_name Taxonomy name. | |
| 253 | + * | |
| 254 | + * @return array Array of term IDs. | |
| 255 | + */ | |
| 256 | + protected function get_term_ids_from_slugs( $term_slugs, $taxonomy_name ) { | |
| 257 | + return get_terms( | |
| 258 | + array( | |
| 259 | + 'fields' => 'ids', | |
| 260 | + 'hide_empty' => false, | |
| 261 | + 'slug' => $term_slugs, | |
| 262 | + 'taxonomy' => $taxonomy_name, | |
| 263 | + ) | |
| 264 | + ); | |
| 265 | + } | |
| 266 | + | |
| 267 | + /** | |
| 268 | + * Create any missing terms in the given taxonomy. | |
| 269 | + * | |
| 270 | + * @param array $term_slugs The slugs of the terms to check for. | |
| 271 | + * @param array $existing_ids The IDs of any terms that already exist. | |
| 272 | + * @param string $taxonomy_name The name of the taxonomy. | |
| 273 | + * | |
| 274 | + * @return array The IDs of any terms that are now in the taxonomy. | |
| 275 | + */ | |
| 276 | + protected function create_missing_terms( $term_slugs, $existing_ids, $taxonomy_name ) { | |
| 277 | + $ids = $existing_ids; | |
| 278 | + | |
| 279 | + foreach ( $term_slugs as $term_slug ) { | |
| 280 | + if ( ! term_exists( $term_slug, $taxonomy_name ) ) { | |
| 281 | + $term_name = $this->slug_to_readable_name( $term_slug ); | |
| 282 | + $new_term = wp_insert_term( $term_name, $taxonomy_name, array( 'slug' => $term_slug ) ); | |
| 283 | + if ( ! is_wp_error( $new_term ) && isset( $new_term['term_id'] ) ) { | |
| 284 | + $ids[] = $new_term['term_id']; | |
| 285 | + } | |
| 286 | + } | |
| 287 | + } | |
| 288 | + | |
| 289 | + return $ids; | |
| 290 | + } | |
| 291 | + | |
| 292 | + /** | |
| 293 | + * Convert a slug to a readable name. | |
| 294 | + * | |
| 295 | + * @param string $slug Slug to convert. | |
| 296 | + * @return string Converted name. | |
| 297 | + */ | |
| 298 | + protected function slug_to_readable_name( $slug ) { | |
| 299 | + $name = str_replace( array( '-', '_' ), ' ', $slug ); | |
| 300 | + $name = ucwords( $name ); | |
| 301 | + return $name; | |
| 302 | + } | |
| 303 | +} | |