Repository navigation
Replacing Image Files
A REST route gives an existing image a new file while keeping its ID, attachment page and, where the file type allows, its URLs. It is for publishing tools that keep a site's images up to date. PhotoPress also acts on replacements made with the Enable Media Replace plugin.
- WordPress 6.6 or later.
- A user with
upload_fileswho can edit the image (edit_poston the attachment). Requests use any REST authentication WordPress accepts, such as an application password. - The image must be an image attachment. The new file must be an image this site can display.
- Optional: Enable Media Replace, for replacements made in the media library. See Enable Media Replace.
PhotoPress has no screen for this. A client sends the new file to:
POST /wp-json/photopress/v1/media/<id>/file
The body is the file, as for POST /wp/v2/media: the raw file with a Content-Disposition: attachment; filename="photo.jpg" header, or a multipart field named file. Other arguments go in the query string or form fields.
| Argument | Type | What it does | Default |
|---|---|---|---|
update_references |
boolean | Rewrites stored links to files whose names changed. | true |
reprocess_metadata |
boolean | Reads the new file's metadata as on upload: image taxonomy terms, alt text and description. false leaves them as they are. |
true |
sync_embedded |
boolean | Gives every image block showing this image, on its own or in a gallery, the image's current alt text and caption, replacing what is there. | false |
photopress_gallery |
object | Also adds the image to a post's gallery: post (required), gallery (its HTML anchor), replace. Added by the gallery module. |
none |
The response is the image as GET /wp/v2/media/<id>?context=edit returns it, plus photopress_replaced:
| Field | Meaning |
|---|---|
files |
Old upload-relative paths mapped to new ones, for files whose names changed. |
posts_updated |
IDs of posts whose stored links or image blocks were rewritten. |
text_updated |
Whether the title, caption, description or alt text changed. |
metadata_reprocessed |
The reprocess_metadata argument. |
embedded_synced |
IDs of posts whose image blocks were synced. |
A client can find the image to replace with core's GET /wp/v2/media?search=<file name>, which searches file names.
| Code | Status | Cause |
|---|---|---|
rest_post_invalid_id |
404 | Not an image attachment. |
rest_upload_no_data, rest_upload_no_content_disposition
|
400 | No body, or no file name. |
rest_upload_invalid_type |
400 | The file is not an image, judged by its contents. |
rest_upload_invalid_image |
400 | Not an image this site can display. |
photopress_replace_under_way |
409 | Another replacement of this image began less than 10 minutes ago and has not ended. |
photopress_sizes_not_made |
500 | WordPress skipped sizes the new file should have. The image keeps its old file. data.sizes lists them. |
photopress_replace_failed, rest_upload_unknown_error
|
500 | The file could not be saved, backed up, moved, or given its sizes. The image keeps its old file. |
replaceFile runs these steps in the request.
The type is read from the file's contents with wp_check_filetype_and_ext() and wp_getimagesize() before anything is written.
| New file | Where it goes | URLs |
|---|---|---|
| Same MIME type | Over the original upload (the file before WordPress scaled it). | Unchanged. Sizes with the same dimensions keep their names. |
| Another type | Same folder, same base name, the new extension. If a file that is not this image's has that name, the next free name (wp_unique_filename()). The image's MIME type is updated. |
New. Stored links are rewritten. |
Size names carry dimensions, so a new shape or size also renames the sizes.
The file is first saved next to the old ones as <name>-photopress-replacing.<ext> through wp_handle_sideload(). Upload checks and the filters other plugins hook on uploads run as for any upload, including PhotoPress's license embedding.
- A restore record is added as the post meta
_photopress_replace_restore: start time, the old file, metadata and MIME type, the temporary file, and each file's backup. It is added only if none exists, so a second replace of the image at the same time gets 409. - An Action Scheduler action,
photopress_replace_settle, is scheduled 11 minutes later. - Each of the image's files is backed up as
<file>.photopress-backup. The file about to be overwritten is hard-linked. The others are copied. - The new file is renamed into place in one step.
- WordPress makes the sizes with
wp_generate_attachment_metadata(), over the old files where names match. The big image threshold applies, so a large file gets a-scaledcopy. With WP Offload Media active, its upload waits for the finished set. - PhotoPress checks for sizes WordPress skipped (
missingSizes): sizes fromintermediate_image_sizes_advancedthat the file is large enough for, either way up, and that the new metadata lacks. WordPress skips a size it fails to make, as when short of memory, without an error. - Success: the metadata is saved, the record is marked committed, the backups are deleted, then the record.
- A failure, an exception or a skipped size: the image is put back (
restore). Files written for the new image are deleted, each backup is moved back, and the file, metadata and MIME type are restored.
If PHP dies midway, the record stays. The next replace of the image, or photopress_replace_settle, undoes it once 10 minutes have passed since it began. A committed replace is finished instead (endPending). Until then, other replaces of the image get 409.
Files of the image that are not in the new set are deleted: renamed sizes, a -scaled copy no longer needed, the old type's original, and copies kept by WordPress's image editor (_wp_attachment_backup_sizes, which is deleted). Their URLs, as served, go to photopress_attachment_files_removed. With WP Offload Media, that can delete them from the bucket. See Offload Media and CDN.
With update_references, each old file is mapped to the new file of the same size. A size the new image lacks maps to the new file closest in width (fileReplacements).
updateReferences rewrites:
- post content and excerpts, except revisions and attachments;
- post meta of those posts (page builders, custom fields);
- comment meta, term meta, user meta, and options (block widgets, site logo, plugin settings).
Matching is by file name after a slash:
- Candidate rows contain
/<name>.or/<name>-, or the JSON-escaped\/<name>.or\/<name>-. - A match is the file's uploads path (
2024/05/photo.jpg), with/or\/between parts. Local URLs, CDN URLs with the same path, WP Offload Media URLs with a version folder (2024/05/12345678/photo.jpg) and JSON-escaped URLs in block attributes all match.photo.jpgin another month's folder, orphoto.jpg.webp, does not (code). - Serialized values are unserialized without classes, rewritten and serialized again. A value holding an object is left alone (code).
Rows are written with $wpdb->update(), not wp_update_post(). Posts keep their modified date, get no revision, and are not run through kses again. Their caches are cleaned.
Set from the new file by WordPress's upload rules, with wp_read_image_metadata() (updateText):
- Title: the file's title. Blank or numeric, the file name.
- Caption: the file's caption. Blank clears it.
- Description: from
photopress_attachment_description. The metadata module returns the description template's result when the description is on. Otherwise the description is left alone.
photopress_attachment_file_replaced fires. PhotoPress itself acts on it in three ways.
- The metadata module reads the new file into the image's description and alt text, unless
reprocess_metadataisfalse. It also sets the image taxonomy terms while Image Taxonomies is on in the Metadata tab. - While licensing is on, the metadata module records whether the license could be written into the new file.
- The CDN invalidator queues the image's path. See Offload Media and CDN.
With sync_embedded, core/image blocks whose id is this image, at any depth, get its alt text on the img and its caption as a figcaption (syncEmbedded). Only posts the requesting user can edit are changed. Link rewrites in step 4 go into every post, so images do not break. Posts are written as in step 4.
_photopress_replaced_at is set to the time. photopress_post_changed fires for:
- the image, with the URLs of its old and new files (
files); - each post rewritten or synced (
content: true); - when text changed, each other post showing the image (by
wp-image-<id>, a link to its files, or as featured image); - its parent post (
gallery: true), as a dynamic gallery there shows it without a link in the content.
On a site with a page cache and Proxy Cache Purge, PhotoPress purges these. See Page Cache.
| Hook | Type | Code | Purpose |
|---|---|---|---|
rest_api_init |
action | MediaRest.php#L80 | Registers the routes. |
rest_namespace_index |
filter | MediaRest.php#L81 | Adds versions to GET /photopress/v1. |
photopress_replace_settle |
Action Scheduler action | MediaRest.php#L82 | Undoes or finishes a replace that did not end. Argument: attachment ID. Group photopress. |
upload_dir |
filter | MediaRest.php#L310 | During the sideload only: the new file goes in the image's folder. |
image_make_intermediate_size |
filter | MediaRest.php#L549 | During size making only: tracks files written, for cleanup on failure. |
big_image_size_threshold |
filter (applied) | MediaRest.php#L196 | Read for /media/limits. |
| Name | Kind | Purpose |
|---|---|---|
_photopress_replaced_at |
post meta | When the image's file was last replaced (Unix time). |
_photopress_replace_restore |
post meta | The restore record of a replace under way. Deleted when it ends. |
<file>.photopress-backup |
files | Backups during a replace. Deleted when it ends. |
| Route | Method | Permission | Returns |
|---|---|---|---|
/photopress/v1/media/<id>/file |
POST |
upload_files and edit_post on the image |
The image, with photopress_replaced. |
/photopress/v1/media/limits |
GET | upload_files |
image_size_threshold: the size images are scaled down to on upload, 0 when off (code). |
/photopress/v1 |
GET | public | The namespace index. For users with upload_files, also versions: wordpress and photopress (code). |
PhotoPress uses only its enable-media-replace-upload-done action, which it fires once a replacement is done (EnableMediaReplace.php). Arguments: target URL, source URL, attachment ID. Versions before 4 pass the URLs only. PhotoPress then finds the image with attachment_url_to_postid().
For an image, PhotoPress then:
- fires
photopress_attachment_file_replacedwith the old and new upload-relative paths (when they differ) andreprocess_metadatatrue. The metadata is re-read and the CDN path queued, as for PhotoPress's route; - fires
photopress_post_changedfor the image (with both URLs), for each post showing it, and for its parent (gallery: true).
PhotoPress does not rewrite links, back up files, or fire photopress_attachment_files_removed for these replacements. Enable Media Replace does its own work.
Without Enable Media Replace, the action never fires and nothing happens. PhotoPress's route does not depend on it.
While sizes are made, PhotoPress returns true from as3cf_pre_update_attachment_metadata so the finished set is uploaded once, to the same keys. See Offload Media and CDN.
| Hook | Type | Arguments | When | Code |
|---|---|---|---|---|
photopress_attachment_file_replaced |
action |
int $id, string[] $replacements (old path => new path), int[] $rewritten (post IDs), array $options (reprocess_metadata) |
After a new file is in place and links are rewritten. Also fired for Enable Media Replace, with $rewritten empty. |
MediaRest.php#L388 |
photopress_attachment_files_removed |
action |
int $id, string[] $urls (as served) |
After the replacement deleted files the new file does not have. | MediaRest.php#L364 |
photopress_attachment_description |
filter |
null $description, int $id, string $file
|
When the text is set from the new file. Return a string to set the description. Return null to leave it. | MediaRest.php#L726 |
photopress_replace_file_args |
filter |
array $args (route arguments by name) |
When the route is registered. | MediaRest.php#L140 |
photopress_replace_file_response |
filter |
array $data, int $id, WP_REST_Request $request
|
Before the response is returned. | MediaRest.php#L457 |
photopress_post_changed |
action |
int $id, array $context (files, content, gallery) |
For the image and each post whose display changed. | GalleryAdd.php#L789 |
- The whole replacement, including making every size and rewriting links, runs in the one request.
- A new file type changes the image's URLs. Links outside the tables listed in step 4, such as other sites or hard-coded theme files, are not rewritten.
- Revisions are not rewritten.
-
sync_embeddedchanges onlycore/imageblocks. Classic content keeps its alt text and captions. - The skipped-size check is not made when no image editor on the server handles the file's type.
- A replace that dies holds the image for 10 minutes. Requests in that time get 409.
Galleries
- Gallery Layouts
- Full-Screen Slideshow
- Gallery Slideshow Block
- Taxonomy Gallery
- Child Pages Gallery
- Gallery Images Added and Removed
- Image Templates and Blocks
Metadata
Pages and search
Media
Operation