Skip to content

Show file extension icons for non-image files in the FileUpload widget - #1045

Open
AIC-BV wants to merge 5 commits into
wintercms:developfrom
AIC-BV:patch-20
Open

AIC-BV wants to merge 5 commits into
wintercms:developfrom
AIC-BV:patch-20

Conversation

@AIC-BV

@AIC-BV AIC-BV commented Jan 24, 2024 •

Copy link
Copy Markdown
Contributor

Replaces the original iframe approach in this PR with the direction discussed above: reuse the file extension icon from the MediaManager (#1061) in the FileUpload FormWidget.

Problem

In image mode, the FileUpload widget always renders <img src="thumbUrl">. For files that aren't images (e.g. a PDF when fileTypes allows it), getThumb() returns the file path itself, so the widget shows a broken image, both for existing files and right after upload.

Changes

  • Shared icon helper: new Backend::fileIcon(string $extension) on the Backend helper (next to Backend::dateTime()). It renders the backend::file_icon view (modules/backend/views/file_icon.php), which holds the SVG that used to live in mediamanager/partials/_item-icon.php. The extension is now escaped, since FileUpload file names are user-supplied.
  • Shared icon styles: moved the .file-icon rules (including the CSS variable colours from Update Winter logos & use CSS variables for backend colour #1541) out of mediamanager.less into modules/backend/assets/less/controls/file-icon.less. Both mediamanager.less and fileupload.less import it, so the icon renders outside the [data-control="media-manager"] scope.
  • MediaManager: _item-icon.php calls Backend::fileIcon(); the tile view looks the same as before.
  • FileUpload:
    • New makeFileIcon($file) helper: returns Backend::fileIcon() for files browsers can't display in an <img>, null otherwise.
    • _image_single.php and _image_multi.php render the icon instead of the <img> for those files.
    • onUpload() returns the icon markup as icon. fileupload.js swaps it in for the thumbnail on success.
    • Icon sizing for both image modes (100×100 single, 44×44 multi).

Notes

  • "Displayable" is a fixed list on the widget ($displayableImageExtensions: avif, bmp, gif, ico, jpeg, jpg, png, svg, webp), not FileDefinitions::get('imageExtensions'). That definition is configurable via cms.fileDefinitions, and some installs widen it to allow e.g. PDFs in image fields, which is exactly the case this PR fixes.
  • Only fileupload.css and mediamanager.css were recompiled.

Testing

Tested in a Winter 1.2 backend with an attachMany field (image-multi) and an attachOne field (image-single):

  • Uploading a PDF / DOCX shows the extension icon; uploading a PNG still shows its thumbnail.
  • After a reload, existing files render the same way.
  • MediaManager tile view renders its file icons as before.
Before After
Before: PDF and DOCX show an empty thumbnail After: PDF and DOCX show a file extension icon

🤖 Generated with Claude Code

@AIC-BV
AIC-BV marked this pull request as draft January 24, 2024 10:28
@AIC-BV

AIC-BV commented Jan 24, 2024

Copy link
Copy Markdown
Contributor Author

Well I guess its the same as https://github.com/wintercms/winter/pull/990/files 😆

@AIC-BV

AIC-BV commented Jan 24, 2024

Copy link
Copy Markdown
Contributor Author

Kind of worked around it using a partial

<?php
if ($model->drawing) {
    $src = $model->drawing->getPath();
?>
    <iframe src="<?= e($src) ?>#toolbar=0" width="50%" height="420px"></iframe>
<?php } ?>

@damsfx

damsfx commented Jan 24, 2024 •

Copy link
Copy Markdown
Contributor

It might be interesting to use a generic icon in SVG format, using a <text /> node to display the file extension.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="100%" height="100%" viewBox="0 0 250 250" version="1.1"
    xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve"
    xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
    <path d="M62.5,0c-8.594,0 -15.625,7.031 -15.625,15.625l0,218.75c0,8.594 7.031,15.625 15.625,15.625l156.25,0c8.594,0 15.625,-7.031 15.625,-15.625l0,-171.875l-62.5,-62.5l-109.375,0Z" style="fill:#e2e5e7;fill-rule:nonzero;"/>
    <path d="M187.5,62.5l46.875,0l-62.5,-62.5l0,46.875c0,8.594 7.031,15.625 15.625,15.625Z" style="fill:#b0b7bd;fill-rule:nonzero;"/>
    <path d="M234.375,109.375l-46.875,-46.875l46.875,0l0,46.875Z" style="fill:#cad1d8;fill-rule:nonzero;"/>
    <path d="M195.313,210.938l-148.438,-0.001l0,7.813l148.438,0c4.296,0 7.812,-3.516 7.812,-7.813l-0,-7.812c-0,4.297 -3.516,7.813 -7.813,7.813Z" style="fill:#cad1d8;fill-rule:nonzero;"/>
    <!-- Colour label -->
    <path d="M203.125,203.125c0,4.297 -3.516,7.813 -7.813,7.813l-171.875,-0c-4.296,-0 -7.812,-3.516 -7.812,-7.813l-0,-78.125c-0,-4.297 3.516,-7.813 7.812,-7.813l171.875,0.001c4.297,-0.001 7.813,3.515 7.813,7.812l0,78.125Z" style="fill:#576d7e;fill-rule:nonzero;"/>
    <!-- Extension text node -->
    <text x="47.965px" y="188.125px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:61.512px;fill:#fff;">EXT</text>
</svg>

In this case EXT in text node may be replaced by the file extension.
The color can also be changed.

Some examples :

Render
Generic file-icon
PSD file-icon
PDF file-icon

@LukeTowers

Copy link
Copy Markdown
Member

I like that @damsfx, @AIC-BV are you interested in making that change?

@AIC-BV

AIC-BV commented Feb 21, 2024

Copy link
Copy Markdown
Contributor Author

That looks awesome and will work for 'all' file types.
However, I don't have time to do try this straight away

@AIC-BV

AIC-BV commented Feb 23, 2024

Copy link
Copy Markdown
Contributor Author

@damsfx too fast
#1061

@AIC-BV AIC-BV closed this Feb 23, 2024
@AIC-BV

AIC-BV commented Feb 23, 2024

Copy link
Copy Markdown
Contributor Author

Hmm or is that only in the mediafinder and not in form widget? Might need to extend it. Need to test it first.

@damsfx

damsfx commented Feb 23, 2024 •

Copy link
Copy Markdown
Contributor

Hmm or is that only in the mediafinder and not in form widget? Might need to extend it. Need to test it first.

@AIC-BV It's only related to the mediamanager! ⚠

Your use case is a little more specific ... I think you are using a fileupload field in image mode BUT allow it to accept other file types.
This is cool, because it allows you to have the same GUI to manage all file types.

If it became possible to carry over dynamic icon support to the fileupload formwidget, I think it wouldn't even be necessary to have two different modes of behavior (unless you wanted a more compact interface).

@AIC-BV AIC-BV reopened this Feb 23, 2024
@LukeTowers

Copy link
Copy Markdown
Member

@damsfx perhaps we should take the logic for generating the SVG and put it in a helper class so that the mediamanager and the fileupload field can use it?

@damsfx

damsfx commented Mar 5, 2024

Copy link
Copy Markdown
Contributor

@LukeTowers Yes, that would be perfect, but I'm not sure how to get started with that change.

@LukeTowers

Copy link
Copy Markdown
Member

@bennothommo do you have any thoughts on a desired API / direction to go in for the proposal of making it easier to reuse the work that was just added for generating icons based on file extension in the media manager?

@bennothommo

Copy link
Copy Markdown
Member

Sure, we could use the PHP SVG library. Would be very simple to have it generate text on the fly from what I can see.

@damsfx

damsfx commented Mar 6, 2024

Copy link
Copy Markdown
Contributor

@LukeTowers @bennothommo Isn't using a library to generate an SVG image (a simple string of characters) a bit over-dimmed?
What's needed is just displaying the image where it's needed, wouldn't a simple partial containing the image code be enough?

@AIC-BV

AIC-BV commented Mar 6, 2024

Copy link
Copy Markdown
Contributor Author

Yes the SVG is already made by @damsfx

@LukeTowers

Copy link
Copy Markdown
Member

@damsfx are you still interested in porting your work in #1061 to the FileUpload FormWidget?

@damsfx

damsfx commented Jul 31, 2024

Copy link
Copy Markdown
Contributor

@LukeTowers Yes, of course.
But I still don't know the best way to share a partial representing the structure of the SVG icon and the associated stylesheet between the mediamanager widget and the fileupload formwidget.

@LukeTowers

Copy link
Copy Markdown
Member

@bennothommo bennothommo added the enhancement PRs that implement a new feature or substantial change label Oct 11, 2024
AIC-BV and others added 2 commits September 25, 2026 15:29
Move the MediaManager's file extension SVG icon and its styles into a
shared backend partial and LESS file, and use them in the FileUpload
image modes for files browsers can't display in an <img>, instead of
rendering a broken thumbnail. Replaces the earlier PDF iframe approach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e3cdf970-d389-4d59-b5c6-2ae0b7f0a557
📥 Commits

Reviewing files that changed from the base of the PR and between 0f7b623 and f6a2675.

📒 Files selected for processing (11)
  • modules/backend/assets/less/controls/file-icon.less
  • modules/backend/facades/Backend.php
  • modules/backend/formwidgets/FileUpload.php
  • modules/backend/formwidgets/fileupload/assets/css/fileupload.css
  • modules/backend/formwidgets/fileupload/assets/less/fileupload.less
  • modules/backend/helpers/Backend.php
  • modules/backend/views/file_icon.php
  • modules/backend/widgets/mediamanager/assets/css/mediamanager.css
  • modules/backend/widgets/mediamanager/assets/less/mediamanager.less
  • modules/backend/widgets/mediamanager/partials/_item-icon.php
  • modules/system/assets/ui/less/tokens.less
💤 Files with no reviewable changes (1)
  • modules/backend/widgets/mediamanager/assets/less/mediamanager.less
🚧 Files skipped from review as they are similar to previous changes (1)
  • modules/backend/formwidgets/fileupload/assets/less/fileupload.less

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.


Walkthrough

The change adds a shared SVG file-icon renderer and file-type styles. The media manager now uses the shared renderer and styles. File-upload responses include an icon for extensions outside the browser-displayable image list. Existing-file and upload previews display the icon when available and otherwise use the thumbnail. The upload styles set icon dimensions for single-image and multi-image previews.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to f6a26

Default image previews and extension icons follow the intended behavior. Custom extensions outside the fixed displayable set may show icons instead of thumbnails, but the repository does not establish that this is a supported-preview regression.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 0f7b6

The new icon is escaped before display, and the upload’s existing validation and attachment flow is unchanged. A failure while rendering an icon could nevertheless leave a saved file behind while reporting that the upload failed.

Retained concerns

  • Low · reliability · inferred: If icon rendering fails after a non-image file is saved and attached, the handler returns an error without visible rollback or a file ID for client cleanup. A repeated upload could create another attachment. This is a conditional failure-containment concern, not an observed runtime failure.
Security review details

Security Blast Radius

  • inferred — The new post-write rendering path applies to FileUpload widgets accepting extensions outside the displayable-image list. Any resulting stranded files would affect their backing file storage or relations; cross-tenant reachability is not established.

Security Findings and Attack Paths

  • inferred — No markup-injection path is established through the new icon field: the client inserts server-rendered markup, but the extension interpolated into that markup is escaped. No runtime exploit of the post-write failure path is established.

Trust Boundaries and Controls

  • observed — The upload method retains file validity, size, configured extension, and optional MIME checks before saving. The inspected method does not establish the external route or permission policy; its public visibility and upload flow predate the icon response.

Resilience and Maintainability Implications

  • inferred — Returning an error after attachment without returning its ID weakens failure containment for an affected upload; rollback or deduplication outside the inspected handler remains unknown.

Hardening Proposals

  • proposed — Ensure icon generation cannot turn a completed write into a failed upload response, or pair post-write rendering failures with explicit compensation or idempotent recovery.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 9 files. (6 skipped: 6 …
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding file extension icons for non-image files in the FileUpload widget.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@AIC-BV AIC-BV changed the title [add] support for displaying PDF file Show file extension icons for non-image files in the FileUpload widget Sep 25, 2026
@AIC-BV
AIC-BV marked this pull request as ready for review September 25, 2026 14:01
@AIC-BV

AIC-BV commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

See 1st message in conversation

@LukeTowers LukeTowers left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge conflicts need to be resolved, the PR description need screenshots / screen recordings so I can review it easier, and is there any way we could make the medialibrary and the fileupload widget use the same base functionality for file type icons?

AIC-BV and others added 2 commits October 8, 2026 12:12
Moves the CSS variable colours from wintercms#1541 into the shared file-icon.less and recompiles fileupload.css and mediamanager.css.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The FileUpload widget and the MediaManager both render the icon through the helper instead of including the partial by path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AIC-BV

AIC-BV commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review @LukeTowers!

Merge conflicts: resolved. They all came from #1541: the .file-icon rules this PR moves out of mediamanager.less had been switched to CSS variables there. Those variables now live in the shared controls/file-icon.less, and only fileupload.css and mediamanager.css were recompiled.

Screenshots: there's a before/after table at the bottom of the description (PDF and DOCX in an image-multi field).

Shared base for the icons: both widgets already shared the same SVG markup and styles. I've now put that behind a helper, so neither widget points at a partial path anymore:

  • Backend::fileIcon(string $extension) on the Backend helper, next to Backend::dateTime(), renders the backend::file_icon view.
  • The styles are in modules/backend/assets/less/controls/file-icon.less, imported by both mediamanager.less and fileupload.less.
  • The FileUpload widget and the MediaManager's _item-icon.php both call Backend::fileIcon().

Each widget still decides when to show the icon. The MediaManager does it by item type. FileUpload uses a fixed list of extensions that browsers can display in an <img>, because imageExtensions is configurable and some installs add PDFs to it (exactly the case this PR fixes). If you'd rather have the helper in Storm's File facade, like you suggested earlier in this thread, I'm happy to move it.

Side note, unrelated to this PR: on current develop, winter:util compile less seems unable to resolve the Font Awesome imports. The new allowed import roots (themes/plugins/modules) don't include node_modules, so the import resolves to an empty file and the build fails on @fa-var-500px. I worked around it locally for these two bundles. Want me to open a separate issue for it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement PRs that implement a new feature or substantial change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants