Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
b7afadb
feature: send email to internal users of circles when shared with circle
yemkareems Jun 30, 2025
80d58ff
fix: add FileCopyrightText and License-Identifier
yemkareems Jun 30, 2025
222e3d2
fix: refactored service to mail provider and tests adjusted accordingly
yemkareems Jun 30, 2025
a10022d
fix: refactor ShareByCircleProvider class to implement IShareProvider…
yemkareems Jul 1, 2025
e50f772
fix: move helper functions to provider, remove the helper class and r…
yemkareems Jul 1, 2025
0722d76
fix: composer run cs fix
yemkareems Jul 1, 2025
81e87c0
fix: removed the check sharing.enable_share_mail as it is already done
yemkareems Jul 8, 2025
7d2a2a5
fix: removed the DefaultShareProvider dependency for psalm
yemkareems Jul 8, 2025
787bcb4
Merge branch 'master' into feature/notify-internal-users-on-circle-share
yemkareems Jul 9, 2025
bc31bc1
fix: removed comment, corrected text, used user type constant, if con…
yemkareems Jul 16, 2025
05fb5ae
Merge branch 'feature/notify-internal-users-on-circle-share' of githu…
yemkareems Jul 16, 2025
23db5ad
Merge branch 'master' into feature/notify-internal-users-on-circle-share
yemkareems Jul 16, 2025
82acf7c
fix: note was not getting sent in email
yemkareems Jul 23, 2025
4ad4380
fix: initiator moved outside the loop, member check refactored to not…
yemkareems Aug 3, 2025
bb52de4
fix: moved sending email to ShareCreatedSendMail and removed mail sen…
yemkareems Aug 11, 2025
a9a459c
fix: cs fix
yemkareems Aug 11, 2025
c5828fc
fix: user type added to getInheritedMembers, member isLocal check add…
yemkareems Aug 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/Db/ShareWrapperRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public function save(IShare $share, int $parentId = 0): int {
$qb = $this->getShareInsertSql();
$qb->setValue('attributes', $qb->createNamedParameter($this->formatShareAttributes($share->getAttributes())))
->setValue('share_type', $qb->createNamedParameter($share->getShareType()))
->setValue('mail_send', $qb->createNamedParameter($share->getMailSend()))
->setValue('item_type', $qb->createNamedParameter($share->getNodeType()))
->setValue('item_source', $qb->createNamedParameter($share->getNodeId()))
->setValue('file_source', $qb->createNamedParameter($share->getNodeId()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Circles\Provider;
namespace OCA\Circles\Helpers;

use OC\Share20\DefaultShareProvider;
use OCP\Defaults;
Expand All @@ -19,7 +19,7 @@
use OCP\Share\IShareProviderWithNotification;
use Psr\Log\LoggerInterface;

class CircleShareMailProvider extends DefaultShareProvider implements IShareProviderWithNotification {
class CircleShareMailHelper extends DefaultShareProvider implements IShareProviderWithNotification {

public function __construct(
private IMailer $mailer,
Expand All @@ -34,7 +34,9 @@ public function __construct(
public function sendShareNotification(IShare $share, $circle): void {
if ($this->config->getSystemValueBool('sharing.enable_share_mail', true)) {
$circleMembers = $circle->getMembers();
$link = $this->urlGenerator->linkToRouteAbsolute('files_sharing.Accept.accept', ['shareId' => 'ocinternal:' . $share->getId()]);
$link = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', [
'token' => $share->getToken()
]);
foreach ($circleMembers as $member) {
if ($member->getUserType() != 1) {
continue;
Expand Down
12 changes: 12 additions & 0 deletions lib/Model/ShareWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class ShareWrapper extends ManagedModel implements IDeserializable, IQueryRow, J
private ?ShareToken $shareToken = null;
private ?IAttributes $attributes = null;
private bool $hideDownload = false;
private bool $mailSend = true;

public function __construct() {
$this->shareTime = new DateTime();
Expand Down Expand Up @@ -374,6 +375,16 @@ public function setHideDownload(bool $hideDownload): self {
return $this;
}

public function setMailSend(bool $mailSend): self {
$this->mailSend = $mailSend;

return $this;
}

public function getMailSend(): bool {
return $this->mailSend;
}


/**
* @throws IllegalIDChangeException
Expand All @@ -396,6 +407,7 @@ public function getShare(
$share->setHideDownload($this->getHideDownload());
$share->setAttributes($this->getAttributes());
$share->setNote($this->getShareNote());
$share->setMailSend($this->getMailSend());
if ($this->hasShareToken()) {
$password = $this->getShareToken()->getPassword();
if ($password !== '') {
Expand Down
35 changes: 26 additions & 9 deletions lib/ShareByCircleProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace OCA\Circles;

use Exception;
use OC\Share20\DefaultShareProvider;
use OCA\Circles\Exceptions\CircleNotFoundException;
use OCA\Circles\Exceptions\ContactAddressBookNotFoundException;
use OCA\Circles\Exceptions\ContactFormatException;
Expand All @@ -31,12 +32,12 @@
use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\FederatedItems\Files\FileShare;
use OCA\Circles\FederatedItems\Files\FileUnshare;
use OCA\Circles\Helpers\CircleShareMailHelper;
use OCA\Circles\Model\Federated\FederatedEvent;
use OCA\Circles\Model\Member;
use OCA\Circles\Model\Probes\CircleProbe;
use OCA\Circles\Model\Probes\DataProbe;
use OCA\Circles\Model\ShareWrapper;
use OCA\Circles\Provider\CircleShareMailProvider;
use OCA\Circles\Service\CircleService;
use OCA\Circles\Service\EventService;
use OCA\Circles\Service\FederatedEventService;
Expand All @@ -58,15 +59,15 @@
use OCP\Share\Exceptions\IllegalIDChangeException;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IShare;
use OCP\Share\IShareProvider;
use OCP\Share\IShareProviderWithNotification;
use Psr\Log\LoggerInterface;

/**
* Class ShareByCircleProvider
*
* @package OCA\Circles
*/
class ShareByCircleProvider implements IShareProvider {
class ShareByCircleProvider extends DefaultShareProvider implements IShareProviderWithNotification {
use TArrayTools;
use TStringTools;
use TNCLogger;
Expand All @@ -85,7 +86,7 @@ public function __construct(
private FederatedEventService $federatedEventService,
private CircleService $circleService,
private EventService $eventService,
private CircleShareMailProvider $circleShareMailProvider,
private CircleShareMailHelper $circleShareMailHelper,
) {
}

Expand Down Expand Up @@ -143,13 +144,9 @@ public function create(IShare $share): IShare {

$circle = $this->circleService->probeCircle($share->getSharedWith(), $circleProbe, $dataProbe);
$share->setToken($this->token(15));
$share->setMailSend(true);
$owner = $circle->getInitiator();
$this->shareWrapperService->save($share);
try {
$this->circleShareMailProvider->sendShareNotification($share, $circle);
} catch (Exception $e) {
$this->logger->error('sending sharing email failed', [$e->getMessage()]);
}

try {
$wrappedShare = $this->shareWrapperService->getShareById((int)$share->getId());
Expand All @@ -168,6 +165,26 @@ public function create(IShare $share): IShare {
return $wrappedShare->getShare($this->rootFolder, $this->userManager, $this->urlGenerator);
}

/**
* @inheritDoc
*/
public function sendMailNotification(IShare $share): bool {
$circleProbe = new CircleProbe();
$dataProbe = new DataProbe();
$dataProbe->add(DataProbe::OWNER)
->add(DataProbe::INITIATOR, [DataProbe::BASED_ON]);

$circle = $this->circleService->probeCircle($share->getSharedWith(), $circleProbe, $dataProbe);
try {
$this->circleShareMailHelper->sendShareNotification($share, $circle);
return true;
} catch (Exception $e) {
//fail silently as share created already and mail sending alone failed
$this->logger->error('Circle share internal email sending failed', [$e->getMessage()]);
}

return false;
}

/**
* @param IShare $share
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
use OCA\Circles\Provider\CircleShareMailProvider;

use OCA\Circles\Helpers\CircleShareMailHelper;
use OCP\Defaults;
use OCP\IConfig;
use OCP\IL10N;
Expand All @@ -16,7 +17,7 @@
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;

class CircleShareMailProviderTest extends TestCase {
class CircleShareMailHelperTest extends TestCase {
public function testSendShareNotificationCallsSendUserShareMail(): void {
$share = $this->createConfiguredMock(IShare::class, [
'getId' => 42,
Expand Down Expand Up @@ -51,7 +52,7 @@ public function testSendShareNotificationCallsSendUserShareMail(): void {
$l10n = $this->createMock(IL10N::class);
$l10n->method('t')->willReturnCallback(fn ($text, $args = []) => vsprintf($text, $args));

$provider = $this->buildProvider([
$provider = $this->buildHelper([
'userManager' => $userManager,
'mailer' => $mailer,
'urlGenerator' => $urlGenerator,
Expand All @@ -76,7 +77,7 @@ public function testSendShareNotificationCallsSendUserShareMail(): void {
public function testNoMailIsSentWhenSharingMailIsDisabled(): void {
$config = $this->createConfiguredMock(IConfig::class, ['getSystemValueBool' => false]);

$provider = $this->buildProvider(['config' => $config], ['sendUserShareMail']);
$provider = $this->buildHelper(['config' => $config], ['sendUserShareMail']);

$share = $this->createMock(IShare::class);
$circle = $this->createConfiguredMock(\OCA\Circles\Model\Circle::class, ['getMembers' => []]);
Expand All @@ -100,7 +101,7 @@ public function testNonUserTypeMembersAreSkipped(): void {
$userManager = $this->createMock(IUserManager::class);
$userManager->method('get')->with('user123')->willReturn($user);

$provider = $this->buildProvider([
$provider = $this->buildHelper([
'userManager' => $userManager
], ['sendUserShareMail']);

Expand All @@ -117,7 +118,7 @@ public function testNullUserIsSkipped(): void {
$userManager = $this->createMock(IUserManager::class);
$userManager->method('get')->with('user123')->willReturn(null);

$provider = $this->buildProvider([
$provider = $this->buildHelper([
'userManager' => $userManager
], ['sendUserShareMail']);

Expand All @@ -144,7 +145,7 @@ public function testInvalidOrEmptyEmailIsSkipped(): void {
$mailer = $this->createMock(IMailer::class);
$mailer->method('validateMailAddress')->willReturn(false);

$provider = $this->buildProvider([
$provider = $this->buildHelper([
'userManager' => $userManager,
'mailer' => $mailer
], ['sendUserShareMail']);
Expand Down Expand Up @@ -197,7 +198,7 @@ public function testAllValidMembersReceiveEmail(): void {
'getNote' => ''
]);

$provider = $this->buildProvider([
$provider = $this->buildHelper([
'userManager' => $userManager,
'mailer' => $mailer,
'urlGenerator' => $urlGenerator
Expand All @@ -207,7 +208,7 @@ public function testAllValidMembersReceiveEmail(): void {
$provider->sendShareNotification($share, $circle);
}

private function buildProvider(array $overrides = [], array $mockMethods = []): CircleShareMailProvider {
private function buildHelper(array $overrides = [], array $mockMethods = []): CircleShareMailHelper {
$mailer = $overrides['mailer'] ?? $this->createMock(IMailer::class);
$l10n = $overrides['l10n'] ?? $this->createMock(IL10N::class);
$logger = $overrides['logger'] ?? $this->createMock(LoggerInterface::class);
Expand All @@ -216,7 +217,7 @@ private function buildProvider(array $overrides = [], array $mockMethods = []):
$userManager = $overrides['userManager'] ?? $this->createMock(IUserManager::class);
$defaults = $overrides['defaults'] ?? $this->createConfiguredMock(Defaults::class, ['getName' => 'Nextcloud']);

$builder = $this->getMockBuilder(CircleShareMailProvider::class)
$builder = $this->getMockBuilder(CircleShareMailHelper::class)
->setConstructorArgs([
$mailer,
$l10n,
Expand Down
Loading