PluginProbe
WP-Stateless – Google Cloud Storage / trunk
WP-Stateless – Google Cloud Storage vtrunk
4.4.3 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.3.0 2.3.1 2.3.2 3.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 All 62 releases
← All changes | lib/classes/class-utility.php +459 -412 3.0trunk View file →
@@ -1,5 +1,6 @@
1 1 <?php
2 +
2 3 /**
3 4 * Helper Functions List
4 5 *
5 6 * Can be called via Singleton. Since Singleton uses magic method __call().
@@ -12,14 +13,17 @@
12 13 */
13 14
14 15 namespace wpCloud\StatelessMedia {
15 16
16 - if( !class_exists( 'wpCloud\StatelessMedia\Utility' ) ) {
17 + use wpCloud\StatelessMedia\Sync\BackgroundSync;
18 + use \Firebase\JWT\JWT;
19 + use \Firebase\JWT\Key;
17 20
21 + if (!class_exists('wpCloud\StatelessMedia\Utility')) {
22 +
18 23 class Utility {
19 24
20 25 static $can_delete_attachment = [];
21 - static $synced_sizes = [];
22 26
23 27 /**
24 28 * ChromeLogger
25 29 *
@@ -25,34 +29,24 @@
25 29 *
26 30 * @author potanin@UD
27 31 * @param $data
28 32 */
29 - static public function log( $data ) {
33 + static public function log($data) {
30 34
31 - if( !class_exists( 'wpCloud\StatelessMedia\Logger' ) ) {
32 - include_once( __DIR__ . '/class-logger.php' );
35 + if (!class_exists('wpCloud\StatelessMedia\Logger')) {
36 + include_once(__DIR__ . '/class-logger.php');
33 37 }
34 38
35 - if( !class_exists( 'wpCloud\StatelessMedia\Logger' ) ) {
39 + if (!class_exists('wpCloud\StatelessMedia\Logger')) {
36 40 return;
37 41 }
38 42
39 - if( defined( 'WP_STATELESS_CONSOLE_LOG' ) && WP_STATELESS_CONSOLE_LOG ) {
40 - Logger::log( '[wp-stateless]', $data );
43 + if (defined('WP_STATELESS_CONSOLE_LOG') && WP_STATELESS_CONSOLE_LOG) {
44 + Logger::log('[wp-stateless]', $data);
41 45 }
42 -
43 46 }
44 47
45 48 /**
46 - * Override Cache Control
47 - * @param $cacheControl
48 - * @return mixed
49 - */
50 - public static function override_cache_control( $cacheControl ) {
51 - return ud_get_stateless_media()->get( 'sm.cache_control' );
52 - }
53 -
54 - /**
55 49 * wp_normalize_path was added in 3.9.0
56 50 *
57 51 * @param $path
58 52 * @return mixed|string
@@ -57,18 +51,17 @@
57 51 * @param $path
58 52 * @return mixed|string
59 53 *
60 54 */
61 - public static function normalize_path( $path ) {
55 + public static function normalize_path($path) {
62 56
63 - if( function_exists( 'wp_normalize_path' ) ) {
64 - return wp_normalize_path( $path );
57 + if (function_exists('wp_normalize_path')) {
58 + return wp_normalize_path($path);
65 59 }
66 60
67 - $path = str_replace( '\\', '/', $path );
68 - $path = preg_replace( '|/+|', '/', $path );
61 + $path = str_replace('\\', '/', $path);
62 + $path = preg_replace('|/+|', '/', $path);
69 63 return $path;
70 -
71 64 }
72 65
73 66 /**
74 67 * Randomize file name
@@ -74,38 +67,38 @@
74 67 * Randomize file name
75 68 * @param $filename
76 69 * @return string
77 70 */
78 - public static function randomize_filename( $filename ) {
79 - $return = apply_filters( 'stateless_skip_cache_busting', null, $filename );
80 - if( $return ) {
71 + public static function randomize_filename($filename) {
72 + $return = apply_filters('stateless_skip_cache_busting', null, $filename);
73 + if ($return) {
81 74 return $return;
82 75 }
83 76
84 - if( preg_match( '/^[a-f0-9]{8}-/', $filename ) ) {
77 + if (preg_match('/^[a-f0-9]{8}-/', $filename)) {
85 78 return $filename;
86 79 }
87 80
88 - $info = pathinfo( $filename );
89 - $ext = empty( $info[ 'extension' ] ) ? '' : '' . $info[ 'extension' ];
81 + $info = pathinfo($filename);
82 + $ext = empty($info['extension']) ? '' : '' . $info['extension'];
90 83 $_parts = array();
91 - $rand = substr( md5( time() ), 0, 8 );
84 + $rand = substr(md5(time()), 0, 8);
92 85
93 - if( strpos( $info[ 'filename' ], '@' ) ) {
94 - $_cleanName = explode( '@', $info[ 'filename' ] )[ 0 ];
95 - $_retna = explode( '@', $info[ 'filename' ] )[ 1 ];
86 + if (strpos($info['filename'], '@')) {
87 + $_cleanName = explode('@', $info['filename'])[0];
88 + $_retna = explode('@', $info['filename'])[1];
96 89 $_parts[] = $rand;
97 90 $_parts[] = '-';
98 - $_parts[] = strtolower( $_cleanName );
99 - $_parts[] = '@' . strtolower( $_retna );
91 + $_parts[] = strtolower($_cleanName);
92 + $_parts[] = '@' . strtolower($_retna);
100 93 } else {
101 94 $_parts[] = $rand;
102 95 $_parts[] = '-';
103 - $_parts[] = strtolower( $info[ 'filename' ] );
96 + $_parts[] = strtolower($info['filename']);
104 97 }
105 98
106 - $filename = join( '', $_parts );
107 - if( !empty( $ext ) ) {
99 + $filename = join('', $_parts);
100 + if (!empty($ext)) {
108 101 $filename .= '.' . $ext;
109 102 }
110 103
111 104 return $filename;
@@ -118,11 +111,11 @@
118 111 * @param array $metadata
119 112 * @param array $data
120 113 * @return string
121 114 */
122 - public static function getContentDisposition( $attachment_id = null, $metadata = array(), $data = array() ) {
115 + public static function getContentDisposition($attachment_id = null, $metadata = array(), $data = array()) {
123 116 // return 'Content-Disposition: attachment; filename=some-file.sql';
124 - return apply_filters( 'sm:item:contentDisposition', null, array( 'attachment_id' => $attachment_id, 'mime_type' => get_post_mime_type( $attachment_id ), 'metadata' => $metadata, 'data' => $data ) );
117 + return apply_filters('sm:item:contentDisposition', null, array('attachment_id' => $attachment_id, 'mime_type' => get_post_mime_type($attachment_id), 'metadata' => $metadata, 'data' => $data));
125 118 }
126 119
127 120 /**
128 121 * @param null $attachment_id
@@ -129,26 +122,22 @@
129 122 * @param array $metadata
130 123 * @param array $data
131 124 * @return string
132 125 */
133 - public static function getCacheControl( $attachment_id = null, $metadata = array(), $data = array() ) {
134 - if( !$attachment_id ) {
135 - return apply_filters( 'sm:item:cacheControl', 'private, no-cache, no-store', $attachment_id, array( 'attachment_id' => null, 'mime_type' => null, 'metadata' => $metadata, 'data' => $data ) );
126 + public static function getCacheControl($attachment_id = null, $metadata = array(), $data = array()) {
127 + if (!$attachment_id) {
128 + return apply_filters('sm:item:cacheControl', 'private, no-cache, no-store', $attachment_id, array('attachment_id' => null, 'mime_type' => null, 'metadata' => $metadata, 'data' => $data));
136 129 }
137 130
138 - $_mime_type = get_post_mime_type( $attachment_id );
131 + $_mime_type = get_post_mime_type($attachment_id);
139 132
140 - // Treat images as public.
141 - if( strpos( $_mime_type, 'image/' ) !== false ) {
142 - return apply_filters( 'sm:item:cacheControl', 'public, max-age=36000, must-revalidate', array( 'attachment_id' => $attachment_id, 'mime_type' => null, 'metadata' => $metadata, 'data' => $data ) );
133 + // Treat SQL as non-public.
134 + if (strpos($_mime_type, 'sql') !== false) {
135 + return apply_filters('sm:item:cacheControl', 'private, no-cache, no-store', array('attachment_id' => $attachment_id, 'mime_type' => null, 'metadata' => $metadata, 'data' => $data));
143 136 }
144 137
145 - // Treat images as public.
146 - if( strpos( $_mime_type, 'sql' ) !== false ) {
147 - return apply_filters( 'sm:item:cacheControl', 'private, no-cache, no-store', array( 'attachment_id' => $attachment_id, 'mime_type' => null, 'metadata' => $metadata, 'data' => $data ) );
148 - }
149 -
150 - return apply_filters( 'sm:item:cacheControl', 'public, max-age=30, no-store, must-revalidate', array( 'attachment_id' => $attachment_id, 'mime_type' => null, 'metadata' => $metadata, 'data' => $data ) );
138 + // Treat all other files as public.
139 + return apply_filters('sm:item:cacheControl', ud_get_stateless_media()->get_default_cache_control(), array('attachment_id' => $attachment_id, 'mime_type' => null, 'metadata' => $metadata, 'data' => $data));
151 140 }
152 141
153 142 /**
154 143 * Add/Update Media to Bucket
@@ -164,23 +153,23 @@
164 153 * @param boolean $force Whether to force the upload incase of it's already exists.
165 154 * @param array $args Whether to only sync the full size image.
166 155 * @return bool|string
167 156 */
168 - public static function add_media( $metadata, $attachment_id, $force = false, $args = array() ) {
169 - global $stateless_synced_full_size;
170 - $sm_mode = ud_get_stateless_media()->get( 'sm.mode' );
157 + public static function add_media($metadata, $attachment_id, $force = false, $args = array()) {
158 + $sm_mode = ud_get_stateless_media()->get('sm.mode');
171 159 $file = '';
172 160 $upload_dir = wp_upload_dir();
173 - $args = wp_parse_args( $args, array( 'no_thumb' => false, 'is_webp' => '', // expected value ".webp";
174 - ) );
161 + $args = wp_parse_args($args, array(
162 + 'is_webp' => '', // expected value ".webp";
163 + ));
175 164
176 165 /* Get metadata in case if method is called directly. */
177 - if( current_filter() !== 'wp_generate_attachment_metadata' && current_filter() !== 'wp_update_attachment_metadata' && current_filter() !== 'intermediate_image_sizes_advanced' ) {
178 - $metadata = wp_get_attachment_metadata( $attachment_id );
166 + if (current_filter() !== 'wp_generate_attachment_metadata' && current_filter() !== 'wp_update_attachment_metadata' && current_filter() !== 'intermediate_image_sizes_advanced') {
167 + $metadata = wp_get_attachment_metadata($attachment_id);
179 168 }
180 169
181 170 // making sure meta data isn't null.
182 - if( empty( $metadata ) ) {
171 + if (empty($metadata)) {
183 172 $metadata = array();
184 173 }
185 174
186 175 /**
@@ -199,25 +188,25 @@
199 188 * @param string $attachment_id Attachment ID.
200 189 * @param bool $force (optional) Whether to force the sync even the file already exist in GCS.
201 190 * @param bool $args (optional) Whether to only sync the full size image.
202 191 */
203 - $check = apply_filters( 'wp_stateless_skip_add_media', null, $metadata, $attachment_id, $force, $args );
192 + $check = apply_filters('wp_stateless_skip_add_media', null, $metadata, $attachment_id, $force, $args);
204 193
205 194 $client = ud_get_stateless_media()->get_client();
206 195
207 - if( ( !is_wp_error( $client ) || ( $sm_mode == 'stateless' && !wp_doing_ajax() ) ) && !$check ) {
196 + if ((!is_wp_error($client) || ($sm_mode == 'stateless' && !wp_doing_ajax())) && !$check) {
208 197
209 198 $image_host = ud_get_stateless_media()->get_gs_host();
210 199 $bucketLink = apply_filters('wp_stateless_bucket_link', $image_host);
211 - $fullsizepath = wp_normalize_path( get_attached_file( $attachment_id ) );
212 - $_cacheControl = self::getCacheControl( $attachment_id, $metadata, null );
213 - $_contentDisposition = self::getContentDisposition( $attachment_id, $metadata, null );
200 + $fullsizepath = wp_normalize_path(get_attached_file($attachment_id));
201 + $_cacheControl = self::getCacheControl($attachment_id, $metadata, null);
202 + $_contentDisposition = self::getContentDisposition($attachment_id, $metadata, null);
214 203
215 204 // Ensure image upload to GCS when attachment is updated,
216 205 // by checking if the attachment metadata is changed.
217 - if( $attachment_id && !empty( $metadata ) && !$force ) {
218 - $db_metadata = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
219 - if( $db_metadata != $metadata ) {
206 + if ($attachment_id && !empty($metadata) && !$force) {
207 + $db_metadata = get_post_meta($attachment_id, '_wp_attachment_metadata', true);
208 + if ($db_metadata != $metadata) {
220 209 $force = true;
221 210 }
222 211 }
223 212
@@ -237,27 +226,27 @@
237 226 * @param string $attachment_id Attachment ID.
238 227 * @param bool $force (optional) Whether to force the sync even the file already exist in GCS.
239 228 * @param bool $args (optional) Whether to only sync the full size image.
240 229 */
241 - $skip_remove_media = apply_filters( 'wp_stateless_skip_remove_media', false, $metadata, $attachment_id, $force, $args );
230 + $skip_remove_media = apply_filters('wp_stateless_skip_remove_media', false, $metadata, $attachment_id, $force, $args);
242 231
243 232 // Make non-images uploadable.
244 233 // empty $metadata['file'] can cause problem, so we need to generate it.
245 - if( empty( $metadata[ 'file' ] ) && $attachment_id ) {
246 - $mime_type = get_post_mime_type( $attachment_id );
247 - $file = str_replace( wp_normalize_path( trailingslashit( $upload_dir[ 'basedir' ] ) ), '', $fullsizepath );
234 + if (empty($metadata['file']) && $attachment_id) {
235 + $mime_type = get_post_mime_type($attachment_id);
236 + $file = str_replace(wp_normalize_path(trailingslashit($upload_dir['basedir'])), '', $fullsizepath);
248 237
249 238 // We shouldn't create $metadata["file"] if it's PDF file.
250 - if( $mime_type != "application/pdf" ) {
251 - $metadata[ "file" ] = $file;
239 + if ($mime_type != "application/pdf") {
240 + $metadata["file"] = $file;
252 241 }
253 242 }
254 243
255 - $cloud_meta = get_post_meta( $attachment_id, 'sm_cloud', true );
244 + $cloud_meta = apply_filters('wp_stateless_get_file', [], $attachment_id, true);
256 245
257 246 $cloud_meta = wp_parse_args($cloud_meta, array(
258 247 'name' => '',
259 - 'bucket' => ud_get_stateless_media()->get( 'sm.bucket' ),
248 + 'bucket' => ud_get_stateless_media()->get('sm.bucket'),
260 249 'fileLink' => '',
261 250 'mediaLink' => '',
262 251 'cacheControl' => $_cacheControl,
263 252 'contentDisposition' => $_contentDisposition,
@@ -268,51 +257,38 @@
268 257 * Storing file size to sm_cloud first,
269 258 * Because assigning directly to $metadata['filesize'] don't work.
270 259 * Maybe filesize gets removed in first run (when file exists).
271 260 */
272 - if( file_exists( $fullsizepath ) ) {
273 - $cloud_meta[ 'filesize' ] = filesize( $fullsizepath );
261 + if (file_exists($fullsizepath)) {
262 + $cloud_meta['filesize'] = filesize($fullsizepath);
274 263 }
275 264 // Getting file size from sm_cloud.
276 - if( !empty( $cloud_meta[ 'filesize' ] ) ) {
277 - $metadata[ 'filesize' ] = $cloud_meta[ 'filesize' ];
265 + if (!empty($cloud_meta['filesize'])) {
266 + $metadata['filesize'] = $cloud_meta['filesize'];
278 267 }
279 268
280 - $image_sizes = self::get_path_and_url( $metadata, $attachment_id );
281 - foreach( $image_sizes as $size => $img ) {
282 - if ( (isset($_REQUEST['size']) && $_REQUEST['size'] == $size) || empty($_REQUEST['size']) ) {
283 - // also skips full size image if already uploaded using that feature.
284 - // and delete it in ephemeral modes as it already bin uploaded through intermediate_image_sizes_advanced filter.
285 - if( !$img[ 'is_thumb' ] && $stateless_synced_full_size == $attachment_id ) {
286 - if( $sm_mode === 'ephemeral' && $args[ 'no_thumb' ] != true && \file_exists( $img[ 'path' ] ) && !$skip_remove_media ) {
287 - unlink( $img[ 'path' ] );
288 - }
289 - continue;
290 - }
269 + $image_sizes = self::get_path_and_url($metadata, $attachment_id);
291 270
292 - // skips thumbs when it's called from Upload the full size image first, through intermediate_image_sizes_advanced filter.
293 - if( $args[ 'no_thumb' ] && $img[ 'is_thumb' ] || !empty( self::$synced_sizes[ $attachment_id ][ $size ] ) && $sm_mode !== 'stateless' && !$args[ 'is_webp' ] ) {
294 - continue;
295 - }
296 -
271 + foreach ($image_sizes as $size => $img) {
272 + if ((isset($_REQUEST['size']) && $_REQUEST['size'] == $size) || empty($_REQUEST['size'])) {
297 273 // GCS metadata
298 274 $_metadata = array(
299 - "width" => $img[ 'width' ],
300 - "height" => $img[ 'height' ],
275 + "width" => $img['width'],
276 + "height" => $img['height'],
301 277 'child-of' => $attachment_id,
302 - 'file-hash' => md5( $file ),
278 + 'file-hash' => md5($file),
303 279 'size' => $size,
304 280 );
305 281
306 282 // adding extra GCS meta for full size image.
307 - if( !$img[ 'is_thumb' ] ) {
308 - unset( $_metadata[ 'child-of' ] ); // no need in full size image.
309 - $_metadata[ 'object-id' ] = $attachment_id;
310 - $_metadata[ 'source-id' ] = md5( $attachment_id . ud_get_stateless_media()->get( 'sm.bucket' ) );
283 + if (!$img['is_thumb']) {
284 + unset($_metadata['child-of']); // no need in full size image.
285 + $_metadata['object-id'] = $attachment_id;
286 + $_metadata['source-id'] = md5($attachment_id . ud_get_stateless_media()->get('sm.bucket'));
311 287 }
312 288
313 - $media_args = array_filter( array(
314 - 'force' => $img['is_thumb'] ? $force : $force && $stateless_synced_full_size != $attachment_id,
289 + $media_args = array_filter(array(
290 + 'force' => $force,
315 291 'name' => $img['gs_name'],
316 292 'is_webp' => $args['is_webp'],
317 293 'mimeType' => $img['mime_type'],
318 294 'metadata' => $_metadata,
@@ -318,14 +294,15 @@
318 294 'metadata' => $_metadata,
319 295 'absolutePath' => $img['path'],
320 296 'cacheControl' => $_cacheControl,
321 297 'contentDisposition' => $_contentDisposition,
322 - ) );
298 + ));
323 299
324 - if ( $sm_mode == 'stateless' && !wp_doing_ajax() ) {
300 + if ($sm_mode == 'stateless' && !wp_doing_ajax() && !wp_doing_cron()
301 + || ($sm_mode == 'stateless' && wp_doing_ajax()) ) {
325 302 global $gs_client;
326 303
327 - $media_args = wp_parse_args( $media_args, array(
304 + $media_args = wp_parse_args($media_args, array(
328 305 'use_root' => true,
329 306 'force' => false,
330 307 'name' => false,
331 308 'absolutePath' => false,
@@ -331,13 +308,13 @@
331 308 'absolutePath' => false,
332 309 'mimeType' => 'image/jpeg',
333 310 'metadata' => array(),
334 311 'is_webp' => '',
335 - ) );
312 + ));
336 313 $media_args = apply_filters('wp_stateless_add_media_args', $media_args);
337 314
338 315 //Bucket
339 - $bucket = ud_get_stateless_media()->get( 'sm.bucket' );
316 + $bucket = ud_get_stateless_media()->get('sm.bucket');
340 317
341 318 $bucket = $gs_client->bucket($bucket);
342 319 $object = $bucket->object($media_args['name']);
343 320
@@ -345,59 +322,48 @@
345 322 * Updating object metadata, ACL, CacheControl and contentDisposition
346 323 * @return media object
347 324 */
348 325 try {
349 - $media = $object->update( array( 'metadata' => $media_args['metadata']) +
350 - array('cacheControl' => $_cacheControl,
351 - 'predefinedAcl' => 'publicRead',
352 - 'contentDisposition' => $_contentDisposition)
326 + $mediaOptions = array(
327 + 'cacheControl' => $_cacheControl,
328 + 'contentDisposition' => $_contentDisposition
353 329 );
354 330
331 + if ( !defined('WP_STATELESS_SKIP_ACL_SET') || !WP_STATELESS_SKIP_ACL_SET) {
332 + $mediaOptions['predefinedAcl'] = 'publicRead';
333 + }
334 +
335 + $media = $object->update(array('metadata' => $media_args['metadata']) + $mediaOptions);
336 +
355 337 $cloud_meta = self::generate_cloud_meta($cloud_meta, $media, $size, $img, $bucketLink);
356 338 } catch (\Throwable $th) {
357 339 //throw $th;
358 340 }
359 -
360 - $cloud_meta = self::generate_cloud_meta($cloud_meta, $media, $size, $img, $bucketLink);
361 -
362 341 } else {
363 342 /* Add default image */
364 - $media = $client->add_media( $media_args);
343 + $media = $client->add_media($media_args);
365 344
366 345 /* Break if we have errors. */
367 - if( !is_wp_error( $media ) ) {
346 + if (!is_wp_error($media)) {
368 347 // @note We don't add storageClass because it's same as parent...
369 - $cloud_meta = self::generate_cloud_meta( $cloud_meta, $media, $size, $img, $bucketLink );
348 + $cloud_meta = self::generate_cloud_meta($cloud_meta, $media, $size, $img, $bucketLink);
370 349
371 350 /**
372 351 * Ephemeral and stateless mode: we don't need the local version.
373 352 * Except when uploading the full size image first.
374 353 */
375 - if( self::can_delete_attachment( $attachment_id, $args ) && !$skip_remove_media ) {
376 - unlink( $img[ 'path' ] );
354 + if (self::can_delete_attachment($attachment_id, $args) && !$skip_remove_media) {
355 + @unlink($img['path']);
377 356 }
378 357 }
379 358 }
380 - // Setting
381 - if( empty( self::$synced_sizes[ $attachment_id ][ $size ] ) ) {
382 - self::$synced_sizes[ $attachment_id ][ $size ] = true;
383 - }
384 359 }
385 360 }
386 361 // End of image sync loop
387 - if( !$args[ 'is_webp' ] ) {
388 - update_post_meta( $attachment_id, 'sm_cloud', $cloud_meta );
389 - } else {
390 - // There is no use case for is_webp meta.
391 - // $cloud_meta = get_post_meta( $attachment_id, 'sm_cloud', true);
392 - // $cloud_meta['is_webp'] = true;
393 - // update_post_meta( $attachment_id, 'sm_cloud', $cloud_meta );
362 + if (!$args['is_webp']) {
363 + update_post_meta($attachment_id, 'sm_cloud', $cloud_meta);
394 364 }
395 365
396 - if( $args[ 'no_thumb' ] == true ) {
397 - $stateless_synced_full_size = $attachment_id;
398 - }
399 -
400 366 /**
401 367 * Triggers when the media and it's thumbs are synced.
402 368 *
403 369 * $force and $args params will no be passed on non media library uploads.
@@ -409,9 +375,9 @@
409 375 * @param string $attachment_id Attachment ID.
410 376 * @param bool $force (optional) Whether to force the sync even the file already exist in GCS.
411 377 * @param bool $args (optional) Whether to only sync the full size image.
412 378 */
413 - $metadata = apply_filters( 'wp_stateless_media_synced', $metadata, $attachment_id, $force, $args );
379 + $metadata = apply_filters('wp_stateless_media_synced', $metadata, $attachment_id, $force, $args);
414 380 }
415 381
416 382 return $metadata;
417 383 }
@@ -425,32 +391,33 @@
425 391 * @action delete_attachment
426 392 * @author peshkov@UD
427 393 * @param $post_id
428 394 */
429 - public static function remove_media( $post_id ) {
395 + public static function remove_media($post_id) {
430 396 /* Get attachments metadata */
431 - $metadata = wp_get_attachment_metadata( $post_id );
397 + $metadata = wp_get_attachment_metadata($post_id);
432 398
433 399 /* Be sure we have the same bucket in settings and have GS object's name before proceed. */
434 - if( isset( $metadata[ 'gs_name' ] ) && isset( $metadata[ 'gs_bucket' ] ) && $metadata[ 'gs_bucket' ] == ud_get_stateless_media()->get( 'sm.bucket' ) ) {
400 + if (isset($metadata['gs_name']) && isset($metadata['gs_bucket']) && $metadata['gs_bucket'] == ud_get_stateless_media()->get('sm.bucket')) {
435 401 $client = ud_get_stateless_media()->get_client();
436 - if( !is_wp_error( $client ) ) {
402 + if (!is_wp_error($client)) {
437 403
438 404 /* Remove default image */
439 - $client->remove_media( $metadata[ 'gs_name' ], $post_id );
405 + $client->remove_media($metadata['gs_name'], $post_id);
406 + $client->remove_media(get_attached_file($post_id), $post_id);
407 +
440 408 // Remove webp
441 - $client->remove_media( $metadata[ 'gs_name' ] . '.webp', $post_id, true, "", true );
409 + $client->remove_media($metadata['gs_name'] . '.webp', $post_id, true, "", true);
442 410
443 411 /* Now, go through all sizes and remove 'image sizes' images from Bucket too. */
444 - if( !empty( $metadata[ 'sizes' ] ) && is_array( $metadata[ 'sizes' ] ) ) {
445 - foreach( $metadata[ 'sizes' ] as $k => $v ) {
446 - if( !empty( $v[ 'gs_name' ] ) ) {
447 - $client->remove_media( $v[ 'gs_name' ], $post_id, true, $k );
448 - $client->remove_media( $v[ 'gs_name' ] . '.webp', $post_id, true, $k, true );
412 + if (!empty($metadata['sizes']) && is_array($metadata['sizes'])) {
413 + foreach ($metadata['sizes'] as $k => $v) {
414 + if (!empty($v['gs_name'])) {
415 + $client->remove_media($v['gs_name'], $post_id, true, $k);
416 + $client->remove_media($v['gs_name'] . '.webp', $post_id, true, $k, true);
449 417 }
450 418 }
451 419 }
452 -
453 420 }
454 421 }
455 422 }
456 423
@@ -459,28 +426,29 @@
459 426 * @param $metadata
460 427 * @param $attachment_id
461 428 * @return mixed
462 429 */
463 - public static function get_path_and_url( $metadata, $attachment_id ) {
430 + public static function get_path_and_url($metadata, $attachment_id) {
464 431 /* Get metadata in case if method is called directly. */
465 - if( empty( $metadata ) && current_filter() !== 'wp_generate_attachment_metadata' && current_filter() !== 'wp_update_attachment_metadata' ) {
466 - $metadata = wp_get_attachment_metadata( $attachment_id );
432 + if (empty($metadata) && current_filter() !== 'wp_generate_attachment_metadata' && current_filter() !== 'wp_update_attachment_metadata') {
433 + $metadata = wp_get_attachment_metadata($attachment_id);
467 434 }
468 435
469 436 $gs_name_path = array();
470 - $full_size_path = get_attached_file( $attachment_id );
471 - $base_dir = dirname( $full_size_path );
437 + $full_size_path = get_attached_file($attachment_id);
438 + $base_dir = dirname($full_size_path);
472 439
473 - $use_wildcards = self::is_use_wildcards();
474 - $gs_name = apply_filters( 'wp_stateless_file_name', $full_size_path, true, $attachment_id, '', $use_wildcards );
475 - $gs_base_dir = dirname( $gs_name ) == '.' ? '' : trailingslashit(dirname( $gs_name ));
440 + $gs_name = apply_filters('wp_stateless_file_name', $full_size_path, true, $attachment_id, '');
441 + $gs_base_dir = dirname($gs_name) == '.' ? '' : trailingslashit(dirname($gs_name));
476 442
477 - if( !isset( $metadata[ 'width' ] ) && file_exists( $full_size_path ) ) {
443 + if (!isset($metadata['width']) && file_exists($full_size_path)) {
478 444 try {
479 - $_image_size = getimagesize( $full_size_path );
480 - $metadata[ 'width' ] = $_image_size[ 0 ];
481 - $metadata[ 'height' ] = $_image_size[ 1 ];
482 - } catch( \Exception $e ) {
445 + $_image_size = getimagesize($full_size_path);
446 + if ($_image_size !== false && is_array($_image_size)) {
447 + $metadata['width'] = $_image_size[0];
448 + $metadata['height'] = $_image_size[1];
449 + }
450 + } catch (\Exception $e) {
483 451 // lets do nothing.
484 452 }
485 453 }
486 454
@@ -488,20 +456,20 @@
488 456 'gs_name' => $gs_name,
489 457 'path' => $full_size_path,
490 458 'sm_meta' => true,
491 459 'is_thumb' => false,
492 - 'mime_type' => get_post_mime_type( $attachment_id ),
460 + 'mime_type' => get_post_mime_type($attachment_id),
493 461 'width' => isset($metadata['width']) ? $metadata['width'] : null,
494 462 'height' => isset($metadata['height']) ? $metadata['height'] : null,
495 463 );
496 464
497 465 /* Now we go through all available image sizes and upload them to Google Storage */
498 - if( !empty( $metadata[ 'sizes' ] ) && is_array( $metadata[ 'sizes' ] ) ) {
499 - foreach( $metadata[ 'sizes' ] as $image_size => $data ) {
500 - if( empty( $data[ 'file' ] ) ) continue;
501 - $absolutePath = wp_normalize_path( $base_dir . '/' . $data[ 'file' ] );
502 - $gs_name = $gs_base_dir . $data[ 'file' ];
503 - $gs_name = apply_filters( 'wp_stateless_file_name', $gs_name, true, $attachment_id, $image_size, $use_wildcards );
466 + if (!empty($metadata['sizes']) && is_array($metadata['sizes'])) {
467 + foreach ($metadata['sizes'] as $image_size => $data) {
468 + if (empty($data['file'])) continue;
469 + $absolutePath = wp_normalize_path($base_dir . '/' . $data['file']);
470 + $gs_name = $gs_base_dir . $data['file'];
471 + $gs_name = apply_filters('wp_stateless_file_name', $gs_name, true, $attachment_id, $image_size);
504 472
505 473 $gs_name_path[$image_size] = array(
506 474 'gs_name' => $gs_name,
507 475 'path' => $absolutePath,
@@ -513,9 +481,9 @@
513 481 );
514 482 }
515 483 }
516 484
517 - return apply_filters( 'wp_stateless_get_path_and_url', $gs_name_path, $metadata, $attachment_id );
485 + return apply_filters('wp_stateless_get_path_and_url', $gs_name_path, $metadata, $attachment_id);
518 486 }
519 487
520 488 /**
521 489 * Return URL and path for all image sizes of a attachment.
@@ -525,32 +493,31 @@
525 493 * @param $img
526 494 * @param $bucketLink
527 495 * @return mixed
528 496 */
529 - public static function generate_cloud_meta( $cloud_meta, $media, $image_size, $img, $bucketLink ) {
530 - $gs_name = !empty( $media[ 'name' ] ) ? $media[ 'name' ] : $img[ 'gs_name' ];
531 - $fileLink = trailingslashit( $bucketLink ) . $gs_name;
532 - $version = get_option( 'wp_sm_version', false );
497 + public static function generate_cloud_meta($cloud_meta, $media, $image_size, $img, $bucketLink) {
498 + $gs_name = !empty($media['name']) ? $media['name'] : $img['gs_name'];
499 + $fileLink = trailingslashit($bucketLink) . $gs_name;
500 + $version = get_option('wp_sm_version', false);
533 501
534 - if( $img[ 'is_thumb' ] ) {
502 + if ($img['is_thumb']) {
535 503 // Cloud meta for thumbs.
536 - $cloud_meta[ 'sizes' ][ $image_size ]['name'] = $gs_name;
537 - $cloud_meta[ 'sizes' ][ $image_size ]['fileLink'] = $fileLink;
538 - $cloud_meta[ 'sizes' ][ $image_size ]['mediaLink'] = $media[ 'mediaLink' ];
539 - $cloud_meta[ 'sizes' ][ $image_size ]['width'] = ($media[ 'metadata' ][ 'width' ]) ? $media[ 'metadata' ][ 'width' ] : $img[ 'width' ];
540 - $cloud_meta[ 'sizes' ][ $image_size ]['height'] = ($media[ 'metadata' ][ 'height' ]) ? $media[ 'metadata' ][ 'width' ] : $img[ 'height' ];
541 - }
542 - else{
504 + $cloud_meta['sizes'][$image_size]['name'] = $gs_name;
505 + $cloud_meta['sizes'][$image_size]['fileLink'] = $fileLink;
506 + $cloud_meta['sizes'][$image_size]['mediaLink'] = $media['mediaLink'];
507 + $cloud_meta['sizes'][$image_size]['width'] = ($media['metadata']['width']) ? $media['metadata']['width'] : $img['width'];
508 + $cloud_meta['sizes'][$image_size]['height'] = ($media['metadata']['height']) ? $media['metadata']['height'] : $img['height'];
509 + } else {
543 510 // cloud meta for full size image.
544 511 $cloud_meta['name'] = $gs_name;
545 512 $cloud_meta['fileLink'] = $fileLink;
546 - $cloud_meta['mediaLink'] = $media[ 'mediaLink' ];
547 - $cloud_meta['width'] = ($media[ 'metadata' ][ 'width' ]) ? $media[ 'metadata' ][ 'width' ] : $img[ 'width' ];
548 - $cloud_meta['height'] = ($media[ 'metadata' ][ 'height' ]) ? $media[ 'metadata' ][ 'width' ] : $img[ 'height' ];
549 - $cloud_meta['bucket'] = ud_get_stateless_media()->get( 'sm.bucket' );
513 + $cloud_meta['mediaLink'] = $media['mediaLink'];
514 + $cloud_meta['width'] = isset($media['metadata']['width']) ? $media['metadata']['width'] : ($img['width'] ? $img['width'] : 0);
515 + $cloud_meta['height'] = isset($media['metadata']['height']) ? $media['metadata']['height'] : ($img['height'] ? $img['height'] : 0);
516 + $cloud_meta['bucket'] = ud_get_stateless_media()->get('sm.bucket');
550 517 $cloud_meta['sm_version'] = $version;
551 518 }
552 - return apply_filters( 'wp_stateless_generate_cloud_meta', $cloud_meta, $media, $image_size, $img, $bucketLink );
519 + return apply_filters('wp_stateless_generate_cloud_meta', $cloud_meta, $media, $image_size, $img, $bucketLink);
553 520 }
554 521
555 522 /**
556 523 * join_url
@@ -558,51 +525,56 @@
558 525 * @param array $parts
559 526 * @param boolean $encode
560 527 * @return string $url
561 528 */
562 - public static function join_url( $parts, $encode=TRUE ){
563 - if ( $encode ){
564 - if ( isset( $parts['user'] ) )
565 - $parts['user'] = rawurlencode( $parts['user'] );
566 - if ( isset( $parts['pass'] ) )
567 - $parts['pass'] = rawurlencode( $parts['pass'] );
568 - if ( isset( $parts['host'] ) &&
569 - !preg_match( '!^(\[[\da-f.:]+\]])|([\da-f.:]+)$!ui', $parts['host'] ) )
570 - $parts['host'] = rawurlencode( $parts['host'] );
571 - if ( !empty( $parts['path'] ) )
572 - $parts['path'] = preg_replace( '!%2F!ui', '/',
573 - rawurlencode( $parts['path'] ) );
574 - if ( isset( $parts['query'] ) )
575 - $parts['query'] = rawurlencode( $parts['query'] );
576 - if ( isset( $parts['fragment'] ) )
577 - $parts['fragment'] = rawurlencode( $parts['fragment'] );
529 + public static function join_url($parts, $encode = TRUE) {
530 + if ($encode) {
531 + if (isset($parts['user']))
532 + $parts['user'] = rawurlencode($parts['user']);
533 + if (isset($parts['pass']))
534 + $parts['pass'] = rawurlencode($parts['pass']);
535 + if (
536 + isset($parts['host']) &&
537 + !preg_match('!^(\[[\da-f.:]+\]])|([\da-f.:]+)$!ui', $parts['host'])
538 + )
539 + $parts['host'] = rawurlencode($parts['host']);
540 + if (!empty($parts['path']))
541 + $parts['path'] = preg_replace(
542 + '!%2F!ui',
543 + '/',
544 + rawurlencode($parts['path'])
545 + );
546 + if (isset($parts['query']))
547 + $parts['query'] = rawurlencode($parts['query']);
548 + if (isset($parts['fragment']))
549 + $parts['fragment'] = rawurlencode($parts['fragment']);
578 550 }
579 551
580 552 $url = '';
581 - if ( !empty( $parts['scheme'] ) )
553 + if (!empty($parts['scheme']))
582 554 $url .= $parts['scheme'] . ':';
583 - if ( isset( $parts['host'] ) ){
555 + if (isset($parts['host'])) {
584 556 $url .= '//';
585 - if ( isset( $parts['user'] ) ){
557 + if (isset($parts['user'])) {
586 558 $url .= $parts['user'];
587 - if ( isset( $parts['pass'] ) )
559 + if (isset($parts['pass']))
588 560 $url .= ':' . $parts['pass'];
589 561 $url .= '@';
590 562 }
591 - if ( preg_match( '!^[\da-f]*:[\da-f.:]+$!ui', $parts['host'] ) )
563 + if (preg_match('!^[\da-f]*:[\da-f.:]+$!ui', $parts['host']))
592 564 $url .= '[' . $parts['host'] . ']'; // IPv6
593 565 else
594 566 $url .= $parts['host']; // IPv4 or name
595 - if ( isset( $parts['port'] ) )
567 + if (isset($parts['port']))
596 568 $url .= ':' . $parts['port'];
597 - if ( !empty( $parts['path'] ) && $parts['path'][0] != '/' )
569 + if (!empty($parts['path']) && $parts['path'][0] != '/')
598 570 $url .= '/';
599 571 }
600 - if ( !empty( $parts['path'] ) )
572 + if (!empty($parts['path']))
601 573 $url .= $parts['path'];
602 - if ( isset( $parts['query'] ) )
574 + if (isset($parts['query']))
603 575 $url .= '?' . $parts['query'];
604 - if ( isset( $parts['fragment'] ) )
576 + if (isset($parts['fragment']))
605 577 $url .= '#' . $parts['fragment'];
606 578 return $url;
607 579 }
608 580
@@ -611,10 +583,10 @@
611 583 * @param $t
612 584 * @param $user
613 585 * @return mixed
614 586 */
615 - public function add_webp_mime( $t, $user ) {
616 - $t[ 'webp' ] = 'image/webp';
587 + public function add_webp_mime($t, $user) {
588 + $t['webp'] = 'image/webp';
617 589 return $t;
618 590 }
619 591
620 592 /**
@@ -625,10 +597,10 @@
625 597 * @param array $image_meta
626 598 * @param int $attachment_id
627 599 * @return array $new_sizes
628 600 */
629 - public static function store_can_delete_attachment( $new_sizes, $image_meta, $attachment_id ) {
630 - if( !in_array( $attachment_id, self::$can_delete_attachment ) ) {
601 + public static function store_can_delete_attachment($new_sizes, $image_meta, $attachment_id) {
602 + if (!in_array($attachment_id, self::$can_delete_attachment)) {
631 603 self::$can_delete_attachment[] = $attachment_id;
632 604 }
633 605 return $new_sizes;
634 606 }
@@ -639,20 +611,20 @@
639 611 * @param int $attachment_id
640 612 * @param array $args
641 613 * @return boolean
642 614 */
643 - public static function can_delete_attachment( $attachment_id, $args ) {
644 - $sm_mode = ud_get_stateless_media()->get( 'sm.mode' );
615 + public static function can_delete_attachment($attachment_id, $args) {
616 + $sm_mode = ud_get_stateless_media()->get('sm.mode');
645 617
646 - if( in_array( $sm_mode, array( 'ephemeral', 'stateless' ) ) && $args[ 'no_thumb' ] != true ) {
618 + if (in_array($sm_mode, array('ephemeral', 'stateless'))) {
647 619 // checks whether it's WP 5.3 and 'intermediate_image_sizes_advanced' is passed.
648 620 // To be sure that we don't delete full size image before thumbnails are generated.
649 - if(
621 + if (
650 622 wp_attachment_is_image($attachment_id) &&
651 623 function_exists('is_wp_version_compatible') &&
652 624 is_wp_version_compatible('5.3-RC4-46673') &&
653 625 !in_array($attachment_id, self::$can_delete_attachment)
654 - ){
626 + ) {
655 627 return false;
656 628 }
657 629 return true;
658 630 }
@@ -681,23 +653,23 @@
681 653 * )
682 654 *
683 655 * @return bool
684 656 */
685 - public static function isCallStackMatches( $callstack, $conditions ) {
686 - if( !is_array( $conditions ) ) {
687 - $conditions = array( $conditions );
657 + public static function isCallStackMatches($callstack, $conditions) {
658 + if (!is_array($conditions)) {
659 + $conditions = array($conditions);
688 660 }
689 661
690 - foreach( $conditions as $condition ) {
691 - $condition[ 'stack_level' ] = $condition[ 'stack_level' ] ? $condition[ 'stack_level' ] : 0;
662 + foreach ($conditions as $condition) {
663 + $condition['stack_level'] = $condition['stack_level'] ? $condition['stack_level'] : 0;
692 664
693 - $levelData = $callstack[ $condition[ 'stack_level' ] ];
665 + $levelData = $callstack[$condition['stack_level']];
694 666
695 - unset( $condition[ 'stack_level' ] );
667 + unset($condition['stack_level']);
696 668
697 669 $levelMatches = false;
698 - foreach( $condition as $key => $value ) {
699 - if( isset( $levelData[ $key ] ) && $levelData[ $key ] === $value ) {
670 + foreach ($condition as $key => $value) {
671 + if (isset($levelData[$key]) && $levelData[$key] === $value) {
700 672 $levelMatches = true;
701 673 } else {
702 674 $levelMatches = false;
703 675 }
@@ -702,9 +674,9 @@
702 674 $levelMatches = false;
703 675 }
704 676 }
705 677
706 - if( $levelMatches ) return true;
678 + if ($levelMatches) return true;
707 679 }
708 680
709 681 return false;
710 682 }
@@ -720,19 +692,19 @@
720 692 *
721 693 * @return bool|int
722 694 * @throws \Exception
723 695 */
724 - public static function sync_get_attachment_if_exist( $url, $save_to ) {
725 - if( is_int( $url ) ) $url = wp_get_attachment_url( $url );
696 + public static function sync_get_attachment_if_exist($url, $save_to) {
697 + if (is_int($url)) $url = wp_get_attachment_url($url);
726 698
727 - $response = wp_remote_get( $url );
728 - if( !is_wp_error( $response ) && is_array( $response ) ) {
729 - if( !empty( $response[ 'response' ][ 'code' ] ) && $response[ 'response' ][ 'code' ] == 200 ) {
699 + $response = wp_remote_get($url);
700 + if (!is_wp_error($response) && is_array($response)) {
701 + if (!empty($response['response']['code']) && $response['response']['code'] == 200) {
730 702 try {
731 - if( wp_mkdir_p( dirname( $save_to ) ) ) {
732 - return file_put_contents( $save_to, $response[ 'body' ] );
703 + if (wp_mkdir_p(dirname($save_to))) {
704 + return file_put_contents($save_to, $response['body']);
733 705 }
734 - } catch( \Exception $e ) {
706 + } catch (\Exception $e) {
735 707 throw $e;
736 708 }
737 709 }
738 710 }
@@ -739,182 +711,76 @@
739 711 return false;
740 712 }
741 713
742 714 /**
743 - * Store failed attachment
744 - * @param $attachment_id
745 - * @param $mode
715 + * Get a secure JWT signing key
716 + * Priority: AUTH_SALT (if valid length) > Plugin-specific stored key > Generated key
717 + *
718 + * @return string A key suitable for HS256 (minimum 32 bytes)
746 719 */
747 - public static function sync_store_failed_attachment( $attachment_id, $mode ) {
748 - if( !in_array( $mode, [ 'other', 'cli_images', 'cli_other' ] ) ) {
749 - $mode = 'images';
720 + public static function get_jwt_signing_key() {
721 + // Minimum key length for HS256 (256 bits = 32 bytes)
722 + $min_key_length = 32;
723 +
724 + // Try AUTH_SALT first if it's long enough
725 + if (defined('AUTH_SALT') && !empty(AUTH_SALT) && strlen(AUTH_SALT) >= $min_key_length) {
726 + return AUTH_SALT;
750 727 }
751 -
752 - $fails = get_option( 'wp_stateless_failed_' . $mode );
753 - if( !empty( $fails ) && is_array( $fails ) ) {
754 - if( !in_array( $attachment_id, $fails ) ) {
755 - $fails[] = $attachment_id;
756 - }
757 - } else {
758 - $fails = array( $attachment_id );
728 +
729 + // Try to get stored plugin-specific key
730 + $stored_key = get_option('wp_stateless_jwt_key');
731 +
732 + if ($stored_key && strlen($stored_key) >= $min_key_length) {
733 + return $stored_key;
759 734 }
760 -
761 - update_option( 'wp_stateless_failed_' . $mode, $fails );
735 +
736 + // Generate a new secure key
737 + $new_key = self::generate_secure_key($min_key_length);
738 + update_option('wp_stateless_jwt_key', $new_key, false);
739 +
740 + return $new_key;
762 741 }
763 -
742 +
764 743 /**
765 - * Checking maybe attachment have already fixed
766 - * @param $mode
767 - * @param $attachment_id
768 - */
769 - public static function sync_maybe_fix_failed_attachment( $mode, $attachment_id ) {
770 - $fails = get_option( 'wp_stateless_failed_' . $mode );
771 -
772 - if( !empty( $fails ) && is_array( $fails ) ) {
773 - if( in_array( $attachment_id, $fails ) ) {
774 - foreach( array_keys( $fails, $attachment_id ) as $key ) {
775 - unset( $fails[ $key ] );
776 - }
777 - }
778 - }
779 -
780 - update_option( 'wp_stateless_failed_' . $mode, $fails );
781 - }
782 -
783 - /**
784 - * Store current synchronization progress
785 - * @param $mode
786 - * @param $id
787 - * @param $cli
788 - */
789 - public static function sync_store_current_progress( $mode, $id, $cli = false ) {
790 - if( !in_array( $mode, [ 'other', 'cli_images', 'cli_other' ] ) ) {
791 - $mode = 'images';
792 - }
793 -
794 - $first_processed = get_option( 'wp_stateless_' . $mode . '_first_processed' );
795 - if( !$first_processed ) {
796 - update_option( 'wp_stateless_' . $mode . '_first_processed', $id );
797 - }
798 - $last_processed = get_option( 'wp_stateless_' . $mode . '_last_processed' );
799 - if( !$last_processed || $id < (int) $last_processed || $cli ) {
800 - update_option( 'wp_stateless_' . $mode . '_last_processed', $id );
801 - }
802 - }
803 -
804 - /**
805 - * Get synchronization progress
806 - * @param $mode
807 - * @return array|bool
808 - */
809 - public static function sync_retrieve_current_progress( $mode ) {
810 - if( !in_array( $mode, [ 'other', 'cli_images', 'cli_other' ] ) ) {
811 - $mode = 'images';
812 - }
813 -
814 - $first_processed = get_option( 'wp_stateless_' . $mode . '_first_processed' );
815 - $last_processed = get_option( 'wp_stateless_' . $mode . '_last_processed' );
816 -
817 - if( !$first_processed || !$last_processed ) {
818 - return false;
819 - }
820 -
821 - return array( (int) $first_processed, (int) $last_processed );
822 - }
823 -
824 - /**
825 - * Reset synchronization progress
826 - * @param $mode
827 - */
828 - public static function sync_reset_current_progress( $mode ) {
829 - if( !in_array( $mode, [ 'other', 'cli_images', 'cli_other' ] ) ) {
830 - $mode = 'images';
831 - }
832 -
833 - delete_option( 'wp_stateless_' . $mode . '_first_processed' );
834 - delete_option( 'wp_stateless_' . $mode . '_last_processed' );
835 - }
836 -
837 - /**
838 - * Get fails
744 + * Generate a cryptographically secure random key
839 745 *
840 - * @param $mode
841 - * @return mixed|void
746 + * @param int $length Key length in bytes
747 + * @return string Base64-encoded key
842 748 */
843 - public static function sync_get_fails( $mode ) {
844 - if( !in_array( $mode, [ 'other', 'cli_images', 'cli_other' ] ) ) {
845 - $mode = 'images';
749 + private static function generate_secure_key($length = 32) {
750 + try {
751 + // Use random_bytes for PHP 7+
752 + $random_bytes = random_bytes($length);
753 + return base64_encode($random_bytes);
754 + } catch (\Exception $e) {
755 + // Fallback: use wp_generate_password
756 + return wp_generate_password($length * 2, true, true);
846 757 }
847 -
848 - return get_option( 'wp_stateless_failed_' . $mode );
849 758 }
850 759
851 760 /**
852 - * Get_non_processed_media_ids
761 + * Generate JWT token signed by secure key
762 + * Uses AUTH_SALT if valid, otherwise uses plugin-specific stored key
853 763 *
854 - * @param $mode
855 - * @param $files
856 - * @param bool $continue
857 - * @param $start_from
858 - * @return array
859 - * @throws \Exception
860 - */
861 - public static function sync_get_non_processed_media_ids( $mode, $files, $continue = false, $start_from = 0 ) {
862 - if( ud_get_stateless_media()->is_connected_to_gs() !== true ) {
863 - throw new \Exception( __( 'Not connected to GCS', ud_get_stateless_media()->domain ) );
864 - }
865 -
866 - if( $continue ) {
867 - $progress = self::sync_retrieve_current_progress( $mode );
868 -
869 - if( false !== $progress ) {
870 - if( $start_from && $start_from != 0 ) {
871 - // adding 1 because we subtracted 1 in js code for presentation.
872 - $progress[ 1 ] = $start_from + 1;
873 - }
874 - $ids = array();
875 - foreach( $files as $file ) {
876 - $id = (int) $file->ID;
877 - // only include IDs that have not been processed yet
878 - if( $id > $progress[ 0 ] || $id < $progress[ 1 ] ) {
879 - $ids[] = $id;
880 - }
881 - }
882 - return $ids;
883 - }
884 - }
885 -
886 - self::sync_reset_current_progress( $mode );
887 -
888 - $ids = array();
889 - foreach( $files as $file ) $ids[] = (int) $file->ID;
890 -
891 - return $ids;
892 - }
893 -
894 - /**
895 - * Generate JWT token signed by current site AUTH_SALT
896 - * If no AUTH_SALT defined - admin email used
897 - *
898 764 * @param $payload
899 765 * @param int $ttl
900 766 * @return string
901 767 */
902 768 public static function generate_jwt_token($payload, $ttl = 3600) {
903 - $payload = wp_parse_args( $payload, [
769 + $payload = wp_parse_args($payload, [
904 770 'iat' => $now = time(),
905 771 'iss' => $site_url = get_site_url(),
906 772 'aud' => $site_url,
907 773 'exp' => $now + $ttl
908 - ] );
774 + ]);
909 775
910 - $key = defined('AUTH_SALT') ? AUTH_SALT : get_option('admin_email');
911 - return \Firebase\JWT\JWT::encode( $payload, $key );
776 + $key = self::get_jwt_signing_key();
777 + return JWT::encode($payload, $key, 'HS256');
912 778 }
913 779
914 780 /**
915 781 * Verify and decode token
916 - * If no AUTH_SALT defined - admin email used
782 + * Uses the same secure key retrieval as generation
917 783 * Throws exceptions if cannot decode
918 784 *
919 785 * @param $token
920 786 * @return object
@@ -920,10 +786,10 @@
920 786 * @return object
921 787 * @throws \Exception
922 788 */
923 789 public static function verify_jwt_token($token) {
924 - $key = defined('AUTH_SALT') ? AUTH_SALT : get_option('admin_email');
925 - return \Firebase\JWT\JWT::decode($token, $key, ['HS256']);
790 + $key = self::get_jwt_signing_key();
791 + return JWT::decode($token, new Key($key, 'HS256'));
926 792 }
927 793
928 794 /**
929 795 * Generate auth token for wizard iframe
@@ -930,14 +796,14 @@
930 796 *
931 797 * @param int $ttl
932 798 * @return string
933 799 */
934 - public static function generate_wizard_auth_token( $ttl = 3600 ) {
800 + public static function generate_wizard_auth_token($ttl = 3600) {
935 801 $payload = [
936 802 'is_network' => is_network_admin(),
937 803 'user_id' => get_current_user_id()
938 804 ];
939 - return self::generate_jwt_token( $payload, $ttl );
805 + return self::generate_jwt_token($payload, $ttl);
940 806 }
941 807
942 808 /**
943 809 * Maps a file extensions to a mimetype.
@@ -946,11 +812,11 @@
946 812 *
947 813 * @return string|null
948 814 * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types
949 815 */
950 - public static function mimetype_from_extension($extension){
816 + public static function mimetype_from_extension($extension) {
951 817 $file_type = wp_check_filetype($extension);
952 - if(!empty($file_type['type'])){
818 + if (!empty($file_type['type'])) {
953 819 return $file_type['type'];
954 820 }
955 821 static $mimetypes = [
956 822 '7z' => 'application/x-7z-compressed',
@@ -1053,22 +919,14 @@
1053 919 'yml' => 'text/yaml',
1054 920 'zip' => 'application/zip',
1055 921 ];
1056 922
1057 - $extension = strtolower( $extension );
923 + $extension = strtolower($extension);
1058 924
1059 - return isset( $mimetypes[ $extension ] ) ? $mimetypes[ $extension ] : false;
925 + return isset($mimetypes[$extension]) ? $mimetypes[$extension] : false;
1060 926 }
1061 927
1062 928 /**
1063 - * Check using wildcards
1064 - * @return bool
1065 - */
1066 - public static function is_use_wildcards() {
1067 - return isset( $_REQUEST[ 'use_wildcards' ] ) ? $_REQUEST[ 'use_wildcards' ] : false;
1068 - }
1069 -
1070 - /**
1071 929 * @param $size
1072 930 * @return float|int
1073 931 */
1074 932 public static function convert_to_byte($size) {
@@ -1099,15 +957,204 @@
1099 957 */
1100 958 public static function get_stateless_media_data_count() {
1101 959 global $wpdb;
1102 960
961 + if ( !ud_get_stateless_media()->get('sm.use_postmeta') ) {
962 + try {
963 + return ud_stateless_db()->get_total_files();
964 + } catch (\Throwable $th) {
965 + }
966 + }
967 +
1103 968 $stateless_media = $wpdb->get_var($wpdb->prepare("
1104 - SELECT COUNT(meta_id)
1105 - FROM ".$wpdb->postmeta."
1106 - WHERE meta_key = %s
1107 - ", 'sm_cloud'));
969 + SELECT COUNT(meta_id)
970 + FROM " . $wpdb->postmeta . "
971 + WHERE meta_key = %s
972 + ", 'sm_cloud'));
1108 973
1109 974 return $stateless_media;
1110 975 }
976 +
977 + /**
978 + * Get a list of instances of all available sync methods
979 + */
980 + public static function get_available_sync_classes() {
981 + return array_filter(array_map(function ($class) {
982 + if (class_exists($class)) {
983 + try {
984 + $class = $class::instance();
985 + if (is_a($class, BackgroundSync::class)) {
986 + return $class;
987 + }
988 + return null;
989 + } catch (\Throwable $e) {
990 + error_log($e->getMessage());
991 + return null;
992 + }
993 + }
994 + return null;
995 + }, apply_filters('wp_stateless_sync_types', [])));
996 + }
997 +
998 + /**
999 + * Process single image attachment by id
1000 + *
1001 + * @param mixed $id Attachment ID
1002 + * @return \WP_Post
1003 + * @throws FatalException
1004 + * @throws UnprocessableException
1005 + */
1006 + public static function process_image_by_id($id) {
1007 + if (ud_get_stateless_media()->is_connected_to_gs() !== true) {
1008 + throw new FatalException(__('Not connected to GCS', ud_get_stateless_media()->domain));
1009 + }
1010 +
1011 + $image = get_post($id);
1012 +
1013 + if (!$image || 'attachment' != $image->post_type || 'image/' != substr($image->post_mime_type, 0, 6))
1014 + throw new UnprocessableException(sprintf(__('Failed to process item: %s is an invalid image ID.', ud_get_stateless_media()->domain), $id));
1015 +
1016 + $fullsizepath = get_attached_file($image->ID);
1017 +
1018 + // If no file found
1019 + if (false === $fullsizepath || !file_exists($fullsizepath)) {
1020 +
1021 + // Try get it and save
1022 + $result_code = ud_get_stateless_media()->get_client()->get_media(apply_filters('wp_stateless_file_name', $fullsizepath, true, "", ""), true, $fullsizepath);
1023 +
1024 + if ($result_code !== 200) {
1025 + if (!Utility::sync_get_attachment_if_exist($image->ID, $fullsizepath)) {
1026 + throw new UnprocessableException(sprintf(__('Both local and remote files are missing. Unable to process. (%s)', ud_get_stateless_media()->domain), $image->guid));
1027 + }
1028 + }
1029 + }
1030 +
1031 + do_action('sm:pre::synced::image', $id);
1032 + if (!function_exists('wp_generate_attachment_metadata')) {
1033 + require_once ABSPATH . '/wp-admin/includes/image.php';
1034 + }
1035 + $metadata = wp_generate_attachment_metadata($image->ID, $fullsizepath);
1036 +
1037 + if (get_post_mime_type($image->ID) !== 'image/svg+xml') {
1038 + if (is_wp_error($metadata)) {
1039 + throw new UnprocessableException($metadata->get_error_message());
1040 + }
1041 +
1042 + if (empty($metadata)) {
1043 + throw new UnprocessableException(sprintf(__('No metadata generated for %1$s (ID %2$s).', ud_get_stateless_media()->domain), esc_html(get_the_title($image->ID)), $image->ID));
1044 + }
1045 + }
1046 +
1047 + // trigger processing filters
1048 + wp_update_attachment_metadata($image->ID, $metadata);
1049 + do_action('sm:synced::image', $id, $metadata);
1050 +
1051 + return $image;
1052 + }
1053 +
1054 + /**
1055 + * Process single attachment file by id
1056 + *
1057 + * @param mixed $id Attachment ID
1058 + * @return \WP_Post
1059 + * @throws FatalException
1060 + * @throws UnprocessableException
1061 + */
1062 + public static function process_file_by_id($id) {
1063 + if (ud_get_stateless_media()->is_connected_to_gs() !== true) {
1064 + throw new FatalException(__('Not connected to GCS', ud_get_stateless_media()->domain));
1065 + }
1066 +
1067 + $file = get_post($id);
1068 +
1069 + if (!$file || 'attachment' != $file->post_type) {
1070 + throw new UnprocessableException(sprintf(__('Attachment not found: %s is an invalid file ID.', ud_get_stateless_media()->domain), $id));
1071 + }
1072 +
1073 + $fullsizepath = get_attached_file($file->ID);
1074 + $local_file_exists = file_exists($fullsizepath);
1075 +
1076 + if (false === $fullsizepath || !$local_file_exists) {
1077 +
1078 + // Try get it and save
1079 + $result_code = ud_get_stateless_media()->get_client()->get_media(apply_filters('wp_stateless_file_name', $fullsizepath, true, "", ""), true, $fullsizepath);
1080 +
1081 + if ($result_code !== 200) {
1082 + if (!Utility::sync_get_attachment_if_exist($file->ID, $fullsizepath)) { // Save file to local from proxy.
1083 + throw new UnprocessableException(sprintf(__('File not found (%s)', ud_get_stateless_media()->domain), $file->guid));
1084 + } else {
1085 + $local_file_exists = true;
1086 + }
1087 + } else {
1088 + $local_file_exists = true;
1089 + }
1090 + }
1091 +
1092 + if ($local_file_exists) {
1093 +
1094 + if (!ud_get_stateless_media()->get_client()->media_exists(apply_filters('wp_stateless_file_name', $fullsizepath, true, "", ""))) {
1095 +
1096 + if (!function_exists('wp_generate_attachment_metadata')) {
1097 + require_once ABSPATH . '/wp-admin/includes/media.php';
1098 + require_once ABSPATH . '/wp-admin/includes/image.php';
1099 + }
1100 + $metadata = wp_generate_attachment_metadata($file->ID, $fullsizepath);
1101 +
1102 + if (is_wp_error($metadata)) {
1103 + throw new UnprocessableException($metadata->get_error_message());
1104 + }
1105 +
1106 + wp_update_attachment_metadata($file->ID, $metadata);
1107 + do_action('sm:synced::nonImage', $id, $metadata);
1108 + } else {
1109 + // Ephemeral and Stateless modes: we don't need the local version.
1110 + if (ud_get_stateless_media()->get('sm.mode') === 'ephemeral' || ud_get_stateless_media()->get('sm.mode') === 'stateless') {
1111 + @unlink($fullsizepath);
1112 +
1113 + $metadata = wp_get_attachment_metadata($file->ID);
1114 + /**
1115 + * removing thumbnails
1116 + * https://github.com/udx/wp-stateless/issues/577
1117 + */
1118 + if (!empty($metadata['sizes'])) {
1119 + $base_dir = dirname($fullsizepath);
1120 + foreach ($metadata['sizes'] as $image_size => $data) {
1121 + $gs_name = $base_dir . '/' . $data['file'];
1122 + if (file_exists($gs_name)) {
1123 + @unlink($gs_name);
1124 + }
1125 + }
1126 + }
1127 + }
1128 + }
1129 + }
1130 +
1131 + return $file;
1132 + }
1133 +
1134 + /**
1135 + * Return list of files in a dir.
1136 + * @param string $dir: Directory path
1137 + * @return array - Lists of files in the directory and subdirectory.
1138 + */
1139 + public static function get_files($dir) {
1140 + $return = array();
1141 + if (is_dir($dir) && $dh = opendir($dir)) {
1142 + while ($file = readdir($dh)) {
1143 + if ($file != '.' && $file != '..') {
1144 + if (is_dir($dir . $file)) {
1145 + // since it is a directory we recursively get files.
1146 + $arr = self::get_files($dir . $file . '/');
1147 + $return = array_merge($return, $arr);
1148 + } else {
1149 + $return[] = $dir . $file;
1150 + }
1151 + }
1152 + }
1153 + closedir($dh);
1154 + }
1155 +
1156 + return $return;
1157 + }
1111 1158 }
1112 1159 }
1113 -}
1160 +}