PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.1
16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 All 504 releases
jetpack / jetpack_vendor / automattic / jetpack-import / src / endpoints / class-post.php

class-post.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.1, at jetpack_vendor/automattic/jetpack-import/src/endpoints/class-post.php

304 lines 8.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 }
304