This commit is contained in:
2026-07-02 15:54:39 -06:00
commit 9883323161
17470 changed files with 4470592 additions and 0 deletions
@@ -0,0 +1,247 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.9.19] - 2026-06-08
### Changed
- Internal updates.
## [0.9.18] - 2026-06-01
### Changed
- Internal updates.
## [0.9.17] - 2026-05-19
### Changed
- Internal updates.
## [0.9.16] - 2026-05-04
### Changed
- Internal: No longer require automattic/jetpack-changelogger as a per-project dev dependency. [#48225]
## [0.9.15] - 2026-02-23
### Changed
- Update dependencies. [#39303]
## [0.9.14] - 2026-02-02
### Changed
- Internal updates.
## [0.9.13] - 2025-11-03
### Fixed
- Phan: Address PhanRedundantCondition, PhanRedundantArrayValuesCall, and PhanPluginRedundantAssignment violations. [#45681]
## [0.9.12] - 2025-10-27
### Changed
- Internal updates.
## [0.9.11] - 2025-08-18
### Changed
- Internal updates.
## [0.9.10] - 2025-08-11
### Fixed
- Prevent PHP errors when directly accessing various files. [#44646]
## [0.9.9] - 2025-08-04
### Changed
- Internal updates.
## [0.9.8] - 2025-04-28
### Changed
- Internal updates.
## [0.9.7] - 2025-03-24
### Changed
- Internal updates.
## [0.9.6] - 2025-03-12
### Changed
- Internal updates.
## [0.9.5] - 2025-03-10
### Changed
- Internal updates.
## [0.9.4] - 2025-02-24
### Fixed
- Code: Prevent dynamic class properties. [#41857]
## [0.9.3] - 2025-01-27
### Changed
- Internal updates.
## [0.9.2] - 2024-12-16
### Fixed
- Import: setting WP_IMPORTING when doing an import. [#40563]
## [0.9.1] - 2024-11-25
### Changed
- Updated dependencies. [#40286]
## [0.9.0] - 2024-11-18
### Removed
- General: Update minimum PHP version to 7.2. [#40147]
## [0.8.11] - 2024-11-04
### Added
- Enable test coverage. [#39961]
## [0.8.10] - 2024-10-29
### Changed
- Internal updates. [#39303]
## [0.8.9] - 2024-09-23
### Changed
- Update dependencies. [#39303]
## [0.8.8] - 2024-09-09
### Changed
- Update dependencies. [#39253]
## [0.8.7] - 2024-08-26
### Changed
- Updated package dependencies. [#39004]
## [0.8.6] - 2024-05-27
### Changed
- Update dependencies.
## [0.8.5] - 2024-05-06
### Added
- Add missing package dependencies. [#37141]
## [0.8.4] - 2024-04-29
### Changed
- Internal updates.
## [0.8.3] - 2024-04-08
### Changed
- Internal updates.
## [0.8.2] - 2024-03-18
### Changed
- Internal updates.
## [0.8.1] - 2023-11-24
### Changed
- Replaced usage of strpos() with str_contains(). [#34137]
## [0.8.0] - 2023-11-20
### Changed
- Updated required PHP version to >= 7.0. [#34192]
## [0.7.4] - 2023-09-19
- Minor internal updates.
## [0.7.3] - 2023-09-11
### Fixed
- Handles scaled images for attachments [#32838]
## [0.7.2] - 2023-08-28
### Changed
- Updated package dependencies. [#32605]
## [0.7.1] - 2023-05-29
### Changed
- Internal updates.
## [0.7.0] - 2023-04-17
### Added
- Added Unified Importer end endpoint [#30087]
- Add new start endpoint. [#30028]
## [0.6.1] - 2023-04-10
### Added
- Add Jetpack Autoloader package suggestion. [#29988]
## [0.6.0] - 2023-04-04
### Added
- Add support for wp_block, wp_navigation, wp_template, wp_template_part import. [#29744]
- Align all HTTP codes to a standard 409. [#29869]
## [0.5.0] - 2023-03-29
### Added
- Prevent media duplication when it's already existed [#29646]
### Fixed
- Remove process post meta function outside of hook [#29771]
## [0.4.0] - 2023-03-27
### Added
- Added global style import. [#29622]
- Added import of custom CSS [#29595]
- Adding functionality to create term on the fly [#29581]
### Changed
- Updated package version [#29457]
### Fixed
- Fixed post meta imports issue [#29497]
- Fix wp_global_styles issue [#29686]
## [0.3.0] - 2023-03-20
### Added
- Add support for nav-menu and nav-menu-item import. [#29481]
### Fixed
- Fix attachments path for import media endpoint [#29379]
## [0.2.0] - 2023-03-08
### Added
- Add the `/jetpack/v4/import/media/*` endpoints. [#29080]
## 0.1.0 - 2023-02-20
### Added
- Added import REST endpoints. [#28824]
- Add new Jetpack Import package. [#28735]
### Fixed
- Fixed various imported resources hierarchies [#29012]
[0.9.19]: https://github.com/Automattic/jetpack-import/compare/v0.9.18...v0.9.19
[0.9.18]: https://github.com/Automattic/jetpack-import/compare/v0.9.17...v0.9.18
[0.9.17]: https://github.com/Automattic/jetpack-import/compare/v0.9.16...v0.9.17
[0.9.16]: https://github.com/Automattic/jetpack-import/compare/v0.9.15...v0.9.16
[0.9.15]: https://github.com/Automattic/jetpack-import/compare/v0.9.14...v0.9.15
[0.9.14]: https://github.com/Automattic/jetpack-import/compare/v0.9.13...v0.9.14
[0.9.13]: https://github.com/Automattic/jetpack-import/compare/v0.9.12...v0.9.13
[0.9.12]: https://github.com/Automattic/jetpack-import/compare/v0.9.11...v0.9.12
[0.9.11]: https://github.com/Automattic/jetpack-import/compare/v0.9.10...v0.9.11
[0.9.10]: https://github.com/Automattic/jetpack-import/compare/v0.9.9...v0.9.10
[0.9.9]: https://github.com/Automattic/jetpack-import/compare/v0.9.8...v0.9.9
[0.9.8]: https://github.com/Automattic/jetpack-import/compare/v0.9.7...v0.9.8
[0.9.7]: https://github.com/Automattic/jetpack-import/compare/v0.9.6...v0.9.7
[0.9.6]: https://github.com/Automattic/jetpack-import/compare/v0.9.5...v0.9.6
[0.9.5]: https://github.com/Automattic/jetpack-import/compare/v0.9.4...v0.9.5
[0.9.4]: https://github.com/Automattic/jetpack-import/compare/v0.9.3...v0.9.4
[0.9.3]: https://github.com/Automattic/jetpack-import/compare/v0.9.2...v0.9.3
[0.9.2]: https://github.com/Automattic/jetpack-import/compare/v0.9.1...v0.9.2
[0.9.1]: https://github.com/Automattic/jetpack-import/compare/v0.9.0...v0.9.1
[0.9.0]: https://github.com/Automattic/jetpack-import/compare/v0.8.11...v0.9.0
[0.8.11]: https://github.com/Automattic/jetpack-import/compare/v0.8.10...v0.8.11
[0.8.10]: https://github.com/Automattic/jetpack-import/compare/v0.8.9...v0.8.10
[0.8.9]: https://github.com/Automattic/jetpack-import/compare/v0.8.8...v0.8.9
[0.8.8]: https://github.com/Automattic/jetpack-import/compare/v0.8.7...v0.8.8
[0.8.7]: https://github.com/Automattic/jetpack-import/compare/v0.8.6...v0.8.7
[0.8.6]: https://github.com/Automattic/jetpack-import/compare/v0.8.5...v0.8.6
[0.8.5]: https://github.com/Automattic/jetpack-import/compare/v0.8.4...v0.8.5
[0.8.4]: https://github.com/Automattic/jetpack-import/compare/v0.8.3...v0.8.4
[0.8.3]: https://github.com/Automattic/jetpack-import/compare/v0.8.2...v0.8.3
[0.8.2]: https://github.com/Automattic/jetpack-import/compare/v0.8.1...v0.8.2
[0.8.1]: https://github.com/Automattic/jetpack-import/compare/v0.8.0...v0.8.1
[0.8.0]: https://github.com/Automattic/jetpack-import/compare/v0.7.4...v0.8.0
[0.7.4]: https://github.com/Automattic/jetpack-import/compare/v0.7.3...v0.7.4
[0.7.3]: https://github.com/Automattic/jetpack-import/compare/v0.7.2...v0.7.3
[0.7.2]: https://github.com/Automattic/jetpack-import/compare/v0.7.1...v0.7.2
[0.7.1]: https://github.com/Automattic/jetpack-import/compare/v0.7.0...v0.7.1
[0.7.0]: https://github.com/Automattic/jetpack-import/compare/v0.6.1...v0.7.0
[0.6.1]: https://github.com/Automattic/jetpack-import/compare/v0.6.0...v0.6.1
[0.6.0]: https://github.com/Automattic/jetpack-import/compare/v0.5.0...v0.6.0
[0.5.0]: https://github.com/Automattic/jetpack-import/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/Automattic/jetpack-import/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/Automattic/jetpack-import/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/Automattic/jetpack-import/compare/v0.1.0...v0.2.0
@@ -0,0 +1,100 @@
<?php
/**
* Set of REST API routes used in WPCOM Unified Importer.
*
* @package automattic/jetpack-import
*/
namespace Automattic\Jetpack\Import;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Connection\Rest_Authentication;
/**
* This class will provide endpoint for the Unified Importer.
*/
class Main {
/**
* Package version.
*
* @var string
*/
const PACKAGE_VERSION = '0.9.19';
/**
* A list of all the routes.
*
* @var \WP_REST_Controller[]
*/
private static $routes = array();
/**
* Before everything else starts getting initalized, we need to initialize Jetpack using the
* Config object.
*
* @return void
*/
public static function configure() {
if ( did_action( 'jetpack_import_initialized' ) ) {
return;
}
$connection = new Connection_Manager();
// Initialize the REST API only if the user is connected.
if ( $connection->has_connected_owner() ) {
add_action( 'rest_api_init', array( __CLASS__, 'initialize_rest_api' ) );
}
/**
* Runs right after the Jetpack Import package is initialized.
*
* @since 0.1.0
*/
do_action( 'jetpack_import_initialized' );
}
/**
* Register import related REST routes.
*
* @return void
*/
public static function initialize_rest_api() {
// Set up the REST authentication hooks.
Rest_Authentication::init();
$routes = array(
'blocks' => new Endpoints\Block(),
'categories' => new Endpoints\Category(),
'comments' => new Endpoints\Comment(),
'custom-css' => new Endpoints\Custom_CSS(),
'end' => new Endpoints\End(),
'global-styles' => new Endpoints\Global_Style(),
'media' => new Endpoints\Attachment(),
'menu-items' => new Endpoints\Menu_Item(),
'menus' => new Endpoints\Menu(),
'navigation' => new Endpoints\Navigation(),
'pages' => new Endpoints\Page(),
'posts' => new Endpoints\Post(),
'start' => new Endpoints\Start(),
'tags' => new Endpoints\Tag(),
'template-parts' => new Endpoints\Template_Part(),
'templates' => new Endpoints\Template(),
);
/**
* Allow other plugins to modify import routes.
*
* @since 0.1.0
*
* @param array $routes Array of import routes.
*/
self::$routes = apply_filters( 'jetpack_import_types', $routes );
// Register all the routes.
foreach ( self::$routes as $route ) {
$route->register_routes();
}
}
}
@@ -0,0 +1,331 @@
<?php
/**
* Attachments REST route
*
* @package automattic/jetpack-import
*/
namespace Automattic\Jetpack\Import\Endpoints;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
/**
* Class Attachment
*/
class Attachment extends \WP_REST_Attachments_Controller {
/**
* Base class
*/
use Import;
/**
* The Import ID add a new item to the schema.
*/
use Import_ID;
/**
* Whether the controller supports batching. Default false.
*
* @var false
*/
protected $allow_batch = false;
/**
* Constructor.
*/
public function __construct() {
parent::__construct( 'attachment' );
// @see add_term_meta
$this->import_id_meta_type = 'post';
}
/**
* Registers the routes for the objects of the controller.
*
* @see WP_REST_Terms_Controller::register_rest_route()
*/
public function register_routes() {
register_rest_route(
self::$rest_namespace,
'/' . $this->rest_base,
$this->get_route_options()
);
register_rest_route(
self::$rest_namespace,
'/' . $this->rest_base . '/(?P<id>[\d]+)',
array(
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the attachment.', 'jetpack-import' ),
'type' => 'integer',
),
),
array(
'methods' => \WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ),
),
'allow_batch' => array( 'v1' => true ),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
self::$rest_namespace,
'/' . $this->rest_base . '/(?P<id>[\d]+)/post-process',
array(
'methods' => \WP_REST_Server::CREATABLE,
'callback' => array( $this, 'post_process_item' ),
'permission_callback' => array( $this, 'import_permissions_callback' ),
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the attachment.', 'jetpack-import' ),
'type' => 'integer',
),
'action' => array(
'type' => 'string',
'enum' => array( 'create-image-subsizes' ),
'required' => true,
),
),
)
);
}
/**
* Create a single attachment.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure.
*/
public function create_item( $request ) {
// Set the WP_IMPORTING constant to prevent sync notifications
$this->set_importing();
$file_info = $this->get_file_info( $request );
$attachment = $this->get_attachment_by_file_info( $file_info );
if ( $attachment ) {
$response = $this->prepare_attachment_for_response( $attachment, $request );
if ( \is_wp_error( $response ) ) {
return $response;
}
return new WP_Error(
'attachment_exists',
__( 'The attachment already exists.', 'jetpack-import' ),
array(
'status' => 409,
'attachment' => $response,
'attachment_id' => $attachment->ID,
)
);
}
$this->set_upload_dir( $request );
// Disable scaled image generation.
add_filter( 'big_image_size_threshold', '__return_false' );
return parent::create_item( $request );
}
/**
* Updates a single attachment.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function update_item( $request ) {
$response = parent::update_item( $request );
return $this->add_import_id_metadata( $request, $response );
}
/**
* Adds the schema from additional fields to a schema array.
*
* The type of object is inferred from the passed schema.
*
* @param array $schema Schema array.
* @return array Modified Schema array.
*/
public function add_additional_fields_schema( $schema ) {
// Validate the upload_date, used for placing the uploaded file in the correct upload directory.
$schema['properties']['upload_date'] = array(
'description' => __( 'The date for the upload directory of the attachment.', 'jetpack-import' ),
'type' => array( 'string', 'null' ),
'pattern' => '^\d{4}\/\d{2}$',
'required' => false,
);
// The unique identifier is only required for PUT requests.
return $this->add_unique_identifier_to_schema( $schema, isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] === 'PUT' );
}
/**
* Performs post-processing on an attachment.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure.
*/
public function post_process_item( $request ) {
require_once ABSPATH . 'wp-admin/includes/image.php';
\wp_update_image_subsizes( $request['id'] );
$request['context'] = 'edit';
return $this->prepare_item_for_response( \get_post( $request['id'] ), $request );
}
/**
* Add a filter that rewrites the upload path.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return void
* @throws \Exception If the date is invalid.
*/
protected function set_upload_dir( $request ) {
if ( ! $request->get_param( 'upload_date' ) ) {
return;
}
add_filter(
'upload_dir',
static function ( $data ) use ( $request ) {
$date = $request->get_param( 'upload_date' );
$fields_to_rewrite = array( 'path', 'url', 'subdir' );
foreach ( $fields_to_rewrite as $field ) {
$data[ $field ] = preg_replace( '/\d{4}\/\d{2}$/', $date, $data[ $field ] );
}
return $data;
}
);
}
/**
* Prepares a single attachment for create or update. This function overrides the parent function
*
* @param WP_REST_Request $request Request object.
* @return \stdClass|WP_Error Post object.
*/
protected function prepare_item_for_database( $request ) {
$prepared_attachment = parent::prepare_item_for_database( $request );
// date_gmt is equal to the date by default, so we need to override it.
if ( $request->get_param( 'date_gmt' ) ) {
$prepared_attachment->post_date_gmt = $request->get_param( 'date_gmt' );
}
return $prepared_attachment;
}
/**
* Retrieve the filename and MIME type from the request headers.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return array An associative array containing the filename and MIME type.
*/
protected function get_file_info( $request ) {
// Get the filename from the Content-Disposition header.
$filename_header = $request->get_header( 'content_disposition' );
$filename = self::get_filename_from_disposition( (array) $filename_header );
$post_date_gmt = $request->get_param( 'date_gmt' );
// Get the MIME type from the Content-Type header.
$mime_type = $request->get_header( 'content_type' );
return array(
'filename' => $filename,
'mime_type' => $mime_type,
'post_date_gmt' => $post_date_gmt,
);
}
/**
* Retrieve attachment metadata by file information.
*
* This function retrieves attachment metadata for a given file based on its filename, MIME type, and creation date.
*
* @param array $fileinfo An associative array containing information about the file. The array must contain the following keys:
* - 'filename': The name of the file.
* - 'mime_type': The MIME type of the file (e.g. 'image/jpeg').
* - 'date': The creation date of the file (e.g. '2022-01-01 12:00:00').
*
* @return mixed An associative array containing metadata for the attachment, or false if no attachment was found.
*/
protected function get_attachment_by_file_info( $fileinfo ) {
// Make sure all required variables are set and not empty
if ( empty( $fileinfo['filename'] ) || empty( $fileinfo['mime_type'] ) ) {
return false;
}
$original_filename = $fileinfo['filename'];
$mime_type = $fileinfo['mime_type'];
$post_date_gmt = $fileinfo['post_date_gmt'];
// From WordPress 5.3, we introduced the scaled image feature, so we'll also need to check for the scaled filename.
// https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
$extension_pos = strrpos( $original_filename, '.' );
$scaled_filename = substr( $original_filename, 0, $extension_pos ) . '-scaled' . substr( $original_filename, $extension_pos );
$filename_check_array = array( $original_filename, $scaled_filename );
$args = array(
'post_type' => 'attachment',
'post_mime_type' => $mime_type,
'date_query' => array(
array(
'after' => $post_date_gmt,
'before' => $post_date_gmt,
'inclusive' => true,
'column' => 'post_date_gmt',
),
),
'posts_per_page' => 1,
);
$args['meta_query'] = array( 'relation' => 'OR' );
foreach ( $filename_check_array as $filename ) {
$args['meta_query'][] = array(
'key' => '_wp_attached_file',
'value' => preg_quote( $filename, '/' ),
'compare' => 'REGEXP',
);
}
$attachments = \get_posts( $args );
if ( ! empty( $attachments ) ) {
// Return the first attachment data found
return $attachments[0];
}
return false;
}
/**
* Prepares an attachment object for REST API response and returns the resulting data as an array.
*
* @param object $attachment The attachment object to be prepared for response.
* @param object $request The REST API request object.
*
* @return array|WP_Error The prepared data as an array, or a WP_Error object if there was an error preparing the data.
*/
private function prepare_attachment_for_response( $attachment, $request ) {
// Prepare attachment data for response
$response = $this->prepare_item_for_response( $attachment, $request );
// Check if there was an error preparing the data
if ( \is_wp_error( $response ) ) {
return $response;
}
return (array) $response->get_data();
}
}
@@ -0,0 +1,42 @@
<?php
/**
* Blocks REST route
*
* @package automattic/jetpack-import
*/
namespace Automattic\Jetpack\Import\Endpoints;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
/**
* Class Block
*/
class Block extends \WP_REST_Blocks_Controller {
/**
* Base class
*/
use Import;
/**
* The Import ID add a new item to the schema.
*/
use Import_ID;
/**
* Whether the controller supports batching.
*
* @var array
*/
protected $allow_batch = array( 'v1' => true );
/**
* Constructor.
*/
public function __construct() {
parent::__construct( 'wp_block' );
}
}
@@ -0,0 +1,88 @@
<?php
/**
* Categories REST route
*
* @package automattic/jetpack-import
*/
namespace Automattic\Jetpack\Import\Endpoints;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
/**
* Class Category
*/
class Category extends \WP_REST_Terms_Controller {
/**
* Base class
*/
use Import;
/**
* The Import ID add a new item to the schema.
*/
use Import_ID;
/**
* Whether the controller supports batching. Default true.
*
* @var array
*/
protected $allow_batch = array( 'v1' => true );
/**
* Constructor.
*/
public function __construct() {
parent::__construct( 'category' );
// @see add_term_meta
$this->import_id_meta_type = 'term';
}
/**
* Adds the schema from additional fields to a schema array.
*
* The type of object is inferred from the passed schema.
*
* @param array $schema Schema array.
* @return array Modified Schema array.
*/
public function add_additional_fields_schema( $schema ) {
// Parent term is saved like a slug in WXR so we have to rewrite the schema.
$schema['properties']['parent']['description'] = __( 'The parent category slug.', 'jetpack-import' );
$schema['properties']['parent']['type'] = 'string';
// Add the import unique ID to the schema.
return $this->add_unique_identifier_to_schema( $schema );
}
/**
* Creates a single category.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function create_item( $request ) {
if ( ! empty( $request['parent'] ) ) {
$parent = get_term_by( 'slug', $request['parent'], 'category' );
// Overwrite the parent ID with the parent term ID found using the slug.
$request['parent'] = $parent ? $parent->term_id : 0;
}
$response = parent::create_item( $request );
// Ensure that the HTTP status is a valid one.
$response = $this->ensure_http_status( $response, 'term_exists', 409 );
return $this->add_import_id_metadata( $request, $response );
}
}
@@ -0,0 +1,114 @@
<?php
/**
* Comments REST route
*
* @package automattic/jetpack-import
*/
namespace Automattic\Jetpack\Import\Endpoints;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
/**
* Class Comment
*/
class Comment extends \WP_REST_Comments_Controller {
/**
* Base class
*/
use Import;
/**
* The Import ID add a new item to the schema.
*/
use Import_ID;
/**
* Whether the controller supports batching.
*
* @var array
*/
protected $allow_batch = array( 'v1' => true );
/**
* Constructor.
*/
public function __construct() {
parent::__construct();
// @see add_comment_meta
$this->import_id_meta_type = 'comment';
}
/**
* Creates a comment.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or error object on failure.
*/
public function create_item( $request ) {
// Set the WP_IMPORTING constant to prevent sync notifications
$this->set_importing();
// Resolve comment post ID.
if ( ! empty( $request['post'] ) ) {
$posts = \get_posts( $this->get_import_db_query( $request['post'] ) );
// Overwrite the comment parent post ID.
$request['post'] = is_array( $posts ) && count( $posts ) ? $posts[0] : 0;
}
// Resolve comment parent ID.
if ( ! empty( $request['parent'] ) ) {
$comments = \get_comments( $this->get_import_db_query( $request['parent'] ) );
// Overwrite the comment parent post ID.
$request['parent'] = is_array( $comments ) && count( $comments ) ? $comments[0] : 0;
}
$duplicated_id = null;
/**
* Core comment creation function doesn't return the duplicated comment ID.
* Add a filter to get the ID.
*
* phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
*/
$get_id_func = function ( $dupe_id, $commentdata ) use ( &$duplicated_id ) {
if ( $dupe_id !== null ) {
$duplicated_id = $dupe_id;
}
return $dupe_id;
};
// Add the filter.
\add_filter( 'duplicate_comment_id', $get_id_func, 10, 2 );
$response = parent::create_item( $request );
// Check if the comment is duplicated.
if (
$duplicated_id !== null &&
is_wp_error( $response ) &&
$response->get_error_code() === 'comment_duplicate' ) {
$data = $response->get_error_data( 'comment_duplicate' );
// Add the comment ID.
$data['comment_id'] = $duplicated_id;
$response->add_data( $data );
}
// Remove the filter.
\remove_filter( 'duplicate_comment_id', $get_id_func );
return $this->add_import_id_metadata( $request, $response );
}
}
@@ -0,0 +1,75 @@
<?php
/**
* Custom CSS REST route
*
* @package automattic/jetpack-import
*/
namespace Automattic\Jetpack\Import\Endpoints;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
require_once ABSPATH . 'wp-includes/theme.php';
/**
* Class Custom_CSS
*/
class Custom_CSS extends \WP_REST_Posts_Controller {
/**
* Base class
*/
use Import;
/**
* The Import ID add a new item to the schema.
*/
use Import_ID;
/**
* Whether the controller supports batching.
*
* @var array
*/
protected $allow_batch = array( 'v1' => true );
/**
* Constructor.
*/
public function __construct() {
parent::__construct( 'custom_css' );
$this->rest_base = 'custom-css';
}
/**
* Update the custom CSS post.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function create_item( $request ) {
// Set the WP_IMPORTING constant to prevent sync notifications
$this->set_importing();
$args = array(
'stylesheet' => $request['title'],
);
$post = wp_update_custom_css_post( $request['content'], $args );
if ( is_wp_error( $post ) ) {
return $post;
}
$response = $this->prepare_item_for_response( $post, $request );
$response = rest_ensure_response( $response );
return $this->add_import_id_metadata( $request, $response );
}
}
@@ -0,0 +1,121 @@
<?php // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
/**
* End REST route
*
* @package automattic/jetpack-import
*/
namespace Automattic\Jetpack\Import\Endpoints;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
/**
* Class End
*
* This class is used to start the import process.
*/
class End extends \WP_REST_Controller {
/**
* Base class
*/
use Import;
/**
* The Import ID add a new item to the schema.
*/
use Import_ID;
/**
* Constructor.
*/
public function __construct() {
$this->rest_base = 'end';
}
/**
* Get the register route options.
*
* @see register_rest_route()
*
* @return array The options.
*/
protected function get_route_options() {
return array(
array(
'methods' => \WP_REST_Server::CREATABLE,
'callback' => array( $this, 'cleanup_database' ),
'permission_callback' => array( $this, 'import_permissions_callback' ),
'args' => array(),
),
'schema' => array( $this, 'get_public_item_schema' ),
);
}
/**
* Retrieves the start values schema, conforming to JSON Schema.
*
* @return array Item schema data.
*/
public function get_item_schema() {
if ( $this->schema ) {
return $this->add_additional_fields_schema( $this->schema );
}
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'import-end',
'type' => 'object',
'properties' => array(
'commentmeta_count' => array(
'description' => __( 'Comment meta deleted count.', 'jetpack-import' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'postmeta_count' => array(
'description' => __( 'Post meta deleted count.', 'jetpack-import' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'termmeta_count' => array(
'description' => __( 'Term meta deleted count.', 'jetpack-import' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
),
);
$this->schema = $schema;
return $this->add_additional_fields_schema( $this->schema );
}
/**
* Delete all meta values from database.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or error object on failure.
*
* phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
*/
public function cleanup_database( $request ) {
global $wpdb;
$where = array( 'meta_key' => $this->import_id_field_name );
return array(
'commentmeta_count' => $wpdb->delete( $wpdb->commentmeta, $where ),
'postmeta_count' => $wpdb->delete( $wpdb->postmeta, $where ),
'termmeta_count' => $wpdb->delete( $wpdb->termmeta, $where ),
);
}
}
@@ -0,0 +1,132 @@
<?php
/**
* Global style REST route
*
* @package automattic/jetpack-import
*/
namespace Automattic\Jetpack\Import\Endpoints;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
require_once ABSPATH . 'wp-includes/class-wp-theme-json-resolver.php';
require_once ABSPATH . 'wp-includes/theme.php';
/**
* Class Global_Style
*/
class Global_Style extends \WP_REST_Posts_Controller {
/**
* Base class
*/
use Import;
/**
* The Import ID add a new item to the schema.
*/
use Import_ID;
/**
* Whether the controller supports batching.
*
* @var array
*/
protected $allow_batch = array( 'v1' => true );
/**
* Constructor.
*/
public function __construct() {
parent::__construct( 'wp_global_styles' );
$this->rest_base = 'global-styles';
}
/**
* Adds the schema from additional fields to a schema array.
*
* The type of object is inferred from the passed schema.
*
* @param array $schema Schema array.
* @return array Modified Schema array.
*/
public function add_additional_fields_schema( $schema ) {
$schema['properties']['theme'] = array(
'description' => __( 'The name of the theme.', 'jetpack-import' ),
'type' => 'string',
'required' => true,
);
// The unique identifier is only required for PUT requests.
return $this->add_unique_identifier_to_schema( $schema, isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] === 'PUT' );
}
/**
* Update the globals style post.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function create_item( $request ) {
// Set the WP_IMPORTING constant to prevent sync notifications
$this->set_importing();
if ( ! class_exists( 'WP_Theme_JSON_Resolver' ) ) {
require_once ABSPATH . 'wp-includes/class-wp-theme-json-resolver.php';
}
$theme = \wp_get_theme( $request['theme'] );
// Check if the theme exists.
if ( ! $theme->exists() ) {
return new WP_Error(
'theme_not_found',
__( 'Theme not found.', 'jetpack-import' ),
array(
'status' => 400,
'theme' => $request['theme'],
)
);
}
/**
* Get the global styles post, or create it.
*
* A global style post is a post with wp-global-styles-{stylesheet} as post slug.
*/
$post = \WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme, true );
if ( \is_wp_error( $post ) ) {
return $post;
}
$args = array(
'ID' => $post['ID'],
'post_content' => $request['content'],
);
// Update the post with the passed data.
$post_id = wp_update_post( $args, true, false );
if ( is_wp_error( $post_id ) ) {
$post_id->add_data( array( 'status' => 400 ) );
return $post_id;
}
// Get the post.
$post = get_post( $post_id );
$response = $this->prepare_item_for_response( $post, $request );
$response = rest_ensure_response( $response );
return $this->add_import_id_metadata( $request, $response );
}
}
@@ -0,0 +1,135 @@
<?php
/**
* Menu items REST route
*
* @package automattic/jetpack-import
*/
namespace Automattic\Jetpack\Import\Endpoints;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
/**
* Class Menu_Item
*/
class Menu_Item extends \WP_REST_Menu_Items_Controller {
/**
* Base class
*/
use Import;
/**
* The Import ID add a new item to the schema.
*/
use Import_ID;
/**
* Whether the controller supports batching. Default true.
*
* @var array
*/
protected $allow_batch = array( 'v1' => true );
/**
* Constructor.
*/
public function __construct() {
parent::__construct( 'nav_menu_item' );
// @see add_term_meta
$this->import_id_meta_type = 'post';
}
/**
* Adds the schema from additional fields to a schema array.
*
* The type of object is inferred from the passed schema.
*
* @param array $schema Schema array.
* @return array Modified Schema array.
*/
public function add_additional_fields_schema( $schema ) {
// WXR saves menu parent as slug, so we need to overwrite the schema.
$schema['properties']['menus']['description'] = __( 'The parent menu slug.', 'jetpack-import' );
$schema['properties']['menus']['type'] = 'string';
// Add the import unique ID to the schema.
return $this->add_unique_identifier_to_schema( $schema );
}
/**
* Creates a single menu item.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function create_item( $request ) {
// Set the WP_IMPORTING constant to prevent sync notifications
$this->set_importing();
if ( ! empty( $request['menus'] ) ) {
$menu_id = \term_exists( $request['menus'], 'nav_menu' );
if ( $menu_id ) {
// Overwrite the menu item parent menu ID.
$request['menus'] = is_array( $menu_id ) ? $menu_id['term_id'] : $menu_id;
}
}
if ( ! empty( $request['parent'] ) ) {
$query = $this->get_import_db_query( $request['parent'] );
$query['post_type'] = 'nav_menu_item';
$parent = \get_posts( $query );
// Overwrite the parent ID.
$request['parent'] = is_array( $parent ) && count( $parent ) ? $parent[0] : 0;
}
// A menu item can be a custom link or a post, page, category or attachment.
if ( ! empty( $request['object_id'] ) ) {
$id = null;
if ( $request['object'] === 'category' ) {
if ( $request['object_id'] === 1 ) {
// The default category is always ID 1, no need to search.
$id = 1;
} else {
$query = $this->get_import_db_query( $request['object_id'] );
$categories = \get_categories( $this->get_import_db_query( $request['object_id'] ) );
// Overwrite the category ID.
$id = is_array( $categories ) && count( $categories ) ? $categories[0] : null;
}
} elseif ( $request['object'] === 'page' ) {
$pages = \get_pages( $this->get_import_db_query( $request['object_id'] ) );
// Overwrite the page ID.
$id = is_array( $pages ) && count( $pages ) ? $pages[0]->ID : null;
} elseif ( $request['object'] === 'post' || $request['object'] === 'attachment' ) {
$posts = \get_posts( $this->get_import_db_query( $request['object_id'] ) );
// Overwrite the post ID.
$id = is_array( $posts ) && count( $posts ) ? $posts[0] : null;
}
if ( empty( $id ) ) {
// Not found the object or a custom menu item, remove the fields.
unset( $request['object_id'] );
unset( $request['object'] );
} else {
$request['object_id'] = $id;
}
}
$response = parent::create_item( $request );
return $this->add_import_id_metadata( $request, $response );
}
}
@@ -0,0 +1,67 @@
<?php
/**
* Menus REST route
*
* @package automattic/jetpack-import
*/
namespace Automattic\Jetpack\Import\Endpoints;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
/**
* Class Menu
*/
class Menu extends \WP_REST_Menus_Controller {
/**
* Base class
*/
use Import;
/**
* The Import ID add a new item to the schema.
*/
use Import_ID;
/**
* Whether the controller supports batching. Default true.
*
* @var array
*/
protected $allow_batch = array( 'v1' => true );
/**
* Constructor.
*/
public function __construct() {
parent::__construct( 'nav_menu' );
// @see add_term_meta
$this->import_id_meta_type = 'term';
}
/**
* Creates a single menu.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function create_item( $request ) {
// Set the WP_IMPORTING constant to prevent sync notifications
$this->set_importing();
$response = parent::create_item( $request );
// Ensure that the HTTP status is a valid one.
$response = $this->ensure_http_status( $response, 'menu_exists', 409 );
return $this->add_import_id_metadata( $request, $response );
}
}
@@ -0,0 +1,42 @@
<?php
/**
* Navigation REST route
*
* @package automattic/jetpack-import
*/
namespace Automattic\Jetpack\Import\Endpoints;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
/**
* Navigation Block
*/
class Navigation extends \WP_REST_Posts_Controller {
/**
* Base class
*/
use Import;
/**
* The Import ID add a new item to the schema.
*/
use Import_ID;
/**
* Whether the controller supports batching.
*
* @var array
*/
protected $allow_batch = array( 'v1' => true );
/**
* Constructor.
*/
public function __construct() {
parent::__construct( 'wp_navigation' );
}
}
@@ -0,0 +1,51 @@
<?php
/**
* Pages REST route
*
* @package automattic/jetpack-import
*/
namespace Automattic\Jetpack\Import\Endpoints;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
/**
* Class Page
*/
class Page extends Post {
/**
* Constructor.
*/
public function __construct() {
parent::__construct( 'page' );
}
/**
* Creates a single page.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function create_item( $request ) {
// Set the WP_IMPORTING constant to prevent sync notifications
$this->set_importing();
if ( ! empty( $request['parent'] ) ) {
$pages = \get_pages( $this->get_import_db_query( $request['parent'] ) );
// Overwrite the page parent page ID.
$request['parent'] = is_array( $pages ) && count( $pages ) ? $pages[0]->ID : 0;
}
$response = parent::create_item( $request );
return $this->add_import_id_metadata( $request, $response );
}
}
@@ -0,0 +1,259 @@
<?php
/**
* Posts REST route
*
* @package automattic/jetpack-import
*/
namespace Automattic\Jetpack\Import\Endpoints;
use Automattic\Jetpack\Sync\Settings;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
if ( ! function_exists( 'post_exists' ) ) {
require_once ABSPATH . 'wp-admin/includes/post.php';
}
/**
* Class Post
*/
class Post extends \WP_REST_Posts_Controller {
/**
* Base class
*/
use Import;
/**
* The Import ID add a new item to the schema.
*/
use Import_ID;
/**
* Whether the controller supports batching.
*
* @var array
*/
protected $allow_batch = array( 'v1' => true );
/**
* Constructor.
*
* @param string $post_type Post type.
*/
public function __construct( $post_type = 'post' ) {
parent::__construct( $post_type );
// @see add_post_meta
$this->import_id_meta_type = $post_type;
}
/**
* Adds the schema from additional fields to a schema array.
*
* The type of object is inferred from the passed schema.
*
* @param array $schema Schema array.
* @return array Modified Schema array.
*/
public function add_additional_fields_schema( $schema ) {
// WXR saves terms as slugs, so we need to overwrite the schema.
$schema['properties']['categories']['items']['type'] = 'string';
$schema['properties']['tags']['items']['type'] = 'string';
// Add the import unique ID to the schema.
return $this->add_unique_identifier_to_schema( $schema );
}
/**
* Creates a single post.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function create_item( $request ) {
// Set the WP_IMPORTING constant to prevent sync notifications
$this->set_importing();
// Skip if the post already exists.
$post_id = \post_exists(
$request['title'],
'',
$request['date'],
$this->post_type,
$request['status']
);
if ( $post_id ) {
return new WP_Error(
'post_exists',
__( 'Cannot create existing post.', 'jetpack-import' ),
array(
'status' => 409,
'post_id' => $post_id,
)
);
}
// WXR saves terms as slugs, so we need to convert them to IDs before send the data to the legacy endpoint.
foreach ( array( 'categories', 'tags' ) as $taxonomy ) {
$request[ $taxonomy ] = $this->extract_terms_ids( $request, $taxonomy );
}
$response = parent::create_item( $request );
// Process post metadata.
if ( ! is_wp_error( $response ) && isset( $response->data ) && isset( $response->data['id'] ) ) {
$this->process_post_meta( $response->data['id'], $request );
}
return $this->add_import_id_metadata( $request, $response );
}
/**
* Extract terms IDs from slugs.
*
* @param WP_REST_Request $request Full details about the request.
* @param string $taxonomy Taxonomy name.
* @return array List of terms IDs.
*/
protected function extract_terms_ids( $request, $taxonomy ) {
$ret = is_array( $request[ $taxonomy ] ) ? $request[ $taxonomy ] : array();
if ( ! count( $ret ) ) {
return $ret;
}
$taxonomy_name = $taxonomy === 'tags' ? 'post_tag' : 'category';
// Extract the terms by ID.
$ids = $this->get_term_ids_from_slugs( $ret, $taxonomy_name );
// Create missing terms and add their IDs to the $ids array.
$ids = $this->create_missing_terms( $ret, $ids, $taxonomy_name );
if ( is_array( $ids ) ) {
return $ids;
} else {
// Flush away any invalid terms.
return array();
}
}
/**
* Processes the metadata of a WordPress post when creating it.
*
* @param int $post_id The post ID.
* @param mixed $request An object containing the metadata being added to the post.
* @return void
*/
public function process_post_meta( $post_id, $request ) {
$metas = $request->get_param( 'meta' );
if ( empty( $metas ) ) {
return;
}
$meta_keys_array = $this->filter_post_meta_keys( $metas );
// Adding it to the whitelist
Settings::update_settings( array( 'post_meta_whitelist' => $meta_keys_array ) );
if ( is_array( $metas ) ) {
foreach ( $metas as $meta_key => $meta_value ) {
$meta_value = maybe_unserialize( $meta_value );
if ( $meta_key === '_edit_last' ) {
update_post_meta( $post_id, $meta_key, $meta_value );
} else {
// Add the meta data to the post
add_post_meta( $post_id, $meta_key, $meta_value );
}
do_action( 'import_post_meta', $post_id, $meta_key, $meta_value );
}
}
}
/**
* Filters an array of post meta keys.
*
* @param array $metas An array of metas to filter.
* @return array The filtered array of meta keys.
*/
private function filter_post_meta_keys( $metas ) {
// Convert array of keys to a plain array of key strings
$meta_keys = array_unique( array_keys( $metas ) );
// // Filter the array by removing the excluded keys and any keys that include '_oembed'
$filtered_keys = array_filter(
$meta_keys,
function ( $key ) {
// We also don't want to include any oembed post meta because it gets created after a post created
return ! str_contains( $key, '_oembed' );
}
);
// Return the filtered array
return $filtered_keys;
}
/**
* Get term IDs from slugs.
*
* @param array $term_slugs Array of term slugs.
* @param string $taxonomy_name Taxonomy name.
*
* @return array Array of term IDs.
*/
protected function get_term_ids_from_slugs( $term_slugs, $taxonomy_name ) {
return get_terms(
array(
'fields' => 'ids',
'hide_empty' => false,
'slug' => $term_slugs,
'taxonomy' => $taxonomy_name,
)
);
}
/**
* Create any missing terms in the given taxonomy.
*
* @param array $term_slugs The slugs of the terms to check for.
* @param array $existing_ids The IDs of any terms that already exist.
* @param string $taxonomy_name The name of the taxonomy.
*
* @return array The IDs of any terms that are now in the taxonomy.
*/
protected function create_missing_terms( $term_slugs, $existing_ids, $taxonomy_name ) {
$ids = $existing_ids;
foreach ( $term_slugs as $term_slug ) {
if ( ! term_exists( $term_slug, $taxonomy_name ) ) {
$term_name = $this->slug_to_readable_name( $term_slug );
$new_term = wp_insert_term( $term_name, $taxonomy_name, array( 'slug' => $term_slug ) );
if ( ! is_wp_error( $new_term ) && isset( $new_term['term_id'] ) ) {
$ids[] = $new_term['term_id'];
}
}
}
return $ids;
}
/**
* Convert a slug to a readable name.
*
* @param string $slug Slug to convert.
* @return string Converted name.
*/
protected function slug_to_readable_name( $slug ) {
$name = str_replace( array( '-', '_' ), ' ', $slug );
$name = ucwords( $name );
return $name;
}
}
@@ -0,0 +1,158 @@
<?php
/**
* Start REST route
*
* @package automattic/jetpack-import
*/
namespace Automattic\Jetpack\Import\Endpoints;
use Automattic\Jetpack\Import\Main;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
/**
* Class Start
*
* This class is used to start the import process.
*/
class Start extends \WP_REST_Controller {
/**
* Base class
*/
use Import;
/**
* Constructor.
*/
public function __construct() {
$this->rest_base = 'start';
}
/**
* Get the register route options.
*
* @see register_rest_route()
*
* @return array The options.
*/
protected function get_route_options() {
return array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'import_permissions_callback' ),
'args' => array(),
),
'schema' => array( $this, 'get_public_item_schema' ),
);
}
/**
* Retrieves main informations.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_item( $request ) {
$items = array(
'max_batch_items' => apply_filters( 'rest_get_max_batch_size', 25 ),
'max_execution_time' => (int) ini_get( 'max_execution_time' ),
'max_input_time' => (int) ini_get( 'max_input_time' ),
'mime_types' => get_allowed_mime_types(),
'posts_max_id' => (int) $this->get_posts_max_id(),
'version' => Main::PACKAGE_VERSION,
);
$response = array();
foreach ( $items as $name => $value ) {
/**
* Filters the value of a item recognized by the REST API.
*/
$response[ $name ] = apply_filters( 'jetpack_import_rest_get_start', $value, $name, $request );
}
return $response;
}
/**
* Retrieves the start values schema, conforming to JSON Schema.
*
* @return array Item schema data.
*/
public function get_item_schema() {
if ( $this->schema ) {
return $this->add_additional_fields_schema( $this->schema );
}
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'import-start',
'type' => 'object',
'properties' => array(
'max_batch_items' => array(
'description' => __( 'Max batch size.', 'jetpack-import' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'max_execution_time' => array(
'description' => __( 'Max execution time.', 'jetpack-import' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'max_input_time' => array(
'description' => __( 'Max execution input time.', 'jetpack-import' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'mime_types' => array(
'description' => __( 'Upload accepted mime types.', 'jetpack-import' ),
'type' => 'array',
'items' => array( 'type' => 'string' ),
'context' => array( 'view' ),
'readonly' => true,
),
'posts_max_id' => array(
'description' => __( 'Last posts autogenerated ID.', 'jetpack-import' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'version' => array(
'description' => __( 'Version of the import package.', 'jetpack-import' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
),
);
$this->schema = $schema;
return $this->add_additional_fields_schema( $this->schema );
}
/**
* Get the last posts autogenerated ID.
*/
private function get_posts_max_id() {
global $wpdb;
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
$max_id = $wpdb->get_var( "SELECT MAX(ID) FROM $wpdb->posts" );
return $max_id;
}
}
@@ -0,0 +1,64 @@
<?php
/**
* Tags REST route
*
* @package automattic/jetpack-import
*/
namespace Automattic\Jetpack\Import\Endpoints;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
/**
* Class Tag
*/
class Tag extends \WP_REST_Terms_Controller {
/**
* Base class
*/
use Import;
/**
* The Import ID add a new item to the schema.
*/
use Import_ID;
/**
* Whether the controller supports batching. Default true.
*
* @var array
*/
protected $allow_batch = array( 'v1' => true );
/**
* Constructor.
*/
public function __construct() {
parent::__construct( 'post_tag' );
// @see add_term_meta
$this->import_id_meta_type = 'term';
}
/**
* Creates a tag.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function create_item( $request ) {
$response = parent::create_item( $request );
// Ensure that the HTTP status is a valid one.
$response = $this->ensure_http_status( $response, 'term_exists', 409 );
return $this->add_import_id_metadata( $request, $response );
}
}
@@ -0,0 +1,42 @@
<?php
/**
* Template parts REST route
*
* @package automattic/jetpack-import
*/
namespace Automattic\Jetpack\Import\Endpoints;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
/**
* Class Template_Part
*/
class Template_Part extends \WP_REST_Templates_Controller {
/**
* Base class
*/
use Import;
/**
* The Import ID add a new item to the schema.
*/
use Import_ID;
/**
* Whether the controller supports batching.
*
* @var array
*/
protected $allow_batch = array( 'v1' => true );
/**
* Constructor.
*/
public function __construct() {
parent::__construct( 'wp_template_part' );
}
}
@@ -0,0 +1,61 @@
<?php
/**
* Templates REST route
*
* @package automattic/jetpack-import
*/
namespace Automattic\Jetpack\Import\Endpoints;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
/**
* Class Template
*/
class Template extends \WP_REST_Templates_Controller {
/**
* Base class
*/
use Import;
/**
* The Import ID add a new item to the schema.
*/
use Import_ID;
/**
* Whether the controller supports batching.
*
* @var array
*/
protected $allow_batch = array( 'v1' => true );
/**
* Constructor.
*/
public function __construct() {
parent::__construct( 'wp_template' );
}
/**
* Update the template post.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function create_item( $request ) {
// Set the WP_IMPORTING constant to prevent sync notifications
$this->set_importing();
$response = parent::create_item( $request );
return $this->add_import_id_metadata( $request, $response );
}
}
@@ -0,0 +1,128 @@
<?php
/**
* Jetpack Import unique import ID.
*
* @package automattic/jetpack-import
*/
namespace Automattic\Jetpack\Import\Endpoints;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
/**
* Import ID trait. Add a unique import ID to the items schema.
*/
trait Import_ID {
/**
* Meta and REST property name used for storing the WXR import ID.
*
* @var string
*/
protected $import_id_field_name = 'unified_importer_id';
/**
* Import ID meta name.
*
* @var string
*/
protected $import_id_meta_type;
/**
* Adds the schema from additional fields to a schema array.
*
* The type of object is inferred from the passed schema.
*
* @param array $schema Schema array.
* @return array Modified Schema array.
*/
public function add_additional_fields_schema( $schema ) {
// Add the import unique ID to the schema.
return $this->add_unique_identifier_to_schema( $schema );
}
/**
* Create a resource.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function create_item( $request ) {
$response = parent::create_item( $request );
return $this->add_import_id_metadata( $request, $response );
}
/**
* Adds the unique identifier to the schema array.
*
* @param array $schema Schema array.
* @param bool $required Whether the field is required.
* @return array Modified Schema array.
*/
protected function add_unique_identifier_to_schema( $schema, $required = true ) {
// Add the import unique ID to the schema.
$schema['properties'][ $this->import_id_field_name ] = array(
'description' => __( 'Jetpack Import unique identifier for the term.', 'jetpack-import' ),
'type' => 'integer',
'context' => array( 'view', 'embed', 'edit' ),
'required' => $required,
);
return $schema;
}
/**
* Add the import unique ID to the resource metadata.
*
* @param WP_REST_Request $request Full details about the request.
* @param WP_REST_Response $response Response object.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
protected function add_import_id_metadata( $request, $response ) {
// Skip on error.
if ( is_wp_error( $response ) ) {
return $response;
}
$data = $response->get_data();
$status = $response->get_status();
// Skip if the resource has not been added or modified.
if ( ! ( $status === 200 || $status === 201 ) ) {
return $response;
}
// Add the import unique ID to the resource metadata.
\add_metadata( $this->import_id_meta_type, $data['id'], $this->import_id_field_name, $request[ $this->import_id_field_name ], true );
return $response;
}
/**
* Get the import DB query. This is used to get the items with a specific
* meta key that have been imported.
*
* @param int $parent_import_id The parent import ID.
* @return array The query.
*/
protected function get_import_db_query( $parent_import_id ) {
// Get the only one item with the parent import ID.
return array(
'number' => 1,
'fields' => 'ids',
'meta_query' => array(
array(
'key' => $this->import_id_field_name,
'value' => $parent_import_id,
),
),
);
}
}
@@ -0,0 +1,118 @@
<?php
/**
* Jetpack Import unique import ID.
*
* @package automattic/jetpack-import
*/
namespace Automattic\Jetpack\Import\Endpoints;
use WP_Error;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
/**
* Import trait. Base class for all import endpoints.
*
* @property array|false $allow_batch Batching details.
*/
trait Import {
/**
* REST API namespace.
*
* @var string
*/
private static $rest_namespace = 'jetpack/v4/import';
/**
* REST base path.
*
* @var ?string
*/
protected $rest_base;
/**
* Registers the routes for the objects of the controller.
*
* @see WP_REST_Controller::register_rest_route()
*/
public function register_routes() {
register_rest_route(
self::$rest_namespace,
'/' . $this->rest_base,
$this->get_route_options()
);
}
/**
* Ensure that the user has permissions to import.
*
* @return bool|WP_Error
*/
public function import_permissions_callback() {
// The permission check is done in the REST API authentication. It's the same
// as the one used in wp-admin/import.php.
if ( \current_user_can( 'import' ) ) {
return true;
}
$error_msg = \esc_html__(
'You are not allowed to perform this action.',
'jetpack-import'
);
return new WP_Error( 'rest_forbidden', $error_msg, array( 'status' => \rest_authorization_required_code() ) );
}
/**
* Get the register route options.
*
* @see register_rest_route()
*
* @return array The options.
*/
protected function get_route_options() {
return array(
array(
'methods' => \WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'import_permissions_callback' ),
'args' => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ),
),
'allow_batch' => $this->allow_batch,
'schema' => array( $this, 'get_public_item_schema' ),
);
}
/**
* Ensure that the HTTP status is correct.
*
* @param WP_Error $response Response error object.
* @param int $error_code Error code.
* @param int $status HTTP status.
*/
protected function ensure_http_status( $response, $error_code, $status ) {
if ( is_wp_error( $response ) && in_array( $error_code, $response->get_error_codes(), true ) ) {
$data = $response->get_error_data( $error_code );
if ( isset( $data['status'] ) ) {
$data['status'] = $status;
$response->add_data( $data );
}
}
return $response;
}
/**
* Set the importing constant.
*/
public function set_importing() {
if ( ! defined( 'WP_IMPORTING' ) ) {
define( 'WP_IMPORTING', true );
}
}
}