| @@ -1,8 +1,20 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | 3 | trait Optml_Dam_Offload_Utils { |
| 4 | + use Optml_Normalizer; | |
| 5 | + | |
| 4 | 6 | /** |
| 7 | + * Check if this contains the DAM flag. | |
| 8 | + * | |
| 9 | + * @param string $url The URL to check. | |
| 10 | + * | |
| 11 | + * @return bool | |
| 12 | + */ | |
| 13 | + private function is_dam_url( $url ) { | |
| 14 | + return strpos( $url, Optml_Dam::URL_DAM_FLAG ) !== false; | |
| 15 | + } | |
| 16 | + /** | |
| 5 | 17 | * Checks that the attachment is a DAM image. |
| 6 | 18 | * |
| 7 | 19 | * @param int $post_id The attachment ID. |
| 8 | 20 | * |
| @@ -25,8 +37,10 @@ | ||
| 25 | 37 | * |
| 26 | 38 | * @return bool |
| 27 | 39 | */ |
| 28 | 40 | private function is_legacy_offloaded_attachment( $id ) { |
| 41 | + $id = apply_filters( 'optml_ensure_source_attachment_id', $id ); | |
| 42 | + | |
| 29 | 43 | return ! $this->is_new_offloaded_attachment( $id ) && ! empty( get_post_meta( $id, Optml_Media_Offload::META_KEYS['offloaded'] ) ); |
| 30 | 44 | } |
| 31 | 45 | |
| 32 | 46 | /** |
| @@ -36,8 +50,10 @@ | ||
| 36 | 50 | * |
| 37 | 51 | * @return bool |
| 38 | 52 | */ |
| 39 | 53 | private function is_new_offloaded_attachment( $id ) { |
| 54 | + $id = apply_filters( 'optml_ensure_source_attachment_id', $id ); | |
| 55 | + | |
| 40 | 56 | return ! empty( get_post_meta( $id, Optml_Media_Offload::OM_OFFLOADED_FLAG, true ) ); |
| 41 | 57 | } |
| 42 | 58 | |
| 43 | 59 | /** |
| @@ -42,9 +58,9 @@ | ||
| 42 | 58 | |
| 43 | 59 | /** |
| 44 | 60 | * Get all registered image sizes. |
| 45 | 61 | * |
| 46 | - * @return array | |
| 62 | + * @return array<string, array{width: int, height: int, crop: bool|array<int, string>}> | |
| 47 | 63 | */ |
| 48 | 64 | private function get_all_image_sizes() { |
| 49 | 65 | $additional_sizes = wp_get_additional_image_sizes(); |
| 50 | 66 | $intermediate = get_intermediate_image_sizes(); |
| @@ -80,14 +96,14 @@ | ||
| 80 | 96 | * Get the dimension from optimized url. |
| 81 | 97 | * |
| 82 | 98 | * @param string $url The image url. |
| 83 | 99 | * |
| 84 | - * @return array Contains the width and height values in this order. | |
| 100 | + * @return array{0: string|false, 1: string|false} Contains the width and height values in this order. | |
| 85 | 101 | */ |
| 86 | 102 | private function parse_dimension_from_optimized_url( $url ) { |
| 87 | 103 | $catch = []; |
| 88 | - $height = 'auto'; | |
| 89 | - $width = 'auto'; | |
| 104 | + $height = false; | |
| 105 | + $width = false; | |
| 90 | 106 | preg_match( '/\/w:(.*)\/h:(.*)\/q:/', $url, $catch ); |
| 91 | 107 | if ( isset( $catch[1] ) && isset( $catch[2] ) ) { |
| 92 | 108 | $width = $catch[1]; |
| 93 | 109 | $height = $catch[2]; |
| @@ -146,15 +162,14 @@ | ||
| 146 | 162 | |
| 147 | 163 | /** |
| 148 | 164 | * Used to filter the image metadata. Adds optimized image url for all image sizes. |
| 149 | 165 | * |
| 150 | - * @param array $metadata The attachment metadata. | |
| 151 | - * @param int $id The attachment id. | |
| 166 | + * @param array<string, mixed> $metadata The attachment metadata. | |
| 167 | + * @param int $id The attachment id. | |
| 152 | 168 | * |
| 153 | - * @return mixed | |
| 169 | + * @return array<string, mixed> | |
| 154 | 170 | */ |
| 155 | 171 | private function get_altered_metadata_for_remote_images( $metadata, $id ) { |
| 156 | - $sizes = $this->get_all_image_sizes(); | |
| 157 | 172 | |
| 158 | 173 | $post = get_post( $id ); |
| 159 | 174 | |
| 160 | 175 | $sizes_meta = []; |
| @@ -169,26 +184,16 @@ | ||
| 169 | 184 | |
| 170 | 185 | if ( ! isset( $metadata['height'] ) || ! isset( $metadata['width'] ) ) { |
| 171 | 186 | return $metadata; |
| 172 | 187 | } |
| 173 | - | |
| 188 | + $sizes = Optml_App_Replacer::image_sizes(); | |
| 174 | 189 | foreach ( $sizes as $size => $args ) { |
| 175 | 190 | // check if the image is portrait or landscape using attachment metadata. |
| 176 | - $is_portrait = $metadata['height'] > $metadata['width']; | |
| 177 | - | |
| 178 | - // proportionally set the width/height based on this if image is uncropped. | |
| 179 | - if ( ! (bool) $args['crop'] ) { | |
| 180 | - if ( $is_portrait ) { | |
| 181 | - $args['width'] = (int) ( $args['height'] * round( $metadata['width'] / $metadata['height'] ) ); | |
| 182 | - } else { | |
| 183 | - $args['height'] = (int) ( $args['width'] * round( $metadata['height'] / $metadata['width'] ) ); | |
| 184 | - } | |
| 185 | - } | |
| 186 | - | |
| 191 | + $dimensions = $this->size_to_dimension( $size, $metadata ); | |
| 187 | 192 | $sizes_meta[ $size ] = [ |
| 188 | 193 | 'file' => $metadata['file'], |
| 189 | - 'width' => $args['width'], | |
| 190 | - 'height' => $args['height'], | |
| 194 | + 'width' => $dimensions['width'], | |
| 195 | + 'height' => $dimensions['height'], | |
| 191 | 196 | 'mime-type' => $post->post_mime_type, |
| 192 | 197 | ]; |
| 193 | 198 | } |
| 194 | 199 | |
| @@ -208,39 +213,125 @@ | ||
| 208 | 213 | $extension = pathinfo( $url, PATHINFO_EXTENSION ); |
| 209 | 214 | |
| 210 | 215 | return str_replace( '.' . $extension, '-scaled.' . $extension, $url ); |
| 211 | 216 | } |
| 217 | + /** | |
| 218 | + * Check if the URL is a scaled image. | |
| 219 | + * | |
| 220 | + * @param string $url The URL to check. | |
| 221 | + * | |
| 222 | + * @return bool | |
| 223 | + */ | |
| 224 | + private function is_scaled_url( $url ) { | |
| 225 | + return strpos( $url, '-scaled.' ) !== false; | |
| 226 | + } | |
| 212 | 227 | |
| 213 | 228 | /** |
| 229 | + * Check if the image has been offloaded completely. | |
| 230 | + * | |
| 231 | + * @param int $id The attachment ID. | |
| 232 | + * | |
| 233 | + * @return bool | |
| 234 | + */ | |
| 235 | + private function is_completed_offload( $id ) { | |
| 236 | + // This is the flag that is used to mark the image as offloaded at the end. | |
| 237 | + $completed = ! empty( get_post_meta( $id, Optml_Media_Offload::OM_OFFLOADED_FLAG, true ) ); | |
| 238 | + if ( $completed ) { | |
| 239 | + return true; | |
| 240 | + } | |
| 241 | + // In some rare cases the image is offloaded but the flag is not set so we can alternatively check this using the file path. | |
| 242 | + $meta = wp_get_attachment_metadata( $id ); | |
| 243 | + if ( ! isset( $meta['file'] ) ) { | |
| 244 | + return false; | |
| 245 | + } | |
| 246 | + if ( Optml_Media_Offload::is_uploaded_image( $meta['file'] ) ) { | |
| 247 | + return true; | |
| 248 | + } | |
| 249 | + | |
| 250 | + return false; | |
| 251 | + } | |
| 252 | + /** | |
| 253 | + * Get the attachment ID from optimole ID. | |
| 254 | + * | |
| 255 | + * @param string $optimole_id The optimole ID. | |
| 256 | + * | |
| 257 | + * @return int | |
| 258 | + */ | |
| 259 | + private function get_attachement_id_from_optimole_id( string $optimole_id ): int { | |
| 260 | + global $wpdb; | |
| 261 | + | |
| 262 | + $id = $wpdb->get_var( | |
| 263 | + $wpdb->prepare( | |
| 264 | + " | |
| 265 | + SELECT post_id | |
| 266 | + FROM {$wpdb->postmeta} | |
| 267 | + WHERE meta_key = %s | |
| 268 | + AND meta_value = %s | |
| 269 | + LIMIT 1 | |
| 270 | + ", | |
| 271 | + Optml_Dam::OM_DAM_IMPORTED_FLAG, | |
| 272 | + $optimole_id | |
| 273 | + ) | |
| 274 | + ); | |
| 275 | + return empty( $id ) ? 0 : (int) $id; | |
| 276 | + } | |
| 277 | + /** | |
| 214 | 278 | * Get the attachment ID from URL. |
| 215 | 279 | * |
| 216 | - * @param string $url The attachment URL. | |
| 280 | + * @param string $input_url The attachment URL. | |
| 217 | 281 | * |
| 218 | 282 | * @return int |
| 219 | 283 | */ |
| 220 | - private function attachment_url_to_post_id( $url ) { | |
| 221 | - $cached = Optml_Attachment_Cache::get_cached_attachment_id( $url ); | |
| 284 | + private function attachment_url_to_post_id( $input_url ) { | |
| 285 | + $cached = Optml_Attachment_Cache::get_cached_attachment_id( $input_url ); | |
| 222 | 286 | |
| 223 | 287 | if ( $cached !== false ) { |
| 224 | - return $cached; | |
| 288 | + return (int) $cached; | |
| 225 | 289 | } |
| 226 | 290 | |
| 227 | - $url = $this->strip_image_size( $url ); | |
| 291 | + if ( Optml_Media_Offload::is_uploaded_image( $input_url ) ) { | |
| 292 | + // The DAM are stored as attachments of format /id:<attachment_id>/<original_url> | |
| 293 | + $pattern = '#/' . Optml_Media_Offload::KEYS['uploaded_flag'] . '([^/]+)#'; | |
| 294 | + if ( preg_match( $pattern, $input_url, $m ) ) { | |
| 295 | + $attachment_id = $this->get_attachement_id_from_optimole_id( $m[1] ); | |
| 296 | + if ( $attachment_id !== 0 ) { | |
| 297 | + Optml_Attachment_Cache::set_cached_attachment_id( $input_url, $attachment_id ); | |
| 228 | 298 | |
| 299 | + return $attachment_id; | |
| 300 | + } | |
| 301 | + } | |
| 302 | + } | |
| 303 | + | |
| 304 | + $url = $this->strip_image_size( $input_url ); | |
| 305 | + | |
| 229 | 306 | $attachment_id = attachment_url_to_postid( $url ); |
| 230 | 307 | |
| 231 | - if ( $attachment_id === 0 ) { | |
| 308 | + if ( $attachment_id === 0 && ! $this->is_scaled_url( $url ) ) { | |
| 232 | 309 | $scaled_url = $this->get_scaled_url( $url ); |
| 233 | 310 | |
| 234 | 311 | $attachment_id = attachment_url_to_postid( $scaled_url ); |
| 235 | 312 | } |
| 236 | 313 | |
| 314 | + /* | |
| 315 | + * TODO: The logic is a mess, we need to refactor at some point. | |
| 316 | + * Websites may transition between 'www' subdomains and apex domains, potentially breaking references to hosted images. This can cause issues when attempting to match attachment IDs if images are linked using outdated domains. The logic is checking for alternative domains and consider the use of 'scaled' prefixes in image URLs for large images, which might affect ID matching. | |
| 317 | + */ | |
| 237 | 318 | if ( $attachment_id === 0 ) { |
| 238 | - return $attachment_id; | |
| 319 | + if ( strpos( $url, 'www.' ) !== false ) { | |
| 320 | + $variant_url = str_replace( 'www.', '', $url ); | |
| 321 | + $attachment_id = attachment_url_to_postid( $variant_url ); | |
| 322 | + } else { | |
| 323 | + $variant_url = str_replace( '://', '://www.', $url ); | |
| 324 | + $attachment_id = attachment_url_to_postid( $variant_url ); | |
| 325 | + } | |
| 326 | + if ( $attachment_id === 0 && ! $this->is_scaled_url( $variant_url ) ) { | |
| 327 | + $scaled_url = $this->get_scaled_url( $variant_url ); | |
| 328 | + | |
| 329 | + $attachment_id = attachment_url_to_postid( $scaled_url ); | |
| 330 | + } | |
| 239 | 331 | } |
| 332 | + Optml_Attachment_Cache::set_cached_attachment_id( $input_url, $attachment_id ); | |
| 240 | 333 | |
| 241 | - Optml_Attachment_Cache::set_cached_attachment_id( $url, $attachment_id ); | |
| 242 | - | |
| 243 | 334 | return $attachment_id; |
| 244 | 335 | } |
| 245 | 336 | |
| 246 | 337 | /** |
| @@ -255,7 +346,6 @@ | ||
| 255 | 346 | $url = str_replace( $src_parts[1], '', $url ); |
| 256 | 347 | } |
| 257 | 348 | |
| 258 | 349 | return $url; |
| 259 | - | |
| 260 | 350 | } |
| 261 | 351 | } |