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 ); |
|
|
|
|
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 ); |
|
|
|
|
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
|
|
|
|
If you suppress an error, we recommend checking for the error condition explicitly: