Completed
Push — add/importing-via-rest-api ( 890fbc )
by
unknown
06:58
created

site-importer.php ➔ jetpack_site_importer_init()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
/*
3
 * Load code specific to importing content
4
 */
5
6
class WP_REST_Jetpack_Imports_Controller extends WP_REST_Posts_Controller {
7
	function register_routes() {
8
		// Support existing `post` routes to allow the post object to be created
9
		parent::register_routes();
10
11
		// Routes that are specific to our custom post type:
12
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)/pieces/(?P<piece_id>[\d]+)', array(
13
			array(
14
//				'show_in_index' => false,
15
				'methods' => WP_REST_Server::CREATABLE,
16
				'callback' => array( $this, 'add_piece' ),
17
				'permission_callback' => array( $this, 'upload_and_import_permissions_check' ),
18
				'args' => array(
19
					'id' => array(
20
						'description' => __( 'Unique identifier for the object.' ),
21
						'type' => 'integer',
22
					),
23
					'piece_id' => array(
24
						'description' => __( 'Unique identifier for the piece.' ),
25
						'type' => 'integer',
26
					),
27
				),
28
				// body: { piece: 'The Base64-encoded piece of the file.' }
29
			),
30
		) );
31
32
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)/import-from-pieces', array(
33
			array(
34
//				'show_in_index' => false,
35
				'methods' => WP_REST_Server::CREATABLE,
36
				'callback' => array( $this, 'import_from_pieces' ),
37
				'permission_callback' => array( $this, 'upload_and_import_permissions_check' ),
38
				'args' => array(
39
					'id' => array(
40
						'description' => __( 'The id of the post storing the export data.' ),
41
						'type' => 'integer',
42
					),
43
					//'checksum'...?
44
				),
45
			),
46
		) );
47
	}
48
49
	/**
50
	 * Used by the parent class (`WP_REST_Posts_Controller`) to control access to post writes
51
	 */
52
	function create_item_permissions_check( $request ) {
53
		$parent_results = parent::create_item_permissions_check( $request );
54
		if ( is_wp_error( $parent_results ) ) {
55
			return $parent_results;
56
		}
57
58
		return $this->upload_and_import_permissions_check( $request );
59
	}
60
61
	function upload_and_import_permissions_check( $request ) {
62
		if ( ! current_user_can( 'upload_files' ) ) {
63
			return new WP_Error( 'Sorry, you are not allowed to upload files as this user' );
64
		}
65
66
		if ( ! current_user_can( 'import' ) ) {
67
			return new WP_Error( 'Sorry, you are not allowed to import as this user' );
68
		}
69
70
		return true;
71
	}
72
73
	static function meta_key_matches( $key ) {
74
		return preg_match( '/^jetpack_file_import_piece_(\d+)$/', $key );
75
	}
76
77
	static protected function get_pieces_for_cpt_id( $id ) {
78
		$all_meta = get_post_meta( $id );
79
80
		// We can't use array_filter w/ ARRAY_FILTER_USE_KEY until 5.6.0, so this is a workaround
81
		$pieces = array_intersect_key(
82
			$all_meta,
83
			array_flip( array_filter( array_keys( $all_meta ), array( 'WP_REST_Jetpack_Imports_Controller', 'meta_key_matches' ) ) )
84
		);
85
86
		ksort( $pieces );
87
		return $pieces;
88
	}
89
90
	function add_piece( $request ) {
91
		$piece = $request->get_param( 'piece' );
92
		$piece_id = $request->get_param( 'piece_id' );
93
		$post_id = $request->get_param( 'id' );
94
		if ( ! update_post_meta( $post_id, 'jetpack_file_import_piece_' . $piece_id, $piece ) ) {
95
			return new WP_Error( 'Error adding piece ' . $piece_id );
96
		}
97
98
		error_log( 'Added piece ' . $piece_id . ' to post_id ' . $post_id );
99
100
		return 'OK';
101
	}
