Skip to content

MIRROR PR: feat(sharebymail): dispatch BeforeShare*MailSentEvent before each mail send - #277

Draft
printminion-co wants to merge 46 commits into
masterfrom
feat/sharebymail-before-mail-sent-event
Draft

MIRROR PR: feat(sharebymail): dispatch BeforeShare*MailSentEvent before each mail send#277
printminion-co wants to merge 46 commits into
masterfrom
feat/sharebymail-before-mail-sent-event

Conversation

@printminion-co

Copy link
Copy Markdown

Summary

Upstream-ready extract of IONOS PR #262, prepared for contribution to nextcloud/server.
Introduces a PSR-14 event dispatched immediately before every mailer->send() in
ShareByMailProvider, so listeners can intercept/replace native SMTP delivery for share-by-mail
notifications.

  • New apps/sharebymail/lib/Event/ classes: AbstractBeforeShareMailSentEvent (base) +
    BeforeShareMailSentEvent, BeforeSharePasswordMailSentEvent, BeforeShareNoteMailSentEvent
  • Dispatch points in sendEmail(), sendPassword(), sendPasswordToOwner(), sendNote();
    markMailHandled() skips native send (no silent fallback)
  • $templateData refactor + fix so createPasswordSendActivity no longer fires when a listener
    suppresses the mail
  • Test coverage + composer autoload entries

Scope / cleanup vs PR #262

  • Fork-only content dropped: the apps-custom/nc_ionos_processes submodule bump and the
    hand-edited generated autoload noise. Diff is apps/sharebymail/** only.
  • Commits reworded to Conventional Commits (no IONOS(...)/HDNEXT markers); rebased onto current
    master. Drift fixes: dropped an unused import; replaced withConsecutive() (removed in
    PHPUnit 10) with a willReturnCallback idiom; autoload entries in ksort order.

Verification (dev container)

  • ./autotest.sh sqlite apps/sharebymail/tests/ShareByMailProviderTest.phpOK (40 tests, 440 assertions)
  • composer cs:check clean on the changed files
  • psalm skipped (container PHP 8.3.6 < required 8.3.16)

Follow-ups

  • ⚠️ The refactor(sharebymail) commit is missing a Signed-off-by — will be added + force-pushed.
  • External nextcloud/server PR to be opened separately by the human contributor (AI policy).

AI assistance

Branch prepared with AI assistance (cherry-pick, reword, drift-conflict resolution). Underlying
code is human-authored (PR #262).

@printminion-co
printminion-co force-pushed the feat/sharebymail-before-mail-sent-event branch from c09ac83 to f2b7041 Compare July 9, 2026 14:25
@printminion-co
printminion-co requested a review from Copilot July 9, 2026 14:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR adds PSR-14 events in apps/sharebymail that are dispatched immediately before each native mailer->send() call in ShareByMailProvider, allowing listeners to suppress/replace SMTP delivery for share-by-mail notifications (share link, password, and note mails).

Changes:

  • Introduce BeforeShare*MailSentEvent classes (with a shared abstract base) to carry the prepared IMessage and recipients and allow suppressing native sending via markMailHandled().
  • Dispatch these events from ShareByMailProvider::sendEmail(), sendPassword(), sendPasswordToOwner(), and sendNote(), skipping native send when handled.
  • Update unit tests and app-local composer autoload classmaps to include the new event classes.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
apps/sharebymail/lib/ShareByMailProvider.php Dispatch new events before each mail send and skip native send when handled; refactor template data arrays.
apps/sharebymail/tests/ShareByMailProviderTest.php Extend tests to assert event dispatch and updated template data; add tests around “mail handled” behavior for password flows.
apps/sharebymail/lib/Event/AbstractBeforeShareMailSentEvent.php Add common event base carrying share, recipients, and rendered message plus “handled” flag.
apps/sharebymail/lib/Event/BeforeShareMailSentEvent.php Add event for share-link notification mails.
apps/sharebymail/lib/Event/BeforeSharePasswordMailSentEvent.php Add event for password notification mails (recipient + owner cases).
apps/sharebymail/lib/Event/BeforeShareNoteMailSentEvent.php Add event for note-update notification mails.
apps/sharebymail/composer/composer/autoload_static.php Register new event classes in the app-local composer autoloader map.
apps/sharebymail/composer/composer/autoload_classmap.php Register new event classes in the app-local composer classmap.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +26 to +28
/**
* @param string[] $resolvedEmails validated recipients
*/

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fixed in commit fix(sharebymail): fix docstring and add isMailHandled tests for sendEmail and sendNote. The @param docstring now reads "resolved recipient addresses (not guaranteed to be validated)" to accurately reflect that call sites like sendNote() and sendPasswordToOwner() do not validate addresses before dispatching.

