Skip to content

Add file attachments to comments#108

Open
ItsMalikJones wants to merge 4 commits into
mainfrom
feat/32-comment-attachments
Open

Add file attachments to comments#108
ItsMalikJones wants to merge 4 commits into
mainfrom
feat/32-comment-attachments

Conversation

@ItsMalikJones
Copy link
Copy Markdown
Contributor

Closes #32

Summary

Adds optional file attachments to comments. Users can attach one or more files when writing a comment; images render as thumbnails and other file types render as downloadable chips beneath the comment body. The feature is opt-in and disabled by default, so existing installations are unaffected until they explicitly enable it and publish the new migration.

What's included

  • New comment_attachments table with a publishable migration (create_commentions_attachments_table), wired into the package's migration tag and the test suite.
  • CommentAttachment modelbelongsTo a comment, resolves its table via Config, exposes getUrl() and isImage(), and deletes its underlying file from the configured disk when the record is deleted.
  • StoreCommentAttachments action — persists uploaded files to the configured disk/directory and creates the attachment records, following the existing Action::run() convention. Logs a warning instead of silently dropping a file if a write fails.
  • Per-component toggle via a HasAttachments Filament concern: enableAttachments() / disableAttachments() (accepts a bool or closure) on CommentsEntry, CommentsAction, and CommentsTableAction, falling back to the global config value.
  • Livewire integration in the Comments component using WithFileUploads: pending-file list with per-file removal, upload progress indicator, and validation errors. The attach control is gated on the editor's wasFocused state so it appears alongside the existing Comment/Cancel buttons rather than always being visible.
  • Cleanup: deleting a comment removes its attachment records and their files.
  • Localization: attach_files and uploading strings added across all bundled locales (ar, en, es, fr, nl, ro).
  • Eager loading: attachments added to the comment query to avoid N+1s in the list.

Configuration

A new attachments config block controls the feature:

'attachments' => [
    'enabled' => env('COMMENTIONS_ATTACHMENTS_ENABLED', false),
    'disk' => env('COMMENTIONS_ATTACHMENTS_DISK', 'public'),
    'directory' => env('COMMENTIONS_ATTACHMENTS_DIRECTORY', 'commentions-attachments'),
    'max_size' => (int) env('COMMENTIONS_ATTACHMENTS_MAX_SIZE', 10240), // KB, per file
    'max_files' => (int) env('COMMENTIONS_ATTACHMENTS_MAX_FILES', 5),
    'accepted_mime_types' => [/* safe image/document allowlist */],
],

The README documents enabling globally vs. per-component, all options, and the deletion behavior.

Security considerations

  • accepted_mime_types ships with a safe allowlist (common images + documents) rather than allowing any type, and is validated against each file's actual contents (mimetypes rule). The README includes a warning that emptying the allowlist permits browser-executable types (e.g. image/svg+xml, text/html) to be served from a public disk.
  • Upload validation (size, count, MIME) runs before the comment is created, so a rejected upload never produces an orphaned comment.
  • The attachmentsEnabled Livewire property is marked #[Locked], so a client cannot flip a closure-based gate by tampering with the request payload.
  • Stored filenames use Laravel's hashed storage paths; the original client filename is only used as display text (escaped in Blade).

Testing

Adds tests/Livewire/CommentAttachmentTest.php covering: attaching a file, the control's visibility (disabled, enabled, and focus-gated), file + record cleanup on comment deletion, oversized-file rejection, disallowed-MIME rejection, pending-file removal, the per-component toggle, and image detection. Run with ./vendor/bin/pest.

Lets users attach files when creating a comment.

- New comment_attachments table (migration stub) and CommentAttachment model
  with a comment() relation, getUrl() and isImage() helpers.
- Comment::attachments() relation; commentsQuery() eager-loads attachments.
- The Comments component uses WithFileUploads — files are validated (size,
  count, optional MIME allowlist), stored via a StoreCommentAttachments action
  on the configured disk, and linked to the comment on save.
- Opt-in: a `commentions.attachments` config block (disabled by default) plus
  CommentsEntry::make()->enableAttachments(), threaded through the entry and
  actions to the Comments component.
- Attachments render below each comment (image previews / download links).
- Deleting a comment deletes its attachment rows and files from disk.
@ItsMalikJones ItsMalikJones self-assigned this May 31, 2026
- Ship a safe default accepted_mime_types allowlist (was empty/any),
  preventing browser-executable types (e.g. svg/html) being served from
  the public disk; add a regression test for rejected types.
- Lock the attachmentsEnabled Livewire property so a closure-based gate
  (e.g. enableAttachments(fn () => $user->isAdmin())) cannot be flipped
  on by tampering with the request payload.
- Log a warning when an upload fails to store instead of silently dropping.
- Document the attachments feature and the public-disk MIME risk in the README.
@ItsMalikJones ItsMalikJones force-pushed the feat/32-comment-attachments branch from 1370d06 to e7d3aa2 Compare May 31, 2026 03:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

File upload

1 participant