post_id = $post_id; if (function_exists('wp_get_original_image_path')) // WP 5.3+ { $source_file = wp_get_original_image_path($post_id); if ($source_file === false) // if it's not an image, returns false, use the old way. { $source_file = trim(get_attached_file($post_id, apply_filters( 'emr_unfiltered_get_attached_file', true ))); } else { $this->source_is_scaled = true; } } else $source_file = trim(get_attached_file($post_id, apply_filters( 'emr_unfiltered_get_attached_file', true ))); /* It happens that the SourceFile returns relative / incomplete when something messes up get_upload_dir with an error something. This case shoudl be detected here and create a non-relative path anyhow.. */ if (! file_exists($source_file) && $source_file && 0 !== strpos( $source_file, '/' ) && ! preg_match( '|^.:\\\|', $source_file ) ) { $file = get_post_meta( $post_id, '_wp_attached_file', true ); $uploads = wp_get_upload_dir(); $source_file = $uploads['basedir'] . "/$source_file"; } Log::addDebug('SourceFile ' . $source_file); $this->sourceFile = $this->fs()->getFile($source_file); $this->source_post = get_post($post_id); $this->source_is_image = wp_attachment_is('image', $this->source_post); $this->source_metadata = wp_get_attachment_metadata( $post_id ); if (function_exists('wp_get_original_image_url')) // WP 5.3+ { $source_url = wp_get_original_image_url($post_id); if ($source_url === false) // not an image, or borked, try the old way $source_url = wp_get_attachment_url($post_id); $this->source_url = $source_url; } else $this->source_url = wp_get_attachment_url($post_id); } private function fs() { return emr()->filesystem(); } public function setMode($mode) { $this->replaceMode = $mode; } public function setTimeMode($mode, $datetime = 0) { if ($datetime == 0) $datetime = current_time('mysql'); $this->datetime = $datetime; $this->timeMode = $mode; } /** Replace the sourceFile with a target * @param $file String Full Path to the Replacement File. This will usually be an uploaded file in /tmp/ * @param $fileName String The fileName of the uploaded file. This will be used if sourcefile is not to be overwritten. * @throws RunTimeException Can throw exception if something went wrong with the files. */ public function replaceWith($file, $fileName) { global $wpdb; $this->targetName = $fileName; $targetFile = $this->getTargetFile(); $fs = $this->fs(); if (is_null($targetFile)) { return null; // $ex = __('Target File could not be set. The source file might not be there. In case of search and replace, a filter might prevent this', "enable-media-replace"); // throw new \RuntimeException($ex); } $targetFileObj = $fs->getFile($targetFile); $directoryObj = $targetFileObj->getFileDir(); $result = $directoryObj->check(); if ($result === false) Log::addError('Directory creation for targetFile failed'); $permissions = $this->sourceFile->getPermissions(); $this->removeCurrent(); // tries to remove the current files. /* @todo See if wp_handle_sideload / wp_handle_upload can be more securely used for this */ // @todo Use FS / File copy for this. $fileObj = $fs->getFile($file); $result_moved = $fileObj->move($targetFileObj); if (false === $result_moved) { if ($targetFileObj->exists()) { Log::addDebug('Could remove file from tmp directory?'); } else { $ex = sprintf( esc_html__('The uploaded file could not be moved to %1$s. This is most likely an issue with permissions, or upload failed.', "enable-media-replace"), $targetFile ); throw new \RuntimeException($ex); } } // init targetFile. $this->targetFile = $fs->getFile($targetFile); if ($permissions > 0) chmod( $targetFile, $permissions ); // restore permissions else { Log::addWarn('Setting permissions failed'); } // update the file attached. This is required for wp_get_attachment_url to work. // Using RawFullPath because FullPath does normalize path, which update_attached_file doesn't so in case of windows / strange Apspaths it fails. $updated = update_attached_file($this->post_id, $this->targetFile->getRawFullPath() ); if (! $updated) Log::addError('Update Attached File reports as not updated or same value'); $this->target_url = $this->getTargetURL(); //wp_get_attachment_url($this->post_id); // Run the filter, so other plugins can hook if needed. $filtered = apply_filters( 'wp_handle_upload', array( 'file' => $this->targetFile->getFullPath(), 'url' => $this->target_url, 'type' => $this->targetFile->getMime(), ), 'sideload'); // check if file changed during filter. Set changed to attached file meta properly. if (isset($filtered['file']) && $filtered['file'] != $this->targetFile->getFullPath() ) { update_attached_file($this->post_id, $filtered['file'] ); $this->targetFile = $this->fs()->getFile($filtered['file']); // handle as a new file Log::addInfo('WP_Handle_upload filter returned different file', $filtered); } // Check and update post mimetype, otherwise badly coded plugins cry. $post_mime = get_post_mime_type($this->post_id); $target_mime = $this->targetFile->getMime(); // update DB post mime type, if somebody decided to mess it up, and the target one is not empty. if ($target_mime !== $post_mime && strlen($target_mime) > 0) { \wp_update_post(array('post_mime_type' => $this->targetFile->getMime(), 'ID' => $this->post_id)); } $metadata = wp_generate_attachment_metadata( $this->post_id, $this->targetFile->getFullPath() ); wp_update_attachment_metadata( $this->post_id, $metadata ); $this->target_metadata = $metadata; /** If author is different from replacer, note this */ $author_id = get_post_meta($this->post_id, '_emr_replace_author', true); if ( intval($this->source_post->post_author) !== get_current_user_id()) { update_post_meta($this->post_id, '_emr_replace_author', get_current_user_id()); } elseif ($author_id) { delete_post_meta($this->post_id, '_emr_replace_author'); } if ($this->replaceMode == self::MODE_SEARCHREPLACE) { // Write new image title. $title = $this->getNewTitle(); $excerpt = $this->getNewExcerpt(); $update_ar = array('ID' => $this->post_id); $update_ar['post_title'] = $title; $update_ar['post_name'] = sanitize_title($title); if ($excerpt !== false) { $update_ar['post_excerpt'] = $excerpt; } $update_ar['guid'] = $this->target_url; //wp_get_attachment_url($this->post_id); // $update_ar['post_mime_type'] = $this->targetFile->getFileMime(); $post_id = \wp_update_post($update_ar, true); // update post doesn't update GUID on updates. $wpdb->update( $wpdb->posts, array( 'guid' => $this->target_url), array('ID' => $this->post_id) ); //enable-media-replace-upload-done // @todo Replace this one with proper Notices:addError; if (is_wp_error($post_id)) { $errors = $post_id->get_error_messages(); foreach ($errors as $error) { echo $error; } } } // SEARCH REPLACE MODE $args = array( 'thumbnails_only' => ($this->replaceMode == self::MODE_SEARCHREPLACE) ? false : true, ); // Search Replace will also update thumbnails. $this->doSearchReplace($args); /*if(wp_attachment_is_image($this->post_id)) { $this->ThumbnailUpdater->setNewMetadata($this->target_metadata); $result = $this->ThumbnailUpdater->updateThumbnails(); if (false === $result) Log::addWarn('Thumbnail Updater returned false'); }*/ // if all set and done, update the date. // This must be done after wp_update_posts $this->updateDate(); // updates the date. // Give the caching a kick. Off pending specifics. $cache_args = array( 'flush_mode' => 'post', 'post_id' => $this->post_id, ); $cache = new emrCache(); $cache->flushCache($cache_args); do_action("enable-media-replace-upload-done", $this->target_url, $this->source_url, $this->post_id); return true; } protected function getNewTitle() { // get basename without extension $title = basename($this->targetFile->getFileName(), '.' . $this->targetFile->getExtension()); $meta = $this->target_metadata; if (isset($meta['image_meta'])) { if (isset($meta['image_meta']['title'])) { if (strlen($meta['image_meta']['title']) > 0) { $title = $meta['image_meta']['title']; } } } // Thanks Jonas Lundman (http://wordpress.org/support/topic/add-filter-hook-suggestion-to) $title = apply_filters( 'enable_media_replace_title', $title ); return $title; } protected function getNewExcerpt() { $meta = $this->target_metadata; $excerpt = false; if (isset($meta['image_meta'])) { if (isset($meta['image_meta']['caption'])) { if (strlen($meta['image_meta']['caption']) > 0) { $excerpt = $meta['image_meta']['caption']; } } } return $excerpt; } /** Gets the source file after processing. Returns a file */ public function getSourceFile() { return $this->sourceFile; } public function getSourceUrl() { return $this->source_url; } public function setNewTargetLocation($new_rel_location) { $uploadDir = wp_upload_dir(); $newPath = trailingslashit($uploadDir['basedir']) . $new_rel_location; // Detect traversal by making sure the canonical path starts with uploads' basedir. if (($newPath = realpath($newPath)) && strpos($newPath, $uploadDir['basedir']) !== 0) { Notices::addError(__('Specificed directory is outside the upload directory. This is not allowed for security reasons', 'enable-media-replace')); return false; } if (! is_dir($newPath)) { Notices::addError(__('Specificed new directory does not exist. Path must be a relative path from the upload directory and exist', 'enable-media-replace')); return false; } $this->target_location = trailingslashit($newPath); return true; } /** Returns a full target path to place to new file. Including the file name! **/ protected function getTargetFile() { $targetPath = null; if ($this->replaceMode == self::MODE_REPLACE) { $targetFile = $this->sourceFile->getFullPath(); // overwrite source } elseif ($this->replaceMode == self::MODE_SEARCHREPLACE) { $path = (string) $this->sourceFile->getFileDir(); if ($this->target_location) // Replace to another path. { $otherTarget = $this->fs()->getFile($this->target_location . $this->targetName); if ($otherTarget->exists()) { Notices::addError(__('In specificied directory there is already a file with the same name. Can\'t replace.', 'enable-media-replace')); return null; } $path = $this->target_location; // if all went well. } //if ($this->sourceFile->getFileName() == $this->targetName) $targetpath = $path . $this->targetName; // If the source and target path AND filename are identical, user has wrong mode, just overwrite the sourceFile. if ($targetpath == $this->sourceFile->getFullPath()) { $unique = $this->sourceFile->getFileName(); $this->replaceMode == self::MODE_REPLACE; } else { $unique = wp_unique_filename($path, $this->targetName); } $new_filename = apply_filters( 'emr_unique_filename', $unique, $path, $this->post_id ); $targetFile = trailingslashit($path) . $new_filename; } if (is_dir($targetFile)) // this indicates an error with the source. { Log::addWarn('TargetFile is directory ' . $targetFile ); $upload_dir = wp_upload_dir(); if (isset($upload_dir['path'])) { $targetFile = trailingslashit($upload_dir['path']) . wp_unique_filename($targetFile, $this->targetName); } else { $err = __('EMR could not establish a proper destination for replacement', 'enable-media-replace'); Notices::addError($err); Log::addError($err); // throw new \RuntimeException($err); // exit($err); // fallback return null; } } return $targetFile; } /** Since WP functions also can't be trusted here in certain cases, create the URL by ourselves */ protected function getTargetURL() { //$uploads['baseurl'] $url = wp_get_attachment_url($this->post_id); $url_basename = basename($url); // Seems all worked as normal. if (strpos($url, '://') >= 0 && $this->targetFile->getFileName() == $url_basename) return $url; // Relative path for some reason if (strpos($url, '://') === false) { $uploads = wp_get_upload_dir(); $url = str_replace($uploads['basedir'], $uploads['baseurl'], $this->targetFile->getFullPath()); } // This can happen when WordPress is not taking from attached file, but wrong /old GUID. Try to replace it to the new one. elseif ($this->targetFile->getFileName() != $url_basename) { $url = str_replace($url_basename, $this->targetFile->getFileName(), $url); } return $url; //$this->targetFile } /** Tries to remove all of the old image, without touching the metadata in database * This might fail on certain files, but this is not an indication of success ( remove might fail, but overwrite can still work) */ protected function removeCurrent() { $meta = \wp_get_attachment_metadata( $this->post_id ); $backup_sizes = get_post_meta( $this->post_id, '_wp_attachment_backup_sizes', true ); // this must be -scaled if that exists, since wp_delete_attachment_files checks for original_files but doesn't recheck if scaled is included since that the one 'that exists' in WP . $this->source_file replaces original image, not the -scaled one. $file = $this->sourceFile->getFullPath(); $result = \wp_delete_attachment_files($this->post_id, $meta, $backup_sizes, $file ); // If Attached file is not the same path as file, this indicates a -scaled images is in play. // Also plugins like Polylang tend to block delete image while there is translation / duplicate item somewhere // 10/06/22 : Added a hard delete if file still exists. Be gone, hard way. $attached_file = get_attached_file($this->post_id); if (file_exists($attached_file)) { @unlink($attached_file); } do_action( 'emr_after_remove_current', $this->post_id, $meta, $backup_sizes, $file ); } /** Handle new dates for the replacement */ protected function updateDate() { global $wpdb; $post_date = $this->datetime; $post_date_gmt = get_gmt_from_date($post_date); $update_ar = array('ID' => $this->post_id); if ($this->timeMode == static::TIME_UPDATEALL || $this->timeMode == static::TIME_CUSTOM) { $update_ar['post_date'] = $post_date; $update_ar['post_date_gmt'] = $post_date_gmt; } else { //$update_ar['post_date'] = 'post_date'; // $update_ar['post_date_gmt'] = 'post_date_gmt'; } $update_ar['post_modified'] = $post_date; $update_ar['post_modified_gmt'] = $post_date_gmt; $updated = $wpdb->update( $wpdb->posts, $update_ar , array('ID' => $this->post_id) ); wp_cache_delete($this->post_id, 'posts'); } protected function doSearchReplace($args = array()) { $defaults = array( 'thumbnails_only' => false, ); $args = wp_parse_args($args, $defaults); // Search-and-replace filename in post database // @todo Check this with scaled images. $base_url = parse_url($this->source_url, PHP_URL_PATH);// emr_get_match_url( $this->source_url); $base_url = str_replace('.' . pathinfo($base_url, PATHINFO_EXTENSION), '', $base_url); $abspath = $this->fs()->getWPAbsPath(); /** Fail-safe if base_url is a whole directory, don't go search/replace */ if (strpos($abspath, $base_url) === 0 && is_dir($base_url)) { Log::addError('Search Replace tried to replace to directory - ' . $base_url); Notices::addError(__('Fail Safe :: Source Location seems to be a directory.', 'enable-media-replace')); return; } if (strlen(trim($base_url)) == 0) { Log::addError('Current Base URL emtpy - ' . $base_url); Notices::addError(__('Fail Safe :: Source Location returned empty string. Not replacing content','enable-media-replace')); return; } // get relurls of both source and target. $urls = $this->getRelativeURLS(); if ($args['thumbnails_only']) { // if (isset($urls['source']['file']) && $urls['source']) /*foreach($urls as $side => $data) { if (isset($data['base'])) { unset($urls[$side]['base']); } if (isset($data['file'])) { unset($urls[$side]['file']); } } */ } $search_urls = $urls['source']; $replace_urls = $urls['target']; /* If the replacement is much larger than the source, there can be more thumbnails. This leads to disbalance in the search/replace arrays. Remove those from the equation. If the size doesn't exist in the source, it shouldn't be in use either */ foreach($replace_urls as $size => $url) { if (! isset($search_urls[$size])) { Log::addDebug('Dropping size ' . $size . ' - not found in source urls'); unset($replace_urls[$size]); } } // Original can be unbalanced if (isset($search_urls['original'])) { if (! isset($replace_urls['original'])) { $replace_urls['original'] = $replace_urls['file']; } } Log::addDebug('Source', $search_urls); Log::addDebug('Target', $replace_urls); /* If on the other hand, some sizes are available in source, but not in target, try to replace them with something closeby. */ foreach($search_urls as $size => $url) { if (! isset($replace_urls[$size])) { $closest = $this->findNearestSize($size); if ($closest) { $sourceUrl = $search_urls[$size]; $baseurl = trailingslashit(str_replace(wp_basename($sourceUrl), '', $sourceUrl)); Log::addDebug('Nearest size of source ' . $size . ' for target is ' . $closest); $replace_urls[$size] = $baseurl . $closest; } else { Log::addDebug('Unset size ' . $size . ' - no closest found in source'); } } elseif ($url === $replace_urls[$size]) { // identical unset($replace_urls[$size]); unset($search_urls[$size]); Log::addDebug('Unset size ' . $size . ' - search and replace identical'); } } /* If source and target are the same, remove them from replace. This happens when replacing a file with same name, and +/- same dimensions generated. After previous loops, for every search there should be a replace size. */ foreach($search_urls as $size => $url) { $replace_url = isset($replace_urls[$size]) ? $replace_urls[$size] : false; if ($url == $replace_url) // if source and target as the same, no need for replacing. { unset($search_urls[$size]); unset($replace_urls[$size]); } } // If the two sides are disbalanced, the str_replace part will cause everything that has an empty replace counterpart to replace it with empty. Unwanted. if (count($search_urls) !== count($replace_urls)) { Log::addError('Unbalanced Replace Arrays, aborting', array($search_urls, $replace_urls, count($search_urls), count($replace_urls) )); Notices::addError(__('There was an issue with updating your image URLS: Search and replace have different amount of values. Aborting updating thumbnails', 'enable-media-replace')); return; } Log::addDebug('Doing meta search and replace -', array($search_urls, $replace_urls) ); Log::addDebug('Searching with BaseuRL ' . $base_url); do_action('emr/replace_urls', $search_urls, $replace_urls); $updated = 0; $updated += $this->doReplaceQuery($base_url, $search_urls, $replace_urls); $replaceRuns = apply_filters('emr/replacer/custom_replace_query', array(), $base_url, $search_urls, $replace_urls); Log::addDebug("REPLACE RUNS", $replaceRuns); foreach($replaceRuns as $component => $run) { Log::addDebug('Running additional replace for : '. $component, $run); $updated += $this->doReplaceQuery($run['base_url'], $run['search_urls'], $run['replace_urls']); } Log::addDebug("Updated Records : " . $updated); return $updated; } // doSearchReplace private function doReplaceQuery($base_url, $search_urls, $replace_urls) { global $wpdb; /* Search and replace in WP_POSTS */ // Removed $wpdb->remove_placeholder_escape from here, not compatible with WP 4.8 $posts_sql = $wpdb->prepare( "SELECT ID, post_content FROM $wpdb->posts WHERE post_status = 'publish' AND post_content LIKE %s", '%' . $base_url . '%'); $rs = $wpdb->get_results( $posts_sql, ARRAY_A ); $number_of_updates = 0; if ( ! empty( $rs ) ) { foreach ( $rs AS $rows ) { $number_of_updates = $number_of_updates + 1; // replace old URLs with new URLs. $post_content = $rows["post_content"]; $post_id = $rows['ID']; $replaced_content = $this->replaceContent($post_content, $search_urls, $replace_urls); if ($replaced_content !== $post_content) { $sql = 'UPDATE ' . $wpdb->posts . ' SET post_content = %s WHERE ID = %d'; $sql = $wpdb->prepare($sql, $replaced_content, $post_id); $result = $wpdb->query($sql); if ($result === false) { Notice::addError('Something went wrong while replacing' . $result->get_error_message() ); Log::addError('WP-Error during post update', $result); } } } } $number_of_updates += $this->handleMetaData($base_url, $search_urls, $replace_urls); return $number_of_updates; } private function handleMetaData($url, $search_urls, $replace_urls) { global $wpdb; $options = array('post', 'comment', 'term', 'user', 'options'); $meta_options = apply_filters('emr/metadata_tables', $options); // fields in options to look for. $option_fields = array('widget_block'); $option_fields = apply_filters('emr/replacer/option_fields', $option_fields); $number_of_updates = 0; $prepare = array('%' . $url . '%'); foreach($meta_options as $type) { switch($type) { case "post": // special case. $sql = 'SELECT meta_id as id, meta_key, meta_value FROM ' . $wpdb->postmeta . ' WHERE post_id in (SELECT ID from '. $wpdb->posts . ' where post_status = "publish") AND meta_value like %s'; $type = 'post'; $update_sql = ' UPDATE ' . $wpdb->postmeta . ' SET meta_value = %s WHERE meta_id = %d'; break; case "options": // basked case (for guten widgets). $in_str_arr = array_fill( 0, count( $option_fields ), '%s' ); $in_str = join( ',', $in_str_arr ); $sql = 'SELECT option_id as id, option_name, option_value as meta_value FROM ' . $wpdb->options . ' WHERE option_value like %s and option_name in (' . $in_str . ')'; $type = 'option'; $prepare = array_merge($prepare, $option_fields); $update_sql = ' UPDATE ' . $wpdb->options . ' SET option_value = %s WHERE option_id = %d'; break; default: $table = $wpdb->{$type . 'meta'}; // termmeta, commentmeta etc $meta_id = 'meta_id'; if ($type == 'user') $meta_id = 'umeta_id'; $sql = 'SELECT ' . $meta_id . ' as id, meta_value FROM ' . $table . ' WHERE meta_value like %s'; $update_sql = " UPDATE $table set meta_value = %s WHERE $meta_id = %d "; break; } $sql = $wpdb->prepare($sql, $prepare); if ($wpdb->last_error) Log::addWarn('Error' . $wpdb->last_error, $wpdb->last_query); // This is a desparate solution. Can't find anyway for wpdb->prepare not the add extra slashes to the query, which messes up the query. // $postmeta_sql = str_replace('[JSON_URL]', $json_url, $postmeta_sql); $rsmeta = $wpdb->get_results($sql, ARRAY_A); if (! empty($rsmeta)) { foreach ($rsmeta as $row) { $number_of_updates++; $content = $row['meta_value']; $id = $row['id']; $content = $this->replaceContent($content, $search_urls, $replace_urls); //str_replace($search_urls, $replace_urls, $content); $prepared_sql = $wpdb->prepare($update_sql, $content, $id); Log::addDebug('Update Meta SQl' . $prepared_sql); $result = $wpdb->query($prepared_sql); if ($wpdb->last_error) Log::addWarn('Error' . $wpdb->last_error, $wpdb->last_query); } } } // foreach return $number_of_updates; } // function /** * Replaces Content across several levels of possible data * @param $content String The Content to replace * @param $search String Search string * @param $replace String Replacement String * @param $in_deep Boolean. This is use to prevent serialization of sublevels. Only pass back serialized from top. */ private function replaceContent($content, $search, $replace, $in_deep = false) { //$is_serial = false; $content = maybe_unserialize($content); $isJson = $this->isJSON($content); if ($isJson) { Log::addDebug('Found JSON Content'); $content = json_decode($content); Log::addDebug('J/Son Content', $content); } if (is_string($content)) // let's check the normal one first. { $content = apply_filters('emr/replace/content', $content, $search, $replace); $content = str_replace($search, $replace, $content); } elseif (is_wp_error($content)) // seen this. { //return $content; // do nothing. } elseif (is_array($content) ) // array metadata and such. { foreach($content as $index => $value) { $content[$index] = $this->replaceContent($value, $search, $replace, true); //str_replace($value, $search, $replace); if (is_string($index)) // If the key is the URL (sigh) { $index_replaced = $this->replaceContent($index, $search,$replace, true); if ($index_replaced !== $index) $content = $this->change_key($content, array($index => $index_replaced)); } } } elseif(is_object($content) && '__PHP_Incomplete_Class' !== get_class($content)) // metadata objects, they exist. prevent incomplete classes from deactivated plugins or whatever. They crash. { foreach($content as $key => $value) { $content->{$key} = $this->replaceContent($value, $search, $replace, true); //str_replace($value, $search, $replace); } } if ($isJson && $in_deep === false) // convert back to JSON, if this was JSON. Different than serialize which does WP automatically. { Log::addDebug('Value was found to be JSON, encoding'); // wp-slash -> WP does stripslashes_deep which destroys JSON $content = json_encode($content, JSON_UNESCAPED_SLASHES); Log::addDebug('Content returning', array($content)); } elseif($in_deep === false && (is_array($content) || is_object($content))) $content = maybe_serialize($content); return $content; } private function change_key($arr, $set) { if (is_array($arr) && is_array($set)) { $newArr = array(); foreach ($arr as $k => $v) { $key = array_key_exists( $k, $set) ? $set[$k] : $k; $newArr[$key] = is_array($v) ? $this->change_key($v, $set) : $v; } return $newArr; } return $arr; } private function getFilesFromMetadata($meta) { $fileArray = array(); if (isset($meta['file'])) $fileArray['file'] = $meta['file']; if (isset($meta['original_image'])) { $fileArray['original'] = $meta['original_image']; } if (isset($meta['sizes'])) { foreach($meta['sizes'] as $name => $data) { if (isset($data['file'])) { $fileArray[$name] = $data['file']; } } } // scaled return $fileArray; } /* Check if given content is JSON format. */ private function isJSON($content) { if (is_array($content) || is_object($content)) return false; // can never be. $json = json_decode($content); return $json && $json != $content; } // Get REL Urls of both source and target. private function getRelativeURLS() { $dataArray = array( 'source' => array('url' => $this->source_url, 'metadata' => $this->getFilesFromMetadata($this->source_metadata) ), 'target' => array('url' => $this->target_url, 'metadata' => $this->getFilesFromMetadata($this->target_metadata) ), ); $result = array(); foreach($dataArray as $index => $item) { $result[$index] = array(); $metadata = $item['metadata']; $baseurl = parse_url($item['url'], PHP_URL_PATH); $result[$index]['base'] = $baseurl; // this is the relpath of the mainfile. $baseurl = trailingslashit(str_replace( wp_basename($item['url']), '', $baseurl)); // get the relpath of main file. foreach($metadata as $name => $filename) { $result[$index][$name] = $baseurl . wp_basename($filename); // filename can have a path like 19/08 etc. } } Log::addDebug('Relative URLS', $result); return $result; } /** FindNearestsize * This works on the assumption that when the exact image size name is not available, find the nearest width with the smallest possible difference to impact the site the least. */ private function findNearestSize($sizeName) { Log::addDebug('Find Nearest: '. $sizeName); if (! isset($this->source_metadata['sizes'][$sizeName]) || ! isset($this->target_metadata['width'])) // This can happen with non-image files like PDF. { // Check if metadata-less item is a svg file. Just the main file to replace all thumbnails since SVG's don't need thumbnails. if (strpos($this->target_url, '.svg') !== false) { $svg_file = wp_basename($this->target_url); return $svg_file; // this is the relpath of the mainfile. } return false; } $old_width = $this->source_metadata['sizes'][$sizeName]['width']; // the width from size not in new image $new_width = $this->target_metadata['width']; // default check - the width of the main image $diff = abs($old_width - $new_width); // $closest_file = str_replace($this->relPath, '', $this->newMeta['file']); $closest_file = wp_basename($this->target_metadata['file']); // mainfile as default foreach($this->target_metadata['sizes'] as $sizeName => $data) { $thisdiff = abs($old_width - $data['width']); if ( $thisdiff < $diff ) { $closest_file = $data['file']; if(is_array($closest_file)) { $closest_file = $closest_file[0];} // HelpScout case 709692915 if(!empty($closest_file)) { $diff = $thisdiff; $found_metasize = true; } } } if(empty($closest_file)) return false; return $closest_file; } } // class