PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 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 All 508 releases
← 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 +}