| @@ -5,8 +5,15 @@ | ||
| 5 | 5 | |
| 6 | 6 | use Exception; |
| 7 | 7 | use WP_Error; |
| 8 | 8 | |
| 9 | +/** | |
| 10 | + * Media Library integration. | |
| 11 | + * | |
| 12 | + * Loaded by the autoloader; activation is gated via `is_installed()` in Integration::init(). | |
| 13 | + * | |
| 14 | + * @since 1.0.0 | |
| 15 | + */ | |
| 9 | 16 | class MediaLibrary { |
| 10 | 17 | private static $instance = null; |
| 11 | 18 | private $assets_url; |
| 12 | 19 | private $version; |
| @@ -24,10 +31,13 @@ | ||
| 24 | 31 | private $deleting_attachment = false; |
| 25 | 32 | |
| 26 | 33 | |
| 27 | 34 | public static $source_type_prefix = "media"; |
| 28 | - public static $label = 'Media Library'; | |
| 29 | 35 | |
| 36 | + public static function get_label() { | |
| 37 | + return __('Media Library', 'media-cloud-sync'); | |
| 38 | + } | |
| 39 | + | |
| 30 | 40 | // Map which meta to be updated |
| 31 | 41 | public static $source_types = [ |
| 32 | 42 | "media_library" => [ |
| 33 | 43 | 'table' => 'posts', |
| @@ -106,8 +116,10 @@ | ||
| 106 | 116 | add_filter( 'wp_update_attachment_metadata', [ $this, 'update_attachment_metadata' ], 110, 2 ); |
| 107 | 117 | add_filter( 'pre_delete_attachment', [ $this, 'pre_delete_attachment' ], 20 ); |
| 108 | 118 | add_filter( 'delete_attachment', [ $this, 'delete_attachment' ], 20 ); |
| 109 | 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 ); | |
| 110 | 122 | add_filter( 'update_attached_file', [ $this, 'update_attached_file' ], 100, 2 ); |
| 111 | 123 | |
| 112 | 124 | add_action( 'wpmcs_do_update_attachment_metadata', [ $this, 'update_attachment_metadata' ], 10, 2 ); |
| 113 | 125 | } |
| @@ -159,12 +171,8 @@ | ||
| 159 | 171 | * @since 1.0.0 |
| 160 | 172 | * |
| 161 | 173 | */ |
| 162 | 174 | public function delete_attachment($attachment_id){ |
| 163 | - if (!Utils::is_service_enabled()) { | |
| 164 | - return $attachment_id; | |
| 165 | - } | |
| 166 | - | |
| 167 | 175 | $wpmcsItem = Item::instance(); |
| 168 | 176 | $item = $wpmcsItem->get($attachment_id, 'media_library'); |
| 169 | 177 | |
| 170 | 178 | if (Utils::is_empty($item)) { |
| @@ -187,8 +195,13 @@ | ||
| 187 | 195 | */ |
| 188 | 196 | public function update_attachment_metadata($attachment_meta, $attachment_id) { |
| 189 | 197 | // Remove Logs |
| 190 | 198 | Logger::instance()->remove_log('sync_to_cloud', $attachment_id, 'media_library'); |
| 199 | + | |
| 200 | + // Reachable from three independent, uncoordinated triggers (WP's own | |
| 201 | + // wp_update_attachment_metadata hook, this plugin's bulk sync, Imagify's re-sync). | |
| 202 | + // No lock here — Item::add()'s upsert is what keeps a race safe; skipping instead | |
| 203 | + // would risk dropping a real re-upload request. | |
| 191 | 204 | try { |
| 192 | 205 | // Ensure attachment is eligible for upload |
| 193 | 206 | $attachment_meta = is_array($attachment_meta) && !empty($attachment_meta) |
| 194 | 207 | ? $attachment_meta |
| @@ -375,9 +388,9 @@ | ||
| 375 | 388 | 'key' => $backup['key'], |
| 376 | 389 | 'original_source_path' => $backup['original_source_path'], |
| 377 | 390 | 'original_key' => $backup['original_key'], |
| 378 | 391 | ], |
| 379 | - 'extra' => Utilsmaybe_unserialize($backup['extra']), | |
| 392 | + 'extra' => Utils::maybe_unserialize($backup['extra']), | |
| 380 | 393 | ]; |
| 381 | 394 | } |
| 382 | 395 | |
| 383 | 396 | if ($delete_existing) { |
| @@ -432,12 +445,8 @@ | ||
| 432 | 445 | * @since 1.0.0 |
| 433 | 446 | * @return string |
| 434 | 447 | */ |
| 435 | 448 | private function filter_unique_filename($filename, $ext, $dir, $post_id = null) { |
| 436 | - if (!Utils::is_service_enabled()) { | |
| 437 | - return $filename; | |
| 438 | - } | |
| 439 | - | |
| 440 | 449 | // sanitize the file name before we begin processing |
| 441 | 450 | $filename = sanitize_file_name($filename); |
| 442 | 451 | $ext = strtolower($ext); |
| 443 | 452 | $name = wp_basename($filename, $ext); |
| @@ -552,9 +561,9 @@ | ||
| 552 | 561 | if ( $this->deleting_attachment ) { |
| 553 | 562 | return $file; |
| 554 | 563 | } |
| 555 | 564 | |
| 556 | - if (!Utils::is_ok_to_serve($attachment_id)) { | |
| 565 | + if (!Utils::should_serve_from_cloud($attachment_id)) { | |
| 557 | 566 | return $file; |
| 558 | 567 | } |
| 559 | 568 | |
| 560 | 569 | $wpmcsItem = Item::instance(); |
| @@ -607,9 +616,9 @@ | ||
| 607 | 616 | return $sources; |
| 608 | 617 | } |
| 609 | 618 | |
| 610 | 619 | // Must need $attachment_id other wise not possible to get data from the table |
| 611 | - if (!Utils::is_ok_to_serve($attachment_id)) { | |
| 620 | + if (!Utils::should_serve_from_cloud($attachment_id)) { | |
| 612 | 621 | return $sources; |
| 613 | 622 | } |
| 614 | 623 | |
| 615 | 624 | $wpmcsItem = Item::instance(); |
| @@ -642,22 +651,26 @@ | ||
| 642 | 651 | * |
| 643 | 652 | * @return null|string |
| 644 | 653 | */ |
| 645 | 654 | protected function find_image_size_from_width( $meta, $width, $filename ) { |
| 646 | - $sizes = $meta['sizes']; | |
| 647 | - foreach ( $sizes as $name => $size ) { | |
| 648 | - if ( $width === absint( $size['width'] ) && $size['file'] === $filename ) { | |
| 649 | - return $name; | |
| 655 | + if ( ! is_array( $meta ) ) { | |
| 656 | + return null; | |
| 657 | + } | |
| 658 | + | |
| 659 | + if ( ! empty( $meta['sizes'] ) && is_array( $meta['sizes'] ) ) { | |
| 660 | + foreach ( $meta['sizes'] as $name => $size ) { | |
| 661 | + if ( $width === absint( $size['width'] ?? 0 ) && isset( $size['file'] ) && $size['file'] === $filename ) { | |
| 662 | + return $name; | |
| 663 | + } | |
| 650 | 664 | } |
| 651 | 665 | } |
| 652 | 666 | |
| 653 | - // Check for full size | |
| 654 | - if($width === $meta['width']) { | |
| 655 | - $full_file = wp_basename( $meta['file'] ); | |
| 656 | - if($full_file === $filename) { | |
| 657 | - return 'full'; | |
| 658 | - } | |
| 659 | - } | |
| 667 | + if ( isset( $meta['width'], $meta['file'] ) && $width === absint( $meta['width'] ) ) { | |
| 668 | + $full_file = wp_basename( $meta['file'] ); | |
| 669 | + if ( $full_file === $filename ) { | |
| 670 | + return 'full'; | |
| 671 | + } | |
| 672 | + } | |
| 660 | 673 | |
| 661 | 674 | return null; |
| 662 | 675 | } |
| 663 | 676 | |
| @@ -672,10 +685,10 @@ | ||
| 672 | 685 | * @return array |
| 673 | 686 | */ |
| 674 | 687 | public function wp_get_attachment_image_attributes($attr, $attachment, $size='thumbnail') { |
| 675 | 688 | if ( |
| 676 | - !$attachment || | |
| 677 | - !Utils::is_ok_to_serve($attachment->ID) | |
| 689 | + !$attachment || | |
| 690 | + !Utils::should_serve_from_cloud($attachment->ID) | |
| 678 | 691 | ) { |
| 679 | 692 | return $attr; |
| 680 | 693 | } |
| 681 | 694 | |
| @@ -722,9 +735,9 @@ | ||
| 722 | 735 | * |
| 723 | 736 | * @return bool|mixed|WP_Error |
| 724 | 737 | */ |
| 725 | 738 | public function wp_get_attachment_url($url, $attachment_id) { |
| 726 | - if (!Utils::is_ok_to_serve($attachment_id)) { | |
| 739 | + if (!Utils::should_serve_from_cloud($attachment_id)) { | |
| 727 | 740 | return $url; |
| 728 | 741 | } |
| 729 | 742 | |
| 730 | 743 | $new_url = Item::instance()->get_url($attachment_id, 'full', 'media_library'); |
| @@ -749,14 +762,14 @@ | ||
| 749 | 762 | * @param string $size |
| 750 | 763 | * |
| 751 | 764 | * @return string |
| 752 | 765 | */ |
| 753 | - public function get_image_tag( $html, $id, $alt, $title, $align, $size ) { | |
| 766 | + public function get_image_tag( $html, $id, $alt = '', $title = '', $align = '', $size = '' ) { | |
| 754 | 767 | if ( ! is_string( $html ) ) { |
| 755 | 768 | return $html; |
| 756 | 769 | } |
| 757 | 770 | |
| 758 | - if (!Utils::is_ok_to_serve($id)) { | |
| 771 | + if (!Utils::should_serve_from_cloud($id)) { | |
| 759 | 772 | return $html; |
| 760 | 773 | } |
| 761 | 774 | |
| 762 | 775 | preg_match( '@\ssrc=[\'\"]([^\'\"]*)[\'\"]@', $html, $matches ); |
| @@ -765,8 +778,16 @@ | ||
| 765 | 778 | // Can't establish img src |
| 766 | 779 | return $html; |
| 767 | 780 | } |
| 768 | 781 | |
| 782 | + if ( empty( $size ) && preg_match( '/\bsize-([^\s"\']+)/', $html, $size_match ) ) { | |
| 783 | + $size = $size_match[1]; | |
| 784 | + } | |
| 785 | + | |
| 786 | + if ( empty( $size ) ) { | |
| 787 | + $size = 'full'; | |
| 788 | + } | |
| 789 | + | |
| 769 | 790 | $img_src = $matches[1]; |
| 770 | 791 | $size = Utils::maybe_convert_size_to_string( $id, $size ); |
| 771 | 792 | $new_url = Item::instance()->get_url($id, $size, 'media_library'); |
| 772 | 793 | |
| @@ -788,9 +809,9 @@ | ||
| 788 | 809 | * |
| 789 | 810 | * @return array |
| 790 | 811 | */ |
| 791 | 812 | public function wp_get_attachment_image_src( $image, $attachment_id, $size, $icon ) { |
| 792 | - if (!Utils::is_ok_to_serve($attachment_id)) { | |
| 813 | + if (!Utils::should_serve_from_cloud($attachment_id)) { | |
| 793 | 814 | return $image; |
| 794 | 815 | } |
| 795 | 816 | |
| 796 | 817 | if ( isset( $image[0] ) ) { |
| @@ -816,9 +837,9 @@ | ||
| 816 | 837 | * |
| 817 | 838 | * @return array |
| 818 | 839 | */ |
| 819 | 840 | public function wp_prepare_attachment_for_js( $response, $attachment, $meta ) { |
| 820 | - if (!Utils::is_ok_to_serve($attachment->ID)) { | |
| 841 | + if (!Utils::should_serve_from_cloud($attachment->ID)) { | |
| 821 | 842 | return $response; |
| 822 | 843 | } |
| 823 | 844 | |
| 824 | 845 | if ( isset( $response['sizes'] ) && is_array( $response['sizes'] ) ) { |
| @@ -844,9 +865,9 @@ | ||
| 844 | 865 | * |
| 845 | 866 | * @return array |
| 846 | 867 | */ |
| 847 | 868 | public function image_get_intermediate_size( $data, $post_id, $size ) { |
| 848 | - if (!Utils::is_ok_to_serve($post_id)) { | |
| 869 | + if (!Utils::should_serve_from_cloud($post_id)) { | |
| 849 | 870 | return $data; |
| 850 | 871 | } |
| 851 | 872 | if ( isset( $data['url'] ) ) { |
| 852 | 873 | $size = Utils::maybe_convert_size_to_string( $post_id, $size ); |
| @@ -996,8 +1017,11 @@ | ||
| 996 | 1017 | $is_image = wp_attachment_is_image($attachment_id); |
| 997 | 1018 | $existing = $wpmcsItem->get($attachment_id, 'media_library'); |
| 998 | 1019 | $existing_extras = $wpmcsItem->get_extras($attachment_id, false, 'media_library'); |
| 999 | 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']); | |
| 1000 | 1024 | $sizes = []; |
| 1001 | 1025 | $uploaded = []; |
| 1002 | 1026 | $extras = []; |
| 1003 | 1027 | $prefix = ''; |
| @@ -1008,10 +1032,11 @@ | ||
| 1008 | 1032 | $do_reupload = apply_filters('wpmcs_do_reupload_media', false, $attachment_id, 'media_library'); |
| 1009 | 1033 | |
| 1010 | 1034 | // Force reupload when sizes/dimensions changed |
| 1011 | 1035 | if (!$do_reupload && $has_existing) { |
| 1012 | - if($this->shouldForceReupload($attachment_meta, $attachment_id, $existing_extras)) { | |
| 1013 | - $do_reupload = true; | |
| 1036 | + $force_reupload = $this->shouldForceReupload($attachment_meta, $attachment_id, $existing_extras); | |
| 1037 | + if ($force_reupload !== false) { | |
| 1038 | + $do_reupload = $force_reupload; | |
| 1014 | 1039 | } |
| 1015 | 1040 | } |
| 1016 | 1041 | |
| 1017 | 1042 | // Add prefix if object versioning is ON |
| @@ -1027,37 +1052,54 @@ | ||
| 1027 | 1052 | $extras['width'] = (isset($attachment_meta['width']) && !empty($attachment_meta['width'])) ? $attachment_meta['width'] : 0; |
| 1028 | 1053 | $extras['height'] = (isset($attachment_meta['height']) && !empty($attachment_meta['height'])) ? $attachment_meta['height'] : 0; |
| 1029 | 1054 | } |
| 1030 | 1055 | |
| 1031 | - // Check whether the extension is enabled for uploading | |
| 1056 | + // Excluded by extension settings — an intentional skip, not a sync failure, so no log entry. | |
| 1032 | 1057 | if (!Utils::is_extension_available($file_path)) { |
| 1033 | - Logger::instance()->add_log('sync_to_cloud', $attachment_id, 'media_library', [ | |
| 1034 | - 'message' => __('File extension is not supported', 'media-cloud-sync'), | |
| 1035 | - 'file' => $file_path, | |
| 1036 | - 'code' => 415 | |
| 1037 | - ]); | |
| 1038 | 1058 | return false; |
| 1039 | 1059 | } |
| 1040 | 1060 | |
| 1041 | 1061 | // Upload main file first, if that fails no need to attempt the rest |
| 1042 | - $uploaded = $this->uploadFullFile($file_path, $source_path, $do_reupload, $has_existing, $existing, $prefix, $attachment_id); | |
| 1062 | + $uploaded = $this->uploadFullFile($file_path, $source_path, $do_reupload, $has_existing, $existing, $prefix, $attachment_id, $is_private); | |
| 1043 | 1063 | |
| 1044 | 1064 | if ($uploaded && isset($uploaded['success']) && $uploaded['success']) { |
| 1045 | 1065 | // Attempt to upload original file if present. Not critical if this fails so we don't check the result before proceeding. |
| 1046 | - $original = $this->uploadOriginalFile($original_file, $file_dir, $do_reupload, $has_existing, $existing, $prefix, $attachment_id); | |
| 1066 | + $original = $this->uploadOriginalFile($original_file, $file_dir, $do_reupload, $has_existing, $existing, $prefix, $attachment_id, $is_private); | |
| 1067 | + $additional_files = []; | |
| 1047 | 1068 | |
| 1048 | 1069 | if ($is_image && isset($file_dir) && !empty($file_dir)) { |
| 1049 | 1070 | $sizes_to_upload = $this->get_attachment_image_sizes_for_upload($attachment_meta, $attachment_id); |
| 1050 | 1071 | if (!empty($sizes_to_upload)) { |
| 1051 | 1072 | // Upload image sizes. Again, not critical if this fails so we don't check the result before proceeding. |
| 1052 | - $sizes = $this->uploadImageSizes($sizes_to_upload, $file_dir, $do_reupload, $existing_extras, $prefix, $attachment_id); | |
| 1073 | + $sizes = $this->uploadImageSizes($sizes_to_upload, $file_dir, $do_reupload, $existing_extras, $prefix, $attachment_id, $is_private); | |
| 1053 | 1074 | } |
| 1075 | + $additional_files = $this->uploadAdditionalFiles($attachment_meta, $file_dir, $do_reupload, $has_existing, $existing_extras, $prefix, $attachment_id, $is_private); | |
| 1054 | 1076 | } |
| 1055 | 1077 | |
| 1056 | 1078 | if (!empty($sizes)) { |
| 1057 | - $extras['sizes'] = $sizes; | |
| 1079 | + // Merge over existing sizes rather than replacing wholesale — a size that | |
| 1080 | + // came back empty from uploadImageSizes() (its own fallback included) must | |
| 1081 | + // never erase a previously-good, unrelated size's record. | |
| 1082 | + $existing_sizes = isset($existing_extras['sizes']) && is_array($existing_extras['sizes']) | |
| 1083 | + ? $existing_extras['sizes'] | |
| 1084 | + : []; | |
| 1085 | + | |
| 1086 | + foreach ($sizes as $size_name => $size_data) { | |
| 1087 | + if (Utils::is_empty($size_data) && isset($existing_sizes[$size_name])) { | |
| 1088 | + unset($sizes[$size_name]); | |
| 1089 | + } | |
| 1090 | + } | |
| 1091 | + | |
| 1092 | + $extras['sizes'] = array_merge($existing_sizes, $sizes); | |
| 1058 | 1093 | } |
| 1059 | 1094 | |
| 1095 | + if (!empty($additional_files)) { | |
| 1096 | + $existing_additional_files = isset($existing_extras['additional_files']) && is_array($existing_extras['additional_files']) | |
| 1097 | + ? $existing_extras['additional_files'] | |
| 1098 | + : []; | |
| 1099 | + $extras['additional_files'] = array_merge($existing_additional_files, $additional_files); | |
| 1100 | + } | |
| 1101 | + | |
| 1060 | 1102 | return [ |
| 1061 | 1103 | 'file' => [ |
| 1062 | 1104 | 'source_path' => $source_path, |
| 1063 | 1105 | 'url' => $uploaded['file_url'], |
| @@ -1069,12 +1111,13 @@ | ||
| 1069 | 1111 | ]; |
| 1070 | 1112 | } |
| 1071 | 1113 | |
| 1072 | 1114 | if (isset($uploaded['success']) && !$uploaded['success']) { |
| 1115 | + // Prefer the service's own error message/code when it provided one. | |
| 1073 | 1116 | Logger::instance()->add_log('sync_to_cloud', $attachment_id, 'media_library', [ |
| 1074 | - 'message' => __('File upload failed', 'media-cloud-sync'), | |
| 1117 | + 'message' => $uploaded['message'] ?? __('File upload failed', 'media-cloud-sync'), | |
| 1075 | 1118 | 'file' => $file_path, |
| 1076 | - 'code' => 500 | |
| 1119 | + 'code' => $uploaded['code'] ?? 500 | |
| 1077 | 1120 | ]); |
| 1078 | 1121 | } |
| 1079 | 1122 | |
| 1080 | 1123 | return false; |
| @@ -1110,8 +1153,10 @@ | ||
| 1110 | 1153 | * @since 1.3.6 |
| 1111 | 1154 | * @param array $attachment_meta |
| 1112 | 1155 | * @param int $attachment_id |
| 1113 | 1156 | * @param array $existing_extras |
| 1157 | + * @return bool|array True forces a full reupload (main file changed); an array of | |
| 1158 | + * size names forces only those sizes; false means nothing changed. | |
| 1114 | 1159 | */ |
| 1115 | 1160 | private function shouldForceReupload($attachment_meta, $attachment_id, $existing_extras) { |
| 1116 | 1161 | $existing_extras = $existing_extras ?? Item::instance()->get_extras($attachment_id, false, 'media_library'); |
| 1117 | 1162 | |
| @@ -1138,31 +1183,26 @@ | ||
| 1138 | 1183 | 'height' => isset($smeta['height']) ? (int)$smeta['height'] : 0, |
| 1139 | 1184 | ]; |
| 1140 | 1185 | } |
| 1141 | 1186 | |
| 1142 | - $existing_relevant = []; | |
| 1143 | - foreach (array_keys($current_sizes) as $sname) { | |
| 1144 | - if (isset($existing_sizes[$sname])) { | |
| 1145 | - $existing_relevant[$sname] = $existing_sizes[$sname]; | |
| 1146 | - } | |
| 1147 | - } | |
| 1148 | - | |
| 1149 | - if (array_keys($existing_relevant) !== array_keys($current_sizes)) { | |
| 1150 | - return true; | |
| 1151 | - } | |
| 1152 | - | |
| 1187 | + // The main file is unchanged (already ruled out above) — a mismatch here is | |
| 1188 | + // limited to specific sizes, so re-send only those instead of forcing a | |
| 1189 | + // blanket reupload that would also re-fetch the main file from local disk | |
| 1190 | + // (which "Remove from server" may have already, correctly, deleted). | |
| 1191 | + $changed_sizes = []; | |
| 1153 | 1192 | foreach ($current_sizes as $sname => $dims) { |
| 1154 | 1193 | if (!isset($existing_sizes[$sname])) { |
| 1155 | - return true; | |
| 1194 | + $changed_sizes[] = $sname; | |
| 1195 | + continue; | |
| 1156 | 1196 | } |
| 1157 | 1197 | $ex_w = isset($existing_sizes[$sname]['width']) ? (int)$existing_sizes[$sname]['width'] : 0; |
| 1158 | 1198 | $ex_h = isset($existing_sizes[$sname]['height']) ? (int)$existing_sizes[$sname]['height'] : 0; |
| 1159 | 1199 | if ($ex_w !== $dims['width'] || $ex_h !== $dims['height']) { |
| 1160 | - return true; | |
| 1200 | + $changed_sizes[] = $sname; | |
| 1161 | 1201 | } |
| 1162 | 1202 | } |
| 1163 | 1203 | |
| 1164 | - return false; | |
| 1204 | + return empty($changed_sizes) ? false : $changed_sizes; | |
| 1165 | 1205 | } |
| 1166 | 1206 | |
| 1167 | 1207 | /** |
| 1168 | 1208 | * Upload the main/full file. |
| @@ -1168,16 +1208,19 @@ | ||
| 1168 | 1208 | * Upload the main/full file. |
| 1169 | 1209 | * @since 1.3.6 |
| 1170 | 1210 | * @param string $file_path |
| 1171 | 1211 | * @param string $source_path |
| 1172 | - * @param bool $do_reupload | |
| 1212 | + * @param bool|array $do_reupload Reupload everything (true), nothing (false), or only these sizes (array of size names). | |
| 1173 | 1213 | * @param bool $has_existing |
| 1174 | 1214 | * @param array $existing |
| 1175 | 1215 | * @param string $prefix |
| 1176 | 1216 | * @param int $attachment_id |
| 1217 | + * @param bool $is_private Keeps a reupload of an already-private item under the private path. | |
| 1177 | 1218 | */ |
| 1178 | - private function uploadFullFile($file_path, $source_path, $do_reupload, $has_existing, $existing, $prefix, $attachment_id) { | |
| 1179 | - if (!$do_reupload && $has_existing && ($existing['source_path'] == $source_path)) { | |
| 1219 | + private function uploadFullFile($file_path, $source_path, $do_reupload, $has_existing, $existing, $prefix, $attachment_id, $is_private = false) { | |
| 1220 | + // $do_reupload may be an array of size names (force just those sizes) — that | |
| 1221 | + // shouldn't force the full file too, only a literal true does. | |
| 1222 | + if ($do_reupload !== true && $has_existing && ($existing['source_path'] == $source_path)) { | |
| 1180 | 1223 | return [ |
| 1181 | 1224 | 'success' => true, |
| 1182 | 1225 | 'file_url' => $existing['url'], |
| 1183 | 1226 | 'key' => $existing['key'] |
| @@ -1184,9 +1227,9 @@ | ||
| 1184 | 1227 | ]; |
| 1185 | 1228 | } |
| 1186 | 1229 | |
| 1187 | 1230 | if (file_exists($file_path)) { |
| 1188 | - return Service::instance()->uploadSingle($file_path, $source_path, $prefix); | |
| 1231 | + return Service::instance()->uploadSingle($file_path, $source_path, $prefix, $is_private); | |
| 1189 | 1232 | } |
| 1190 | 1233 | |
| 1191 | 1234 | Logger::instance()->add_log('sync_to_cloud', $attachment_id, 'media_library', [ |
| 1192 | 1235 | 'message' => __('File not found', 'media-cloud-sync'), |
| @@ -1201,63 +1244,133 @@ | ||
| 1201 | 1244 | * Upload original file when present. |
| 1202 | 1245 | * @since 1.3.6 |
| 1203 | 1246 | * @param string $original_file |
| 1204 | 1247 | * @param string $file_dir |
| 1205 | - * @param bool $do_reupload | |
| 1248 | + * @param bool|array $do_reupload Reupload everything (true), nothing (false), or only these sizes (array of size names). | |
| 1206 | 1249 | * @param bool $has_existing |
| 1207 | 1250 | * @param array $existing |
| 1208 | 1251 | * @param string $prefix |
| 1209 | 1252 | * @param int $attachment_id |
| 1253 | + * @param bool $is_private Keeps a reupload of an already-private item under the private path. | |
| 1210 | 1254 | */ |
| 1211 | - private function uploadOriginalFile($original_file, $file_dir, $do_reupload, $has_existing, $existing, $prefix, $attachment_id) { | |
| 1212 | - $original = []; | |
| 1213 | - if (empty($original_file)) { | |
| 1214 | - return $original; | |
| 1255 | + private function uploadOriginalFile($original_file, $file_dir, $do_reupload, $has_existing, $existing, $prefix, $attachment_id, $is_private = false) { | |
| 1256 | + return $this->uploadNamedFile( | |
| 1257 | + $original_file, $file_dir, | |
| 1258 | + $existing['original_source_path'] ?? '', $existing['original_key'] ?? '', | |
| 1259 | + $do_reupload, $has_existing, $prefix, $attachment_id, $is_private, | |
| 1260 | + __('Original File not found', 'media-cloud-sync') | |
| 1261 | + ); | |
| 1262 | + } | |
| 1263 | + | |
| 1264 | + /** | |
| 1265 | + * Upload one optional named additional file (original image, HEIC source, | |
| 1266 | + * animated-GIF video/poster) that lives alongside the main file. | |
| 1267 | + * @since 1.4.3 | |
| 1268 | + * @param string $filename Relative filename within $file_dir, or empty if absent. | |
| 1269 | + * @param string $file_dir | |
| 1270 | + * @param string $existing_source_path Previously recorded source_path, if any. | |
| 1271 | + * @param string $existing_key Previously recorded cloud key, if any. | |
| 1272 | + * @param bool|array $do_reupload Reupload everything (true), nothing (false), or only these sizes (array of size names). | |
| 1273 | + * @param bool $has_existing | |
| 1274 | + * @param string $prefix | |
| 1275 | + * @param int $attachment_id | |
| 1276 | + * @param bool $is_private Keeps a reupload of an already-private item under the private path. | |
| 1277 | + * @param string $not_found_message | |
| 1278 | + * @return array{source_path?: string, key?: string} Empty when absent or upload failed. | |
| 1279 | + */ | |
| 1280 | + private function uploadNamedFile($filename, $file_dir, $existing_source_path, $existing_key, $do_reupload, $has_existing, $prefix, $attachment_id, $is_private, $not_found_message) { | |
| 1281 | + if (empty($filename)) { | |
| 1282 | + return []; | |
| 1215 | 1283 | } |
| 1216 | 1284 | |
| 1217 | - $original_file_path = trailingslashit($file_dir) . $original_file; | |
| 1218 | - $original_file_source_path = Utils::get_attachment_source_path($original_file_path); | |
| 1285 | + $file_path = trailingslashit($file_dir) . $filename; | |
| 1286 | + $source_path = Utils::get_attachment_source_path($file_path); | |
| 1219 | 1287 | |
| 1220 | - 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) { | |
| 1221 | - return [ 'source_path' => $original_file_source_path, 'key' => $existing['original_key'] ]; | |
| 1288 | + // $do_reupload may be an array of size names (force just those sizes) — that | |
| 1289 | + // shouldn't force this file too, only a literal true does. | |
| 1290 | + if ($do_reupload !== true && $has_existing && !Utils::is_empty($existing_source_path) && $existing_source_path === $source_path) { | |
| 1291 | + return [ 'source_path' => $source_path, 'key' => $existing_key ]; | |
| 1222 | 1292 | } |
| 1223 | 1293 | |
| 1224 | - if (file_exists($original_file_path)) { | |
| 1225 | - $uploaded_original = Service::instance()->uploadSingle($original_file_path, $original_file_source_path, $prefix); | |
| 1226 | - if (isset($uploaded_original['success']) && $uploaded_original['success']) { | |
| 1227 | - return [ 'source_path' => $original_file_source_path, 'key' => $uploaded_original['key'] ]; | |
| 1294 | + if (file_exists($file_path)) { | |
| 1295 | + $uploaded = Service::instance()->uploadSingle($file_path, $source_path, $prefix, $is_private); | |
| 1296 | + if (isset($uploaded['success']) && $uploaded['success']) { | |
| 1297 | + return [ 'source_path' => $source_path, 'key' => $uploaded['key'] ]; | |
| 1228 | 1298 | } |
| 1229 | 1299 | |
| 1230 | - if (isset($uploaded_original['success']) && !$uploaded_original['success']) { | |
| 1300 | + if (isset($uploaded['success']) && !$uploaded['success']) { | |
| 1231 | 1301 | Logger::instance()->add_log('sync_to_cloud', $attachment_id, 'media_library', [ |
| 1232 | - 'message' => __('Original File upload failed', 'media-cloud-sync'), | |
| 1233 | - 'file' => $original_file_path, | |
| 1234 | - 'code' => 500 | |
| 1302 | + 'message' => $uploaded['message'] ?? $not_found_message, | |
| 1303 | + 'file' => $file_path, | |
| 1304 | + 'code' => $uploaded['code'] ?? 500 | |
| 1235 | 1305 | ]); |
| 1236 | 1306 | } |
| 1237 | 1307 | } else { |
| 1238 | 1308 | Logger::instance()->add_log('sync_to_cloud', $attachment_id, 'media_library', [ |
| 1239 | - 'message' => __('Original File not found', 'media-cloud-sync'), | |
| 1240 | - 'file' => $original_file_path, | |
| 1309 | + 'message' => $not_found_message, | |
| 1310 | + 'file' => $file_path, | |
| 1241 | 1311 | 'code' => 404 |
| 1242 | 1312 | ]); |
| 1243 | 1313 | } |
| 1244 | 1314 | |
| 1245 | - return $original; | |
| 1315 | + return []; | |
| 1246 | 1316 | } |
| 1247 | 1317 | |
| 1248 | 1318 | /** |
| 1319 | + * Upload the WP 7.1 client-side-processing additional files (HEIC source, | |
| 1320 | + * animated-GIF video/poster) that uploadMedia() otherwise has no idea exist. | |
| 1321 | + * @since 1.4.3 | |
| 1322 | + * @param array $attachment_meta | |
| 1323 | + * @param string $file_dir | |
| 1324 | + * @param bool|array $do_reupload | |
| 1325 | + * @param bool $has_existing | |
| 1326 | + * @param array $existing_extras | |
| 1327 | + * @param string $prefix | |
| 1328 | + * @param int $attachment_id | |
| 1329 | + * @param bool $is_private | |
| 1330 | + * @return array Keyed by meta key ('source_image', 'animated_video', 'animated_video_poster'). | |
| 1331 | + */ | |
| 1332 | + private function uploadAdditionalFiles($attachment_meta, $file_dir, $do_reupload, $has_existing, $existing_extras, $prefix, $attachment_id, $is_private) { | |
| 1333 | + $additional_file_keys = [ | |
| 1334 | + 'source_image' => __('HEIC source file not found', 'media-cloud-sync'), | |
| 1335 | + 'animated_video' => __('Animated video file not found', 'media-cloud-sync'), | |
| 1336 | + 'animated_video_poster' => __('Animated video poster file not found', 'media-cloud-sync'), | |
| 1337 | + ]; | |
| 1338 | + $existing_additional_files = isset($existing_extras['additional_files']) && is_array($existing_extras['additional_files']) | |
| 1339 | + ? $existing_extras['additional_files'] | |
| 1340 | + : []; | |
| 1341 | + | |
| 1342 | + $additional_files = []; | |
| 1343 | + foreach ($additional_file_keys as $meta_key => $not_found_message) { | |
| 1344 | + if (empty($attachment_meta[$meta_key])) { | |
| 1345 | + continue; | |
| 1346 | + } | |
| 1347 | + $existing_file = $existing_additional_files[$meta_key] ?? []; | |
| 1348 | + $result = $this->uploadNamedFile( | |
| 1349 | + $attachment_meta[$meta_key], $file_dir, | |
| 1350 | + $existing_file['source_path'] ?? '', $existing_file['key'] ?? '', | |
| 1351 | + $do_reupload, $has_existing, $prefix, $attachment_id, $is_private, $not_found_message | |
| 1352 | + ); | |
| 1353 | + if (!empty($result)) { | |
| 1354 | + $additional_files[$meta_key] = $result; | |
| 1355 | + } | |
| 1356 | + } | |
| 1357 | + return $additional_files; | |
| 1358 | + } | |
| 1359 | + | |
| 1360 | + /** | |
| 1249 | 1361 | * Upload intermediate image sizes. |
| 1250 | 1362 | * @since 1.3.6 |
| 1251 | 1363 | * @param array $attachment_sizes |
| 1252 | 1364 | * @param string $file_dir |
| 1253 | - * @param bool $do_reupload | |
| 1365 | + * @param bool|array $do_reupload Reupload everything (true), nothing (false), or only these sizes (array of size names). | |
| 1254 | 1366 | * @param array $existing_extras |
| 1255 | 1367 | * @param string $prefix |
| 1256 | 1368 | * @param int $attachment_id |
| 1257 | - * @return array | |
| 1369 | + * @param bool $is_private Keeps a reupload of an already-private item under the private path. | |
| 1370 | + * @return array | |
| 1258 | 1371 | */ |
| 1259 | - private function uploadImageSizes($attachment_sizes, $file_dir, $do_reupload, $existing_extras, $prefix, $attachment_id) { | |
| 1372 | + private function uploadImageSizes($attachment_sizes, $file_dir, $do_reupload, $existing_extras, $prefix, $attachment_id, $is_private = false) { | |
| 1260 | 1373 | $sizes = []; |
| 1261 | 1374 | |
| 1262 | 1375 | foreach ($attachment_sizes as $size => $sub_image) { |
| 1263 | 1376 | $sub_size = []; |
| @@ -1271,10 +1384,14 @@ | ||
| 1271 | 1384 | } |
| 1272 | 1385 | |
| 1273 | 1386 | $uploaded_sub_image = []; |
| 1274 | 1387 | |
| 1388 | + // $do_reupload may be an array naming specific sizes to force (e.g. a | |
| 1389 | + // WooCommerce on-the-fly regen of one size only) rather than a plain bool. | |
| 1390 | + $force_this_size = ($do_reupload === true) || (is_array($do_reupload) && in_array($size, $do_reupload, true)); | |
| 1391 | + | |
| 1275 | 1392 | if ( |
| 1276 | - !$do_reupload && | |
| 1393 | + !$force_this_size && | |
| 1277 | 1394 | !Utils::is_empty($existing_extras) && |
| 1278 | 1395 | isset($existing_extras['sizes']) && !Utils::is_empty($existing_extras['sizes']) && |
| 1279 | 1396 | isset($existing_extras['sizes'][$size]) && !Utils::is_empty($existing_extras['sizes'][$size]) && |
| 1280 | 1397 | $existing_extras['sizes'][$size]['source_path'] == $sub_file_source_path |
| @@ -1284,9 +1401,9 @@ | ||
| 1284 | 1401 | 'file_url' => $existing_extras['sizes'][$size]['url'], |
| 1285 | 1402 | 'key' => $existing_extras['sizes'][$size]['key'] |
| 1286 | 1403 | ]; |
| 1287 | 1404 | } else if (file_exists($sub_file_path)) { |
| 1288 | - $uploaded_sub_image = Service::instance()->uploadSingle($sub_file_path, $sub_file_source_path, $prefix); | |
| 1405 | + $uploaded_sub_image = Service::instance()->uploadSingle($sub_file_path, $sub_file_source_path, $prefix, $is_private); | |
| 1289 | 1406 | } else { |
| 1290 | 1407 | Logger::instance()->add_log('sync_to_cloud', $attachment_id, 'media_library', [ |
| 1291 | 1408 | /* translators: %1$s: Image size name (e.g., thumbnail, medium, large). */ |
| 1292 | 1409 | 'message' => sprintf(__('Image size %1$s not found', 'media-cloud-sync'), $size), |
| @@ -1309,8 +1426,14 @@ | ||
| 1309 | 1426 | 'file' => $sub_file_path, |
| 1310 | 1427 | 'code' => 500 |
| 1311 | 1428 | ]); |
| 1312 | 1429 | } |
| 1430 | + | |
| 1431 | + // A failed (re)upload attempt shouldn't erase a previously-good record — | |
| 1432 | + // fall back to what's already stored rather than overwriting it with empty data. | |
| 1433 | + if (isset($existing_extras['sizes'][$size]) && !Utils::is_empty($existing_extras['sizes'][$size])) { | |
| 1434 | + $sub_size = $existing_extras['sizes'][$size]; | |
| 1435 | + } | |
| 1313 | 1436 | } |
| 1314 | 1437 | |
| 1315 | 1438 | $sizes[$size] = $sub_size; |
| 1316 | 1439 | } |
| @@ -1341,18 +1464,40 @@ | ||
| 1341 | 1464 | } |
| 1342 | 1465 | |
| 1343 | 1466 | |
| 1344 | 1467 | /** |
| 1468 | + * Whether an attachment is still waiting on WordPress-generated subsizes — same check | |
| 1469 | + * update_attachment_metadata() uses, so callers can tell that apart from a real failure. | |
| 1470 | + * | |
| 1471 | + * @param int $attachment_id | |
| 1472 | + * @return bool | |
| 1473 | + */ | |
| 1474 | + private function is_attachment_not_yet_ready( $attachment_id ) { | |
| 1475 | + if ( ! wp_attachment_is_image( $attachment_id ) ) { | |
| 1476 | + return false; | |
| 1477 | + } | |
| 1478 | + | |
| 1479 | + $attachment_meta = wp_get_attachment_metadata( $attachment_id ); | |
| 1480 | + | |
| 1481 | + return $this->should_wait_for_subsizes( $attachment_meta, $attachment_id ); | |
| 1482 | + } | |
| 1483 | + | |
| 1484 | + /** | |
| 1345 | 1485 | * Upload pending media files. |
| 1346 | 1486 | * |
| 1347 | 1487 | * Processes media attachments that are pending and haven't been synced yet. |
| 1348 | 1488 | * |
| 1489 | + * Paginates by `posts.ID` (a stable cursor) rather than a raw SQL OFFSET, | |
| 1490 | + * so a concurrent single-item retry (which can remove an arbitrary item | |
| 1491 | + * from the middle of the pending set, not just the front) can't desync | |
| 1492 | + * which row gets picked up next. | |
| 1493 | + * | |
| 1349 | 1494 | * @param string $source_type Source type for identifying which source.. |
| 1350 | - * @param int $limit Number of media to process. Default is 50. | |
| 1351 | - * @param int $offset Offset for media query. Default is 0. | |
| 1352 | - * @return array Status array with success and failed media IDs. | |
| 1495 | + * @param int $limit Number of media to process. Default is 50. | |
| 1496 | + * @param int $after_id Only consider attachments with ID greater than this cursor. Default is 0. | |
| 1497 | + * @return array Status array with success, failed count, and the last attachment ID considered. | |
| 1353 | 1498 | */ |
| 1354 | - public function upload_pending_media( $source_type, $limit = 50, $offset = 0 ) { | |
| 1499 | + public function upload_pending_media( $source_type, $limit = 50, $after_id = 0 ) { | |
| 1355 | 1500 | global $wpdb; |
| 1356 | 1501 | |
| 1357 | 1502 | $failed = 0; |
| 1358 | 1503 | |
| @@ -1357,9 +1502,9 @@ | ||
| 1357 | 1502 | $failed = 0; |
| 1358 | 1503 | |
| 1359 | 1504 | $source_type_data = self::$source_types[ $source_type ] ?? false; |
| 1360 | 1505 | if ( ! $source_type_data ) { |
| 1361 | - return [ 'success' => true, 'failed' => $limit ]; | |
| 1506 | + return [ 'success' => true, 'failed' => $limit, 'last_id' => $after_id ]; | |
| 1362 | 1507 | } |
| 1363 | 1508 | |
| 1364 | 1509 | $posts = $wpdb->prefix . $source_type_data['table']; |
| 1365 | 1510 | $items = Db::get_table_name(); |
| @@ -1381,8 +1526,14 @@ | ||
| 1381 | 1526 | $join .= " AND items.region = %s"; |
| 1382 | 1527 | $params[] = $this->region; |
| 1383 | 1528 | } |
| 1384 | 1529 | |
| 1530 | + $after_id_sql = ''; | |
| 1531 | + if ( $after_id > 0 ) { | |
| 1532 | + $after_id_sql = 'AND posts.ID > %d'; | |
| 1533 | + $params[] = (int) $after_id; | |
| 1534 | + } | |
| 1535 | + | |
| 1385 | 1536 | $sql = " |
| 1386 | 1537 | SELECT posts.ID |
| 1387 | 1538 | FROM {$posts} AS posts |
| 1388 | 1539 | LEFT JOIN {$items} AS items USE INDEX (idx_item_lookup) |
| @@ -1388,30 +1539,41 @@ | ||
| 1388 | 1539 | LEFT JOIN {$items} AS items USE INDEX (idx_item_lookup) |
| 1389 | 1540 | ON {$join} |
| 1390 | 1541 | WHERE posts.post_type = 'attachment' |
| 1391 | 1542 | AND items.source_id IS NULL |
| 1543 | + {$after_id_sql} | |
| 1392 | 1544 | ORDER BY posts.ID ASC |
| 1393 | - LIMIT %d OFFSET %d | |
| 1545 | + LIMIT %d | |
| 1394 | 1546 | "; |
| 1395 | 1547 | |
| 1396 | 1548 | $params[] = (int) $limit; |
| 1397 | - $params[] = (int) $offset; | |
| 1398 | 1549 | |
| 1399 | 1550 | $rows = $wpdb->get_results( $wpdb->prepare( $sql, $params ) ); |
| 1400 | 1551 | |
| 1401 | 1552 | if ( empty( $rows ) ) { |
| 1402 | - return [ 'success' => true, 'failed' => 0 ]; | |
| 1553 | + return [ 'success' => true, 'failed' => 0, 'last_id' => $after_id ]; | |
| 1403 | 1554 | } |
| 1404 | 1555 | |
| 1556 | + $last_id = $after_id; | |
| 1405 | 1557 | foreach ( $rows as $row ) { |
| 1558 | + $attachment_id = (int) $row->ID; | |
| 1559 | + | |
| 1560 | + // Still waiting on WordPress to finish generating subsizes — not a failure, | |
| 1561 | + // just not ready yet. Leave $last_id alone so it's reconsidered next tick. | |
| 1562 | + if ( $this->is_attachment_not_yet_ready( $attachment_id ) ) { | |
| 1563 | + break; | |
| 1564 | + } | |
| 1565 | + | |
| 1566 | + $last_id = $attachment_id; | |
| 1567 | + | |
| 1406 | 1568 | try { |
| 1407 | - $this->upload_single_media( (int) $row->ID, $source_type ); | |
| 1569 | + $this->upload_single_media( $attachment_id, $source_type ); | |
| 1408 | 1570 | } catch ( \Throwable $e ) { |
| 1409 | 1571 | $failed++; |
| 1410 | 1572 | continue; |
| 1411 | 1573 | } |
| 1412 | 1574 | |
| 1413 | - if ( ! Item::instance()->get( (int) $row->ID, $source_type ) ) { | |
| 1575 | + if ( ! Item::instance()->get( $attachment_id, $source_type ) ) { | |
| 1414 | 1576 | $failed++; |
| 1415 | 1577 | } |
| 1416 | 1578 | } |
| 1417 | 1579 | |
| @@ -1417,8 +1579,9 @@ | ||
| 1417 | 1579 | |
| 1418 | 1580 | return [ |
| 1419 | 1581 | 'success' => true, |
| 1420 | 1582 | 'failed' => $failed, |
| 1583 | + 'last_id' => $last_id, | |
| 1421 | 1584 | ]; |
| 1422 | 1585 | } |
| 1423 | 1586 | |
| 1424 | 1587 | |
| @@ -1445,9 +1608,9 @@ | ||
| 1445 | 1608 | $provider = $wpmcsItem->get_field($attachment_id, 'provider', 'media_library'); |
| 1446 | 1609 | if($provider) { |
| 1447 | 1610 | $label = Schema::getServiceLabels($provider); ?> |
| 1448 | 1611 | <div class="misc-pub-section misc-pub-provider"> |
| 1449 | - <?php esc_html_e( 'Provider :', 'media-cloud-sync' ); ?> <strong><?php echo esc_textarea(!empty($label) ? $label : $provider); ?></strong></a> | |
| 1612 | + <?php esc_html_e( 'Provider :', 'media-cloud-sync' ); ?> <strong><?php echo esc_textarea(!empty($label) ? $label : $provider); ?></strong> | |
| 1450 | 1613 | </div> |
| 1451 | 1614 | <?php |
| 1452 | 1615 | } |
| 1453 | 1616 | |
| @@ -1453,9 +1616,9 @@ | ||
| 1453 | 1616 | |
| 1454 | 1617 | $region = $wpmcsItem->get_field($attachment_id, 'region', 'media_library'); |
| 1455 | 1618 | if($region) { ?> |
| 1456 | 1619 | <div class="misc-pub-section misc-pub-provider"> |
| 1457 | - <?php esc_html_e( 'Region :', 'media-cloud-sync' ); ?> <strong><?php echo esc_textarea($region); ?></strong></a> | |
| 1620 | + <?php esc_html_e( 'Region :', 'media-cloud-sync' ); ?> <strong><?php echo esc_textarea($region); ?></strong> | |
| 1458 | 1621 | </div> |
| 1459 | 1622 | <?php |
| 1460 | 1623 | } |
| 1461 | 1624 | $private = (int)$wpmcsItem->get_field($attachment_id, 'is_private', 'media_library'); |
| @@ -1460,9 +1623,9 @@ | ||
| 1460 | 1623 | } |
| 1461 | 1624 | $private = (int)$wpmcsItem->get_field($attachment_id, 'is_private', 'media_library'); |
| 1462 | 1625 | ?> |
| 1463 | 1626 | <div class="misc-pub-section misc-pub-provider"> |
| 1464 | - <?php esc_html_e( 'Access :', 'media-cloud-sync' ); ?> <strong><?php $private ? esc_html_e( 'Private', 'media-cloud-sync' ) : esc_html_e( 'Public', 'media-cloud-sync' ); ?></strong></a> | |
| 1627 | + <?php esc_html_e( 'Access :', 'media-cloud-sync' ); ?> <strong><?php $private ? esc_html_e( 'Private', 'media-cloud-sync' ) : esc_html_e( 'Public', 'media-cloud-sync' ); ?></strong> | |
| 1465 | 1628 | </div> |
| 1466 | 1629 | <?php |
| 1467 | 1630 | } |
| 1468 | 1631 | |
| @@ -1468,8 +1631,10 @@ | ||
| 1468 | 1631 | |
| 1469 | 1632 | |
| 1470 | 1633 | /** |
| 1471 | 1634 | * Function to get attachment details by ID |
| 1635 | + * | |
| 1636 | + * @since 1.3.12 Added capability checks for attachment access. | |
| 1472 | 1637 | */ |
| 1473 | 1638 | public function ajax_get_attachment_details() { |
| 1474 | 1639 | $result = array( |
| 1475 | 1640 | 'status' => false, |
| @@ -1476,8 +1641,12 @@ | ||
| 1476 | 1641 | 'data' => array(), |
| 1477 | 1642 | 'exclude' => false |
| 1478 | 1643 | ); |
| 1479 | 1644 | |
| 1645 | + if(!Utils::is_service_enabled()) { | |
| 1646 | + wp_send_json_success( $result ); | |
| 1647 | + } | |
| 1648 | + | |
| 1480 | 1649 | if ( ! isset( $_POST['id'] ) ) { |
| 1481 | 1650 | wp_send_json_success( $result ); |
| 1482 | 1651 | } |
| 1483 | 1652 | |
| @@ -1482,9 +1651,18 @@ | ||
| 1482 | 1651 | } |
| 1483 | 1652 | |
| 1484 | 1653 | check_ajax_referer( 'get_media_provider_details', '_nonce' ); |
| 1485 | 1654 | |
| 1486 | - $id= intval( sanitize_text_field( $_POST['id'] ) ); | |
| 1655 | + if ( ! current_user_can( 'upload_files' ) ) { | |
| 1656 | + wp_send_json_error(); | |
| 1657 | + } | |
| 1658 | + | |
| 1659 | + $id = absint( wp_unslash( $_POST['id'] ) ); | |
| 1660 | + $post = get_post( $id ); | |
| 1661 | + | |
| 1662 | + if ( ! $post || 'attachment' !== $post->post_type || ! current_user_can( 'edit_post', $id ) ) { | |
| 1663 | + wp_send_json_success( $result ); | |
| 1664 | + } | |
| 1487 | 1665 | |
| 1488 | 1666 | // Return if extension not allowed |
| 1489 | 1667 | $path = get_attached_file( $id ); |
| 1490 | 1668 | if(!Utils::is_extension_available($path)) { |
| @@ -1512,9 +1690,9 @@ | ||
| 1512 | 1690 | $region = $wpmcsItem->get_field($id, 'region', 'media_library'); |
| 1513 | 1691 | if($region){ |
| 1514 | 1692 | $item['region'] = $region; |
| 1515 | 1693 | } |
| 1516 | - $item['private'] = $wpmcsItem->get_field($id, 'private', 'media_library'); | |
| 1694 | + $item['private'] = (int) $wpmcsItem->get_field($id, 'is_private', 'media_library'); | |
| 1517 | 1695 | if($item) { |
| 1518 | 1696 | $result= array( |
| 1519 | 1697 | 'status' => true, |
| 1520 | 1698 | 'data' => $item |
| @@ -1549,8 +1727,26 @@ | ||
| 1549 | 1727 | * Note: delete_post is used as there is a potential that deleted_post is not reached. |
| 1550 | 1728 | */ |
| 1551 | 1729 | public function delete_post() { |
| 1552 | 1730 | $this->deleting_attachment = false; |
| 1731 | + } | |
| 1732 | + | |
| 1733 | + /** | |
| 1734 | + * Safety net for deletion that bypasses wp_delete_attachment(). | |
| 1735 | + * | |
| 1736 | + * @handles deleted_post | |
| 1737 | + */ | |
| 1738 | + public function deleted_post( $post_id, $post ) { | |
| 1739 | + if ( ! $post || 'attachment' !== $post->post_type ) { | |
| 1740 | + return; | |
| 1741 | + } | |
| 1742 | + | |
| 1743 | + $item = Item::instance()->get( $post_id, 'media_library' ); | |
| 1744 | + if ( Utils::is_empty( $item ) ) { | |
| 1745 | + return; | |
| 1746 | + } | |
| 1747 | + | |
| 1748 | + Item::instance()->delete( $post_id, 'media_library' ); | |
| 1553 | 1749 | } |
| 1554 | 1750 | |
| 1555 | 1751 | /** |
| 1556 | 1752 | * Has WP Core fixed wp_check_filetype when URL has params yet? |