| @@ -31,10 +31,13 @@ | ||
| 31 | 31 | private $deleting_attachment = false; |
| 32 | 32 | |
| 33 | 33 | |
| 34 | 34 | public static $source_type_prefix = "media"; |
| 35 | - public static $label = 'Media Library'; | |
| 36 | 35 | |
| 36 | + public static function get_label() { | |
| 37 | + return __('Media Library', 'media-cloud-sync'); | |
| 38 | + } | |
| 39 | + | |
| 37 | 40 | // Map which meta to be updated |
| 38 | 41 | public static $source_types = [ |
| 39 | 42 | "media_library" => [ |
| 40 | 43 | 'table' => 'posts', |
| @@ -113,8 +116,10 @@ | ||
| 113 | 116 | add_filter( 'wp_update_attachment_metadata', [ $this, 'update_attachment_metadata' ], 110, 2 ); |
| 114 | 117 | add_filter( 'pre_delete_attachment', [ $this, 'pre_delete_attachment' ], 20 ); |
| 115 | 118 | add_filter( 'delete_attachment', [ $this, 'delete_attachment' ], 20 ); |
| 116 | 119 | add_action( 'delete_post', [$this, 'delete_post'] ); |
| 120 | + // Safety net for code that deletes an attachment bypassing wp_delete_attachment(). | |
| 121 | + add_action( 'deleted_post', [ $this, 'deleted_post' ], 10, 2 ); | |
| 117 | 122 | add_filter( 'update_attached_file', [ $this, 'update_attached_file' ], 100, 2 ); |
| 118 | 123 | |
| 119 | 124 | add_action( 'wpmcs_do_update_attachment_metadata', [ $this, 'update_attachment_metadata' ], 10, 2 ); |
| 120 | 125 | } |
| @@ -556,9 +561,9 @@ | ||
| 556 | 561 | if ( $this->deleting_attachment ) { |
| 557 | 562 | return $file; |
| 558 | 563 | } |
| 559 | 564 | |
| 560 | - if (!Utils::is_ok_to_serve($attachment_id)) { | |
| 565 | + if (!Utils::should_serve_from_cloud($attachment_id)) { | |
| 561 | 566 | return $file; |
| 562 | 567 | } |
| 563 | 568 | |
| 564 | 569 | $wpmcsItem = Item::instance(); |
| @@ -611,9 +616,9 @@ | ||
| 611 | 616 | return $sources; |
| 612 | 617 | } |
| 613 | 618 | |
| 614 | 619 | // Must need $attachment_id other wise not possible to get data from the table |
| 615 | - if (!Utils::is_ok_to_serve($attachment_id)) { | |
| 620 | + if (!Utils::should_serve_from_cloud($attachment_id)) { | |
| 616 | 621 | return $sources; |
| 617 | 622 | } |
| 618 | 623 | |
| 619 | 624 | $wpmcsItem = Item::instance(); |
| @@ -680,10 +685,10 @@ | ||
| 680 | 685 | * @return array |
| 681 | 686 | */ |
| 682 | 687 | public function wp_get_attachment_image_attributes($attr, $attachment, $size='thumbnail') { |
| 683 | 688 | if ( |
| 684 | - !$attachment || | |
| 685 | - !Utils::is_ok_to_serve($attachment->ID) | |
| 689 | + !$attachment || | |
| 690 | + !Utils::should_serve_from_cloud($attachment->ID) | |
| 686 | 691 | ) { |
| 687 | 692 | return $attr; |
| 688 | 693 | } |
| 689 | 694 | |
| @@ -730,9 +735,9 @@ | ||
| 730 | 735 | * |
| 731 | 736 | * @return bool|mixed|WP_Error |
| 732 | 737 | */ |
| 733 | 738 | public function wp_get_attachment_url($url, $attachment_id) { |
| 734 | - if (!Utils::is_ok_to_serve($attachment_id)) { | |
| 739 | + if (!Utils::should_serve_from_cloud($attachment_id)) { | |
| 735 | 740 | return $url; |
| 736 | 741 | } |
| 737 | 742 | |
| 738 | 743 | $new_url = Item::instance()->get_url($attachment_id, 'full', 'media_library'); |
| @@ -762,9 +767,9 @@ | ||
| 762 | 767 | if ( ! is_string( $html ) ) { |
| 763 | 768 | return $html; |
| 764 | 769 | } |
| 765 | 770 | |
| 766 | - if (!Utils::is_ok_to_serve($id)) { | |
| 771 | + if (!Utils::should_serve_from_cloud($id)) { | |
| 767 | 772 | return $html; |
| 768 | 773 | } |
| 769 | 774 | |
| 770 | 775 | preg_match( '@\ssrc=[\'\"]([^\'\"]*)[\'\"]@', $html, $matches ); |
| @@ -804,9 +809,9 @@ | ||
| 804 | 809 | * |
| 805 | 810 | * @return array |
| 806 | 811 | */ |
| 807 | 812 | public function wp_get_attachment_image_src( $image, $attachment_id, $size, $icon ) { |
| 808 | - if (!Utils::is_ok_to_serve($attachment_id)) { | |
| 813 | + if (!Utils::should_serve_from_cloud($attachment_id)) { | |
| 809 | 814 | return $image; |
| 810 | 815 | } |
| 811 | 816 | |
| 812 | 817 | if ( isset( $image[0] ) ) { |
| @@ -832,9 +837,9 @@ | ||
| 832 | 837 | * |
| 833 | 838 | * @return array |
| 834 | 839 | */ |
| 835 | 840 | public function wp_prepare_attachment_for_js( $response, $attachment, $meta ) { |
| 836 | - if (!Utils::is_ok_to_serve($attachment->ID)) { | |
| 841 | + if (!Utils::should_serve_from_cloud($attachment->ID)) { | |
| 837 | 842 | return $response; |
| 838 | 843 | } |
| 839 | 844 | |
| 840 | 845 | if ( isset( $response['sizes'] ) && is_array( $response['sizes'] ) ) { |
| @@ -860,9 +865,9 @@ | ||
| 860 | 865 | * |
| 861 | 866 | * @return array |
| 862 | 867 | */ |
| 863 | 868 | public function image_get_intermediate_size( $data, $post_id, $size ) { |
| 864 | - if (!Utils::is_ok_to_serve($post_id)) { | |
| 869 | + if (!Utils::should_serve_from_cloud($post_id)) { | |
| 865 | 870 | return $data; |
| 866 | 871 | } |
| 867 | 872 | if ( isset( $data['url'] ) ) { |
| 868 | 873 | $size = Utils::maybe_convert_size_to_string( $post_id, $size ); |
| @@ -1012,8 +1017,11 @@ | ||
| 1012 | 1017 | $is_image = wp_attachment_is_image($attachment_id); |
| 1013 | 1018 | $existing = $wpmcsItem->get($attachment_id, 'media_library'); |
| 1014 | 1019 | $existing_extras = $wpmcsItem->get_extras($attachment_id, false, 'media_library'); |
| 1015 | 1020 | $has_existing = !Utils::is_empty($existing); |
| 1021 | + // A reupload of an already-private item must keep writing under the private path — | |
| 1022 | + // never true for a genuinely new item, which is exactly right (new uploads stay public). | |
| 1023 | + $is_private = $has_existing && !empty($existing['is_private']); | |
| 1016 | 1024 | $sizes = []; |
| 1017 | 1025 | $uploaded = []; |
| 1018 | 1026 | $extras = []; |
| 1019 | 1027 | $prefix = ''; |
| @@ -1049,24 +1057,37 @@ | ||
| 1049 | 1057 | return false; |
| 1050 | 1058 | } |
| 1051 | 1059 | |
| 1052 | 1060 | // Upload main file first, if that fails no need to attempt the rest |
| 1053 | - $uploaded = $this->uploadFullFile($file_path, $source_path, $do_reupload, $has_existing, $existing, $prefix, $attachment_id); | |
| 1061 | + $uploaded = $this->uploadFullFile($file_path, $source_path, $do_reupload, $has_existing, $existing, $prefix, $attachment_id, $is_private); | |
| 1054 | 1062 | |
| 1055 | 1063 | if ($uploaded && isset($uploaded['success']) && $uploaded['success']) { |
| 1056 | 1064 | // Attempt to upload original file if present. Not critical if this fails so we don't check the result before proceeding. |
| 1057 | - $original = $this->uploadOriginalFile($original_file, $file_dir, $do_reupload, $has_existing, $existing, $prefix, $attachment_id); | |
| 1065 | + $original = $this->uploadOriginalFile($original_file, $file_dir, $do_reupload, $has_existing, $existing, $prefix, $attachment_id, $is_private); | |
| 1058 | 1066 | |
| 1059 | 1067 | if ($is_image && isset($file_dir) && !empty($file_dir)) { |
| 1060 | 1068 | $sizes_to_upload = $this->get_attachment_image_sizes_for_upload($attachment_meta, $attachment_id); |
| 1061 | 1069 | if (!empty($sizes_to_upload)) { |
| 1062 | 1070 | // Upload image sizes. Again, not critical if this fails so we don't check the result before proceeding. |
| 1063 | - $sizes = $this->uploadImageSizes($sizes_to_upload, $file_dir, $do_reupload, $existing_extras, $prefix, $attachment_id); | |
| 1071 | + $sizes = $this->uploadImageSizes($sizes_to_upload, $file_dir, $do_reupload, $existing_extras, $prefix, $attachment_id, $is_private); | |
| 1064 | 1072 | } |
| 1065 | 1073 | } |
| 1066 | 1074 | |
| 1067 | 1075 | if (!empty($sizes)) { |
| 1068 | - $extras['sizes'] = $sizes; | |
| 1076 | + // Merge over existing sizes rather than replacing wholesale — a size that | |
| 1077 | + // came back empty from uploadImageSizes() (its own fallback included) must | |
| 1078 | + // never erase a previously-good, unrelated size's record. | |
| 1079 | + $existing_sizes = isset($existing_extras['sizes']) && is_array($existing_extras['sizes']) | |
| 1080 | + ? $existing_extras['sizes'] | |
| 1081 | + : []; | |
| 1082 | + | |
| 1083 | + foreach ($sizes as $size_name => $size_data) { | |
| 1084 | + if (Utils::is_empty($size_data) && isset($existing_sizes[$size_name])) { | |
| 1085 | + unset($sizes[$size_name]); | |
| 1086 | + } | |
| 1087 | + } | |
| 1088 | + | |
| 1089 | + $extras['sizes'] = array_merge($existing_sizes, $sizes); | |
| 1069 | 1090 | } |
| 1070 | 1091 | |
| 1071 | 1092 | return [ |
| 1072 | 1093 | 'file' => [ |
| @@ -1180,16 +1201,19 @@ | ||
| 1180 | 1201 | * Upload the main/full file. |
| 1181 | 1202 | * @since 1.3.6 |
| 1182 | 1203 | * @param string $file_path |
| 1183 | 1204 | * @param string $source_path |
| 1184 | - * @param bool $do_reupload | |
| 1205 | + * @param bool|array $do_reupload Reupload everything (true), nothing (false), or only these sizes (array of size names). | |
| 1185 | 1206 | * @param bool $has_existing |
| 1186 | 1207 | * @param array $existing |
| 1187 | 1208 | * @param string $prefix |
| 1188 | 1209 | * @param int $attachment_id |
| 1210 | + * @param bool $is_private Keeps a reupload of an already-private item under the private path. | |
| 1189 | 1211 | */ |
| 1190 | - private function uploadFullFile($file_path, $source_path, $do_reupload, $has_existing, $existing, $prefix, $attachment_id) { | |
| 1191 | - if (!$do_reupload && $has_existing && ($existing['source_path'] == $source_path)) { | |
| 1212 | + private function uploadFullFile($file_path, $source_path, $do_reupload, $has_existing, $existing, $prefix, $attachment_id, $is_private = false) { | |
| 1213 | + // $do_reupload may be an array of size names (force just those sizes) — that | |
| 1214 | + // shouldn't force the full file too, only a literal true does. | |
| 1215 | + if ($do_reupload !== true && $has_existing && ($existing['source_path'] == $source_path)) { | |
| 1192 | 1216 | return [ |
| 1193 | 1217 | 'success' => true, |
| 1194 | 1218 | 'file_url' => $existing['url'], |
| 1195 | 1219 | 'key' => $existing['key'] |
| @@ -1196,9 +1220,9 @@ | ||
| 1196 | 1220 | ]; |
| 1197 | 1221 | } |
| 1198 | 1222 | |
| 1199 | 1223 | if (file_exists($file_path)) { |
| 1200 | - return Service::instance()->uploadSingle($file_path, $source_path, $prefix); | |
| 1224 | + return Service::instance()->uploadSingle($file_path, $source_path, $prefix, $is_private); | |
| 1201 | 1225 | } |
| 1202 | 1226 | |
| 1203 | 1227 | Logger::instance()->add_log('sync_to_cloud', $attachment_id, 'media_library', [ |
| 1204 | 1228 | 'message' => __('File not found', 'media-cloud-sync'), |
| @@ -1213,15 +1237,16 @@ | ||
| 1213 | 1237 | * Upload original file when present. |
| 1214 | 1238 | * @since 1.3.6 |
| 1215 | 1239 | * @param string $original_file |
| 1216 | 1240 | * @param string $file_dir |
| 1217 | - * @param bool $do_reupload | |
| 1241 | + * @param bool|array $do_reupload Reupload everything (true), nothing (false), or only these sizes (array of size names). | |
| 1218 | 1242 | * @param bool $has_existing |
| 1219 | 1243 | * @param array $existing |
| 1220 | 1244 | * @param string $prefix |
| 1221 | 1245 | * @param int $attachment_id |
| 1246 | + * @param bool $is_private Keeps a reupload of an already-private item under the private path. | |
| 1222 | 1247 | */ |
| 1223 | - private function uploadOriginalFile($original_file, $file_dir, $do_reupload, $has_existing, $existing, $prefix, $attachment_id) { | |
| 1248 | + private function uploadOriginalFile($original_file, $file_dir, $do_reupload, $has_existing, $existing, $prefix, $attachment_id, $is_private = false) { | |
| 1224 | 1249 | $original = []; |
| 1225 | 1250 | if (empty($original_file)) { |
| 1226 | 1251 | return $original; |
| 1227 | 1252 | } |
| @@ -1228,14 +1253,16 @@ | ||
| 1228 | 1253 | |
| 1229 | 1254 | $original_file_path = trailingslashit($file_dir) . $original_file; |
| 1230 | 1255 | $original_file_source_path = Utils::get_attachment_source_path($original_file_path); |
| 1231 | 1256 | |
| 1232 | - if (!$do_reupload && $has_existing && isset($existing['original_source_path']) && !Utils::is_empty($existing['original_source_path']) && $existing['original_source_path'] === $original_file_source_path) { | |
| 1257 | + // $do_reupload may be an array of size names (force just those sizes) — that | |
| 1258 | + // shouldn't force the original file too, only a literal true does. | |
| 1259 | + if ($do_reupload !== true && $has_existing && isset($existing['original_source_path']) && !Utils::is_empty($existing['original_source_path']) && $existing['original_source_path'] === $original_file_source_path) { | |
| 1233 | 1260 | return [ 'source_path' => $original_file_source_path, 'key' => $existing['original_key'] ]; |
| 1234 | 1261 | } |
| 1235 | 1262 | |
| 1236 | 1263 | if (file_exists($original_file_path)) { |
| 1237 | - $uploaded_original = Service::instance()->uploadSingle($original_file_path, $original_file_source_path, $prefix); | |
| 1264 | + $uploaded_original = Service::instance()->uploadSingle($original_file_path, $original_file_source_path, $prefix, $is_private); | |
| 1238 | 1265 | if (isset($uploaded_original['success']) && $uploaded_original['success']) { |
| 1239 | 1266 | return [ 'source_path' => $original_file_source_path, 'key' => $uploaded_original['key'] ]; |
| 1240 | 1267 | } |
| 1241 | 1268 | |
| @@ -1261,15 +1288,16 @@ | ||
| 1261 | 1288 | * Upload intermediate image sizes. |
| 1262 | 1289 | * @since 1.3.6 |
| 1263 | 1290 | * @param array $attachment_sizes |
| 1264 | 1291 | * @param string $file_dir |
| 1265 | - * @param bool $do_reupload | |
| 1292 | + * @param bool|array $do_reupload Reupload everything (true), nothing (false), or only these sizes (array of size names). | |
| 1266 | 1293 | * @param array $existing_extras |
| 1267 | 1294 | * @param string $prefix |
| 1268 | 1295 | * @param int $attachment_id |
| 1269 | - * @return array | |
| 1296 | + * @param bool $is_private Keeps a reupload of an already-private item under the private path. | |
| 1297 | + * @return array | |
| 1270 | 1298 | */ |
| 1271 | - private function uploadImageSizes($attachment_sizes, $file_dir, $do_reupload, $existing_extras, $prefix, $attachment_id) { | |
| 1299 | + private function uploadImageSizes($attachment_sizes, $file_dir, $do_reupload, $existing_extras, $prefix, $attachment_id, $is_private = false) { | |
| 1272 | 1300 | $sizes = []; |
| 1273 | 1301 | |
| 1274 | 1302 | foreach ($attachment_sizes as $size => $sub_image) { |
| 1275 | 1303 | $sub_size = []; |
| @@ -1283,10 +1311,14 @@ | ||
| 1283 | 1311 | } |
| 1284 | 1312 | |
| 1285 | 1313 | $uploaded_sub_image = []; |
| 1286 | 1314 | |
| 1315 | + // $do_reupload may be an array naming specific sizes to force (e.g. a | |
| 1316 | + // WooCommerce on-the-fly regen of one size only) rather than a plain bool. | |
| 1317 | + $force_this_size = ($do_reupload === true) || (is_array($do_reupload) && in_array($size, $do_reupload, true)); | |
| 1318 | + | |
| 1287 | 1319 | if ( |
| 1288 | - !$do_reupload && | |
| 1320 | + !$force_this_size && | |
| 1289 | 1321 | !Utils::is_empty($existing_extras) && |
| 1290 | 1322 | isset($existing_extras['sizes']) && !Utils::is_empty($existing_extras['sizes']) && |
| 1291 | 1323 | isset($existing_extras['sizes'][$size]) && !Utils::is_empty($existing_extras['sizes'][$size]) && |
| 1292 | 1324 | $existing_extras['sizes'][$size]['source_path'] == $sub_file_source_path |
| @@ -1296,9 +1328,9 @@ | ||
| 1296 | 1328 | 'file_url' => $existing_extras['sizes'][$size]['url'], |
| 1297 | 1329 | 'key' => $existing_extras['sizes'][$size]['key'] |
| 1298 | 1330 | ]; |
| 1299 | 1331 | } else if (file_exists($sub_file_path)) { |
| 1300 | - $uploaded_sub_image = Service::instance()->uploadSingle($sub_file_path, $sub_file_source_path, $prefix); | |
| 1332 | + $uploaded_sub_image = Service::instance()->uploadSingle($sub_file_path, $sub_file_source_path, $prefix, $is_private); | |
| 1301 | 1333 | } else { |
| 1302 | 1334 | Logger::instance()->add_log('sync_to_cloud', $attachment_id, 'media_library', [ |
| 1303 | 1335 | /* translators: %1$s: Image size name (e.g., thumbnail, medium, large). */ |
| 1304 | 1336 | 'message' => sprintf(__('Image size %1$s not found', 'media-cloud-sync'), $size), |
| @@ -1321,8 +1353,14 @@ | ||
| 1321 | 1353 | 'file' => $sub_file_path, |
| 1322 | 1354 | 'code' => 500 |
| 1323 | 1355 | ]); |
| 1324 | 1356 | } |
| 1357 | + | |
| 1358 | + // A failed (re)upload attempt shouldn't erase a previously-good record — | |
| 1359 | + // fall back to what's already stored rather than overwriting it with empty data. | |
| 1360 | + if (isset($existing_extras['sizes'][$size]) && !Utils::is_empty($existing_extras['sizes'][$size])) { | |
| 1361 | + $sub_size = $existing_extras['sizes'][$size]; | |
| 1362 | + } | |
| 1325 | 1363 | } |
| 1326 | 1364 | |
| 1327 | 1365 | $sizes[$size] = $sub_size; |
| 1328 | 1366 | } |
| @@ -1616,8 +1654,26 @@ | ||
| 1616 | 1654 | * Note: delete_post is used as there is a potential that deleted_post is not reached. |
| 1617 | 1655 | */ |
| 1618 | 1656 | public function delete_post() { |
| 1619 | 1657 | $this->deleting_attachment = false; |
| 1658 | + } | |
| 1659 | + | |
| 1660 | + /** | |
| 1661 | + * Safety net for deletion that bypasses wp_delete_attachment(). | |
| 1662 | + * | |
| 1663 | + * @handles deleted_post | |
| 1664 | + */ | |
| 1665 | + public function deleted_post( $post_id, $post ) { | |
| 1666 | + if ( ! $post || 'attachment' !== $post->post_type ) { | |
| 1667 | + return; | |
| 1668 | + } | |
| 1669 | + | |
| 1670 | + $item = Item::instance()->get( $post_id, 'media_library' ); | |
| 1671 | + if ( Utils::is_empty( $item ) ) { | |
| 1672 | + return; | |
| 1673 | + } | |
| 1674 | + | |
| 1675 | + Item::instance()->delete( $post_id, 'media_library' ); | |
| 1620 | 1676 | } |
| 1621 | 1677 | |
| 1622 | 1678 | /** |
| 1623 | 1679 | * Has WP Core fixed wp_check_filetype when URL has params yet? |