hamaka / silverstripe-pasted-base64-images
Converts images pasted or dropped into the HTML editor (TinyMCE paste_data_images, stored as base64 data URIs) into regular files in the asset admin with an [image] shortcode, on save and for existing content including version history.
Package info
github.com/hamaka/silverstripe-pasted-base64-images
Type:silverstripe-vendormodule
pkg:composer/hamaka/silverstripe-pasted-base64-images
Requires
Requires (Dev)
- phpunit/phpunit: ^11.3
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Converts images that were pasted or dropped into the HTML editor, and stored as base64 data URIs, into regular
files in Files with a standard [image] shortcode. It does this on save, and with a task for existing content,
including the version history.
The problem
TinyMCE has paste_data_images enabled by default in Silverstripe 5 and 6. A pasted or dropped image ends up in the
HTML field as:
<img src="data:image/png;base64,iVBORw0KGgo...">
- The database keeps growing. Every save or publish copies the full image into
SiteTree_Versionsagain. - The image isn't in Files. No management, no reuse, no resizing, no caching as a separate file.
Pasting keeps working for editors: this module leaves paste_data_images on and fixes it on the server.
What it does
- On write, an extension on
DataObjectconverts everyHTMLTextfield that contains a base64 image. That covers pages, elemental blocks and every other object. By default only for users with CMS access. - Each image (png, jpg, gif or webp) is validated, saved as an
ImageinUploads/pasted-imagesand published. The same image is stored once. - The
<img>becomes an[image]shortcode.alt,class,width,heightandtitleare kept. - On an error the original tag is left as it is, so no content is lost. The error is logged.
- A task converts existing content, see Cleaning up existing content.
Requirements
| Silverstripe | PHP | Version | Branch |
|---|---|---|---|
| 5.3+ | 8.1+ | ^1 |
1 |
| 6 | 8.3+ | ^2 |
2 (this branch) |
Installation
composer require hamaka/silverstripe-pasted-base64-images
Then flush: ?flush=1 in the browser, or vendor/bin/sake flush. The module adds no tables and works without
configuration.
Configuration
All settings are optional. These are the defaults:
Hamaka\Base64Images\Base64ImageConverter: folder: 'Uploads/pasted-images' # folder in Files filename_prefix: 'pasted-' # followed by the first 12 characters of the sha1 image_title: 'Pasted image' default_class: 'leftAlone ss-htmleditorfield-file image' # used when the <img> has no class generate_thumbnails: true # thumbnails for the file list in the CMS write_permission: 'CMS_ACCESS' # who triggers the conversion on write; false: everyone max_bytes: 20971520 # largest image that is converted (20 MB); 0: no limit max_pixels: 50000000 # largest width x height that is converted; 0: no limit max_images: 100 # per field of one record; 0: no limit exclude_classes: [] # classes that are never converted, with their subclasses exclude_fields: [] # fields that are never converted, as 'Class.Field' notify_email: null # null: Email.admin_email; false: no mail notify_throttle_minutes: 60 # at most one mail per this many minutes; 0: no throttling
write_permission: the conversion creates and publishes files, so HTML submitted by visitors without CMS access is left as it is. Writes from the command line and the task are always converted.exclude_classesandexclude_fields: for fields that are not rendered through the shortcode parser, such as a newsletter body or content served through an API. They apply on write and in the task.notify_email:Email.admin_emailis empty by default, so no mail is sent until you set one of the two.
To disable the conversion on write altogether and only keep the task:
SilverStripe\ORM\DataObject: extensions: base64Images: null
Cleaning up existing content
ConvertBase64ImagesTask converts the base table, _Live and _Versions of every class, and the _Localised
tables of Fluent.
Make a database backup first.
vendor/bin/sake tasks:ConvertBase64ImagesTask # dry run: count and report vendor/bin/sake tasks:ConvertBase64ImagesTask --details # dry run, per record vendor/bin/sake tasks:ConvertBase64ImagesTask --apply # convert + OPTIMIZE TABLE vendor/bin/sake tasks:ConvertBase64ImagesTask --apply --no-optimize # without OPTIMIZE TABLE
- It writes with direct SQL: no new versions are created, and pages don't show up as "Modified".
- Old versions point to the same file, so the History tab keeps showing the image.
- It runs
OPTIMIZE TABLEon the changed tables, because InnoDB doesn't release the freed space otherwise. - It exits with a non-zero code when an image could not be converted.
- It generates the thumbnails for the file list in the CMS for converted files that don't have them yet.
- It is safe to run again.
The output of an apply run, shortened:
MODE: APPLY
SiteTree.Content: 3 record(s) with data:image/ ...
SiteTree_Versions.Content: 120 record(s) with data:image/ ...
--- Per table ---
SiteTree.Content: 3 records with data:image/, 3 updated, 4 images, 310 KB base64
SiteTree_Versions.Content: 120 records with data:image/, 119 updated, 121 images, 9.2 MB base64, 1 skipped
--- Total ---
125 images converted (9.5 MB base64 removed from the HTML)
4 new files in Files (Uploads/pasted-images), an existing file was reused 121 times
--- Skipped (not converted by this module, left as they are) ---
1 x Unsupported data URI (data:image/svg+xml;base64)
--- Table sizes ---
SiteTree_Versions: 14.1 MB -> 4.3 MB
Skipped data URIs are ones this module does not convert, see below. Errors are images that should have been converted but could not be; replace those in the CMS.
Limitations
- Only png, jpg, gif and webp in the
srcof an<img>. Svg, avif, bmp,srcsetand CSS backgrounds are left as they are. - Only
HTMLTextfields.HTMLFragmentandHTMLVarchardon't render shortcodes, so they are left alone. - Files are published immediately, also when the page they are on is still a draft.
- The task sets
LastEditedon the records it changes. Flush static caches yourself. OPTIMIZE TABLErebuilds the table and needs free disk space of about its size. It and the table sizes are MySQL and MariaDB only.- The dry run leaves your content alone, but runs
ANALYZE TABLEto get current table sizes.
Contributing
Report bugs and ideas in the issues. Pull
requests are welcome; code that is the same for Silverstripe 5 and 6 goes to branch 1.
License
BSD-3-Clause, see LICENSE.md.