Comment on lines +411 to +415
$event = new BeforeShareMailSentEvent($share, $emails, $message, $templateData);
$this->eventDispatcher->dispatchTyped($event);
if ($event->isMailHandled()) {
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Added testSendEmailDoesNotSendWhenMailHandledByListener in commit fix(sharebymail): fix docstring and add isMailHandled tests for sendEmail and sendNote. The test sets up a listener that calls markMailHandled() on the dispatched BeforeShareMailSentEvent and asserts mailer->send() is never called.

Comment on lines +585 to +589
$event = new BeforeShareNoteMailSentEvent($share, [$recipient], $message, $templateData);
$this->eventDispatcher->dispatchTyped($event);
if ($event->isMailHandled()) {
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Added testSendNoteDoesNotSendWhenMailHandledByListener in commit fix(sharebymail): fix docstring and add isMailHandled tests for sendEmail and sendNote. The test marks BeforeShareNoteMailSentEvent as handled via a listener and asserts mailer->send() is never invoked.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

@printminion-co
printminion-co force-pushed the feat/sharebymail-before-mail-sent-event branch 2 times, most recently from d928ae7 to edc6c9c Compare July 10, 2026 07:21
@printminion-co printminion-co changed the title feat(sharebymail): dispatch BeforeShare*MailSentEvent before each mail send MIRROR PR: feat(sharebymail): dispatch BeforeShare*MailSentEvent before each mail send Jul 10, 2026
@printminion-co
printminion-co marked this pull request as draft July 10, 2026 08:52
icewind1991 and others added 4 commits July 16, 2026 07:57
Signed-off-by: Robin Appelman <robin@icewind.nl>
Signed-off-by: Carl Schwan <carlschwan@kde.org>
…rd protected in IShare->isPasswordProtected()

Signed-off-by: Tobias Knöppler <tobias@knoeppler.org>
Cache::copyFromCache() rebuilt the target's cache row via
cacheEntryToArray(), which only carried over a boolean `encrypted`
flag and dropped the real `encryptedVersion` count. Since
View::copy() unconditionally calls copyFromCache() after the storage
copy completes, this silently overwrote the correct encryptedVersion
that Encryption::updateEncryptedVersion() had just set, collapsing it
back to 0/1 on every copy of a file whose encryptedVersion was above 1.

cacheEntryToArray() now includes the source entry's encryptedVersion
when it is encrypted. The existing encrypted-to-non-encrypted-storage
override in copyFromCache() also clears encryptedVersion alongside
`encrypted`, since normalizeData() prefers encryptedVersion over
`encrypted` when both are present in the update data.

Signed-off-by: Kent Delante <kent@delante.me>
Assisted-by: ClaudeCode:claude-sonnet-5
Signed-off-by: Kent Delante <kent@delante.me>
Assisted-by: ClaudeCode:claude-sonnet-5
@printminion-co
printminion-co force-pushed the feat/sharebymail-before-mail-sent-event branch from edc6c9c to 9ad378d Compare July 17, 2026 14:03
susnux and others added 11 commits July 17, 2026 16:19
There was a bug in the filepicker (dialogs library) that passed always
the current folder as selected node even if not pickable (`setCanPick`).
So updated the library.

But we also must check if we have any nodes selected to enable the
copy and move buttons.

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
…r-share-is-pw-protected

Refactor: Centralize logic for checking if a share is password protected
…ize-fail

fix: don't die if we can't encode file metadata on upload
Robin Appleman's SMB library is designed so that 'null' is to be used
when no explicit workgroup is set:

    final class BasicAuth implements IAuth {
        /** @var string */
        private $username;
        /** @var string|null */
        private $workgroup;
        /** @var string */
        private $password;
    //...

However, it previously was loose with its checks and would still treat
any falsy value (e.g. an empty string) as "not specified" when forming
arguments for the various underlying utilities, such as `smbclient`.

icewind/SMB@4a93467905 updated it's handling to be more strict and as
such will now treat empty strings as distinct from null values for
workgroups. An empty value for workgroup often doesn't make sense,
unless the backend tool happens to convert it to "WORKGROUP". In the
case of the `smbclient` utility, passing an empty string for the
$workgroup paramters results in `-W ''` being used which is invalid
syntax and so the invocation fails resulting in the user seeing an
`[Icewind\SMB\Exception\ConnectionRefusedException]` error. Therefore,
it is important to always pass 'null' to this constructor for $workgroup
when there isn't one.

Most paths in the 'files_external' app already handle this correctly,
but this now altered path originally just took the raw value from the
app backend and passed it as-is. Assuming the user left the "Domain"
box empty in the frontend, this would be an empty string. Now the
the SMB library's interface is correctly honored in that case.

It's arguable that the SMB library should simply be updated to
formally accept '' as a valid input meaning "no workgroup", restoring
the old behavior in a canonical fashion, as there is almost no
situation where one would want to pass an empty string and have it
mean anything else; but regardless, there is no harm in being more
explicit on the nextcloud/server side as well.

Fixes nextcloud#58445.

Signed-off-by: Christian Heimlich <chris@pcserenity.com>
The previous implementation executes "ps" with flags -p and -o to check
whether a job process was still running. This approach has portability
issues regarding e.g. BusyBox ps (Alpine Linux) where `ps -p` fails or
BSD/macOS where `ps -o` fails.

Replace with `posix_kill($pid, 0)`, the standard POSIX way to probe
process existence without delivering a signal. EPERM is treated as
"process exists but not owned by current user". This should be available
on all supported platforms.

Fixes: 60ce92a
Suggested-by: Michele Marcionelli
Signed-off-by: Stefan Kalscheuer <stefan@stklcode.de>
Snowflake ID in `JobRuns:deleteBefore()` is computed from given
timestamp, but immediately overwritten by a literal value making the
expiration setting have no effect.

This looks like a leftover artifact from development that should be
cleaned up. Remove the hardcoded ID to restore the desired behavior.

Fixes: dc5499a
Suggested-by: Michele Marcionelli
Signed-off-by: Stefan Kalscheuer <stefan@stklcode.de>
fix(jobs): resolve portability issues with background job cleanup
…iles-smb-auth

fix(files_external/SMB): Use 'null' explicitly for no workgroup
- Do not use hard coded user-id.
- Properly clean-up created user in `finally` clause

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
nextcloud-bot and others added 29 commits July 18, 2026 00:23
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
fix(files): properly handle folders without permissions
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
Signed-off-by: GitHub <noreply@github.com>
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
Installing Nextcloud might take some time.
Marking those tests as slow to prevent flaky CI.

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
test(users): prevent flaky tests with hardcoded values
…che-include-encrypted-version

fix(files): preserve encryptedVersion when copying cache entries
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
test(files): wait for actual requests when deleting files
test(files): wait for PUT before continue with tests
Signed-off-by: Joas Schilling <coding@schilljs.com>
…me-parameters-as-sensitive

fix: Mark some parameters as sensitive
…lay-name

perf(ShareUser): Only fetch display name instead of user object
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
…e-constructor-properties

Use constructor properties in Sharing
…ateData variables

Extract the inline array literals passed to each createEMailTemplate() call
into named $templateData variables. For sendNote(), which previously passed no
data array, add a minimal array so all four send methods are consistent.

Zero behaviour change — $templateData is passed straight through to
createEMailTemplate() with identical contents.

Signed-off-by: Misha M.-Kupriyanov <kupriyanov@strato.de>
…ch mail send

Dispatch a typed PSR-14 event immediately before every mailer->send() call
in ShareByMailProvider so that external listeners can intercept and replace
Nextcloud's native SMTP delivery.

Event hierarchy:
  AbstractBeforeShareMailSentEvent (base: share, resolvedEmails, message,
    markMailHandled / isMailHandled)
  ├── BeforeShareMailSentEvent   – sendEmail()
  ├── BeforeSharePasswordMailSentEvent – sendPassword() + sendPasswordToOwner()
  └── BeforeShareNoteMailSentEvent – sendNote()

Each concrete class holds its own typed $templateData (psalm array-shape)
and exposes named getters (getSenderUserId(), getFileName(), …) instead of
a generic getMailData(): array<string,mixed>.  This avoids defensive
is_string() / null guards in listeners.

$templateData reuses the array already passed to createEMailTemplate(), with
one extra key added for sendEmail(): senderUserId (the raw user ID, distinct
from the display name stored under 'initiator').

The native mailer->send() is skipped when a listener calls markMailHandled().
If the listener's own send throws, the exception propagates and the native
send is also skipped — no silent SMTP fallback.

sendEmail() and sendPassword() are flattened from nested
if (!isMailHandled()) { ... } pyramids to early-return style.

createPasswordSendActivity() in sendPassword() and sendPasswordToOwner() now runs
only when the mail was actually sent: it moved inside the isMailHandled() early
return, so a listener that suppresses the SMTP send no longer produces a false
password-share activity entry.

Signed-off-by: Misha M.-Kupriyanov <kupriyanov@strato.de>
@printminion-co
printminion-co force-pushed the feat/sharebymail-before-mail-sent-event branch from 9ad378d to 3ff2742 Compare July 21, 2026 07:32
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.