102
103
	function import_from_pieces( $request ) {
104
		$post_id = (int) $request->get_param( 'id' );
105
		if ( $post_id < 1 ) {
106
			return new WP_Error( 'missing_id', 'A valid `id` param is required', 500 );
107
		}
108
109
		$post_obj = get_post( $post_id );
110
111
		if ( empty( $post_obj ) || $post_obj->ID !== $post_id ) {
112
			return new WP_Error( 'not_found', 'The specified post does not exist', 500 );
113
		}
114
115
		if ( 'jetpack_file_import' !== $post_obj->post_type ) {
116
			return new WP_Error( 'invalid_post_type', 'The specified post is not the correct type', 500 );
117
		}
118
119
		$tmpfile = tmpfile();
120
121
		if ( false === $tmpfile ) {
122
			return new WP_Error( 'tmpfile_error', 'Temporary file could not be created on the server', 500 );
123
		}
124
125
		$total_bytes = 0;
126
127
		$pieces = self::get_pieces_for_cpt_id( $post_id );
128
129
		foreach ( $pieces as $key => $value ) {
130
			$piece = base64_decode( $value[0] );
131
132
			$piece_bytes = fwrite( $tmpfile, $piece );
133
			if ( false === $piece_bytes ) {
134
				fclose( $tmpfile );
135
				@unlink( $tmpfile );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
136
				throw new Exception( 'Could not write piece' );
137
			}
138
			$total_bytes += $piece_bytes;
139
		}
140
141
		set_time_limit( 0 );
142
		// @TODO fastcgi_finish_request..?
143
144
		$result = $this->_import_from_file( $tmpfile );
145
146
		fclose( $tmpfile );
147
		@unlink( $tmpfile );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
148
149
		return $result;
150
	}
151
152
	protected function _import_from_file( $file ) {
153
		if ( ! defined( 'WP_LOAD_IMPORTERS' ) ) {
154
			define( 'WP_LOAD_IMPORTERS', 1 );
155
			/**
156
			 * The plugin short-circuits if this constant is not set when plugins load:
157
			 * https://github.com/WordPress/wordpress-importer/blob/19c7fe19619f06f51d502ea368011f667a419934/src/wordpress-importer.php#L13
158
			 *
159
			 * ...& core only sets that in a couple of ways:
160
			 * https://github.com/WordPress/WordPress/search?q=WP_LOAD_IMPORTERS&unscoped_q=WP_LOAD_IMPORTERS
161
			 *
162
			 * In order for this to work, we'll need a change to the core plugin, for example:
163
			 * https://github.com/WordPress/wordpress-importer/pull/45
164
			 */
165
		}
166
167
		if ( ! class_exists( 'WP_Import' ) ) {
168
			return new WP_Error( 'missing_wp_import', 'The WP_Import class does not exist' );
169
		}
170
171
		if ( ! function_exists( 'wordpress_importer_init' ) ) {
172
			return new WP_Error( 'missing_wp_import_init', 'The wordpress_importer_init function does not exist' );
173
		}
174
175
		// The REST API does not do `admin_init`, so we need to source a bunch of stuff
176
		require_once ABSPATH . 'wp-admin/includes/admin.php';
177
178
		wordpress_importer_init();
179
180
		if ( empty( $GLOBALS['wp_import'] ) ) {
181
			return new WP_Error( 'empty_wp_import', 'The wp_import global is empty' );
182
		}
183
184
		try {
185
			$file_info = stream_get_meta_data( $file );
186
			if ( empty( $file_info['uri'] ) || ! is_writable( $file_info['uri'] ) ) {
187
				return new WP_Error( 'invalid_file', 'Could not access import file' );
188
			}
189
			// @TODO promote to "proper" attachment, enqueue cron for import, etc...?
190
			error_log( 'Starting import from file:' );
191
			error_log( print_r( $file_info, 1 ) );
192
			$GLOBALS['wp_import']->import( $file_info['uri'] );
193
			return 'Imported';
194
		} catch ( Exception $e ) {
195
			return new WP_Error( 'import_from_file_exception', $e->getMessage() );
196
		}
197
	}
198
}
199
200
function jetpack_site_importer_init() {
201
	register_post_type( 'jetpack_file_import', array(
202
		'public'                => false,
203
		'rest_controller_class' => 'WP_REST_Jetpack_Imports_Controller',
204
		'rest_base'             => 'jetpack-file-imports',
205
		'show_in_rest'          => true,
206
	) );
207
}
208
209
add_action( 'init', 'jetpack_site_importer_init' );
210