-
Notifications
You must be signed in to change notification settings - Fork 35
Expand file tree
/
Copy pathclass-download-sync.php
More file actions
250 lines (221 loc) · 7.27 KB
/
class-download-sync.php
File metadata and controls
250 lines (221 loc) · 7.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
<?php
/**
* Download Sync from Cloudinary.
*
* @package Cloudinary
*/
namespace Cloudinary\Sync;
use Cloudinary\Sync;
/**
* Class Download_Sync.
*
* Pull media from Cloudinary on insert.
*/
class Download_Sync {
/**
* Holds the plugin instance.
*
* @since 0.1
*
* @var \Cloudinary\Plugin Instance of the global plugin.
*/
private $plugin;
/**
* Download_Sync constructor.
*
* @param \Cloudinary\Plugin $plugin The plugin.
*/
public function __construct( \Cloudinary\Plugin $plugin ) {
$this->plugin = $plugin;
$this->register_hooks();
}
/**
* Register any hooks that this component needs.
*/
private function register_hooks() {
add_filter( 'cloudinary_api_rest_endpoints', array( $this, 'rest_endpoints' ) );
}
/**
* Add endpoints to the \Cloudinary\REST_API::$endpoints array.
*
* @param array $endpoints Endpoints from the filter.
*
* @return array
*/
public function rest_endpoints( $endpoints ) {
$endpoints['asset'] = array(
'method' => \WP_REST_Server::CREATABLE,
'callback' => array( $this, 'rest_download_asset' ),
'args' => array(),
'permission_callback' => array( $this, 'rest_can_upload_files' ),
);
return $endpoints;
}
/**
* Admin permission callback.
*
* Explicitly defined to allow easier testability.
*
* @param \WP_REST_Request $request The request.
*
* @return bool
*/
public function rest_can_upload_files( \WP_REST_Request $request ) {
// This would have been from an ajax call. Therefore verify based on capability.
return current_user_can( 'upload_files' );
}
/**
* Handle a failed download by deleting the temp attachment and returning the error in json.
*
* @param int $attachment_id The attachment ID.
* @param string $error The error text to return.
*
* @return \WP_Error
*/
public function handle_failed_download( $attachment_id, $error ) {
// @todo: Place a handler to catch the error for logging.
// Delete attachment temp.
wp_delete_attachment( $attachment_id, true );
// Send error.
wp_send_json_error( $error );
}
/**
* Download attachment from Cloudinary via REST API.
*
* @param \WP_REST_Request $request The request.
*
* @return \WP_REST_Response
*/
public function rest_download_asset( \WP_REST_Request $request ) {
$attachment_id = $request->get_param( 'attachment_id' );
$file_path = $request->get_param( 'src' );
$file_name = $request->get_param( 'filename' );
$transformations = (array) $request->get_param( 'transformations' );
$response = $this->download_asset( $attachment_id, $file_path, $file_name, $transformations );
if ( is_wp_error( $response ) ) {
$this->handle_failed_download( $attachment_id, $response->get_error_message() );
}
return rest_ensure_response( $response );
}
/**
* Prepare and sync down an asset stored remotely.
*
* @param string $attachment_id
*
* @return array|\WP_Error
*/
public function down_sync( $attachment_id ) {
$file = get_post_meta( $attachment_id, '_wp_attached_file', true );
$path = wp_parse_url( $file, PHP_URL_PATH );
$media = $this->plugin->components['media'];
$parts = explode( '/', $path );
$parts = array_map(
function ( $val ) use ( $media ) {
if ( empty( $val ) ) {
return false;
}
if ( $val === $media->credentials['cloud_name'] ) {
return false;
}
if ( in_array( $val, [ 'image', 'video', 'upload' ], true ) ) {
return false;
}
$transformation_maybe = $media->get_transformations_from_string( $val );
if ( ! empty( $transformation_maybe ) ) {
return false;
}
if ( substr( $val, 0, 1 ) === 'v' && is_numeric( substr( $val, 1 ) ) ) {
return false;
}
return $val;
},
$parts
);
// Build public_id.
$parts = array_filter( $parts );
$public_id = implode( '/', $parts );
// Remove extension.
$path = pathinfo( $public_id );
$public_id = strstr( $public_id, '.' . $path['extension'], true );
// Save public ID.
$media->update_post_meta( $attachment_id, Sync::META_KEYS['public_id'], $public_id );
return $this->download_asset( $attachment_id, $file, basename( $file ), $media->get_transformations_from_string( $file ) );
}
/**
* Download an attachment source to the file system.
*
* @param int $attachment_id The attachment ID.
* @param string $file_path The path of the file.
* @param string $file_name The filename.
* @param array|null $transformations
*
* @return array|\WP_Error
*/
public function download_asset( $attachment_id, $file_path, $file_name, $transformations = null ) {
// Get the image and update the attachment.
require_once ABSPATH . WPINC . '/class-http.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/image.php';
require_once ABSPATH . 'wp-admin/includes/media.php';
// Fetch the asset.
try {
// Prime a file to stream to.
$upload = wp_upload_bits( $file_name, null, 'temp' );
if ( ! empty( $upload['error'] ) ) {
return new \WP_Error( 'download_error', $upload['error'] );
}
// If the public_id of an asset includes a file extension, a derived item will have the extension duplicated, but not in the source URL.
// This creates a 404. So, instead, we get the actual file name, and use that over the file name that the source url has.
$source_path = dirname( $file_path );
// Stream file to primed file.
$response = wp_safe_remote_get(
$source_path . '/' . $file_name,
array(
'timeout' => 300, // phpcs:ignore
'stream' => true,
'filename' => $upload['file'],
)
);
if ( is_wp_error( $response ) ) {
return $response;
}
if ( 200 !== $response['response']['code'] ) {
$header_error = wp_remote_retrieve_header( $response, 'x-cld-error' );
if ( ! empty( $header_error ) ) {
$error = $header_error;
} else {
$error = __( 'Could not download the Cloudinary asset.', 'cloudinary' );
}
return new \WP_Error( 'download_error', $error );
}
// Prepare the asset.
update_attached_file( $attachment_id, $upload['file'] );
ob_start(); // Catch possible errors in WordPress's ID3 module when setting meta for transformed videos.
$meta = wp_generate_attachment_metadata( $attachment_id, $upload['file'] );
$captured_errors = ob_get_clean();
wp_update_attachment_metadata( $attachment_id, $meta );
} catch ( \Exception $e ) {
return new \WP_Error( 'download_error', $e->getMessage() );
}
$attachment = wp_prepare_attachment_for_js( $attachment_id );
// Log errors if captured.
if ( ! empty( $captured_errors ) ) {
$attachment['_captured_errors'] = $captured_errors;
}
// Do transformations.
if ( 'image' === $attachment['type'] ) {
// Get the cloudinary_id from public_id not Media::cloudinary_id().
$cloudinary_id = $this->plugin->components['media']->get_cloudinary_id( $attachment_id );
// Make sure all sizes have the transformations on for previewing.
foreach ( $attachment['sizes'] as $name => &$size ) {
$size['url'] = $this->plugin->components['media']->cloudinary_url( $attachment_id, $name, $transformations, $cloudinary_id );
}
}
// Prepare response.
$response = array(
'success' => true,
'data' => $attachment,
);
return $response;
}
}