Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ The rating depends on the installed text processing backend. See [the rating ove

Learn more about the Nextcloud Ethical AI Rating [in our blog](https://nextcloud.com/blog/nextcloud-ethical-ai-rating/).
]]></description>
<version>5.10.0-rc.1</version>
<version>5.10.0-rc.2</version>
<licence>agpl</licence>
<author homepage="https://github.com/ChristophWurst">Christoph Wurst</author>
<author homepage="https://github.com/GretaD">GretaD</author>
Expand Down
8 changes: 6 additions & 2 deletions lib/Controller/DraftsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ public function create(
?int $sendAt = null,
?int $draftId = null,
bool $requestMdn = false,
bool $isPgpMime = false) : JsonResponse {
bool $isPgpMime = false,
?string $governanceLabelId = null) : JsonResponse {
$effectiveUserId = $this->delegationService->resolveAccountUserId($accountId, $this->userId);
$account = $this->accountService->find($effectiveUserId, $accountId);
if ($draftId !== null) {
Expand All @@ -109,6 +110,7 @@ public function create(
$message->setSmimeEncrypt($smimeEncrypt);
$message->setRequestMdn($requestMdn);
$message->setPgpMime($isPgpMime);
$message->setGovernanceLabelId($governanceLabelId);

if (!empty($smimeCertificateId)) {
$smimeCertificate = $this->smimeService->findCertificate($smimeCertificateId, $effectiveUserId);
Expand Down Expand Up @@ -161,7 +163,8 @@ public function update(int $id,
?int $smimeCertificateId = null,
?int $sendAt = null,
bool $requestMdn = false,
bool $isPgpMime = false): JsonResponse {
bool $isPgpMime = false,
?string $governanceLabelId = null): JsonResponse {
$effectiveUserId = $this->delegationService->resolveLocalMessageUserId($id, $this->userId);
$message = $this->service->getMessage($id, $effectiveUserId);
$account = $this->accountService->find($effectiveUserId, $accountId);
Expand All @@ -182,6 +185,7 @@ public function update(int $id,
$message->setSmimeEncrypt($smimeEncrypt);
$message->setRequestMdn($requestMdn);
$message->setPgpMime($isPgpMime);
$message->setGovernanceLabelId($governanceLabelId);

if (!empty($smimeCertificateId)) {
$smimeCertificate = $this->smimeService->findCertificate($smimeCertificateId, $effectiveUserId);
Expand Down
53 changes: 53 additions & 0 deletions lib/Controller/GovernanceLabelsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Mail\Controller;

use OCA\Mail\Http\JsonResponse;
use OCA\Mail\Http\TrapError;
use OCA\Mail\Service\GovernanceLabelService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\Attribute\Route;
use OCP\IRequest;

#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class GovernanceLabelsController extends Controller {

public function __construct(
string $appName,
IRequest $request,
private GovernanceLabelService $governanceLabelService,
) {
parent::__construct($appName, $request);
}

#[TrapError]
#[NoAdminRequired]
#[Route(Route::TYPE_FRONTPAGE, verb: 'GET', url: '/api/governance/labels')]
public function index(): JsonResponse {
return JsonResponse::success(
array_values($this->governanceLabelService->getLabels(true)),
);
}

#[TrapError]
#[NoAdminRequired]
#[Route(Route::TYPE_FRONTPAGE, verb: 'GET', url: '/api/governance/labels/{id}')]
public function show(string $id): JsonResponse {
$label = $this->governanceLabelService->getLabel($id);
if ($label === null) {
return JsonResponse::fail([], Http::STATUS_NOT_FOUND);
}

return JsonResponse::success($label);
}
}
4 changes: 4 additions & 0 deletions lib/Controller/OutboxController.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public function create(
?int $sendAt = null,
bool $requestMdn = false,
bool $isPgpMime = false,
?string $governanceLabelId = null,
): JsonResponse {
$effectiveUserId = $this->delegationService->resolveAccountUserId($accountId, $this->userId);
$account = $this->accountService->find($effectiveUserId, $accountId);
Expand All @@ -126,6 +127,7 @@ public function create(
$message->setSmimeSign($smimeSign);
$message->setSmimeEncrypt($smimeEncrypt);
$message->setRequestMdn($requestMdn);
$message->setGovernanceLabelId($governanceLabelId);

if (!empty($smimeCertificateId)) {
$smimeCertificate = $this->smimeService->findCertificate($smimeCertificateId, $effectiveUserId);
Expand Down Expand Up @@ -198,6 +200,7 @@ public function update(
?int $sendAt = null,
bool $requestMdn = false,
bool $isPgpMime = false,
?string $governanceLabelId = null,
): JsonResponse {
$effectiveUserId = $this->delegationService->resolveLocalMessageUserId($id, $this->userId);
$message = $this->service->getMessage($id, $effectiveUserId);
Expand All @@ -219,6 +222,7 @@ public function update(
$message->setSmimeSign($smimeSign);
$message->setSmimeEncrypt($smimeEncrypt);
$message->setRequestMdn($requestMdn);
$message->setGovernanceLabelId($governanceLabelId);

// Reset the status to make it retryable.
$message->setFailed(false);
Expand Down
7 changes: 7 additions & 0 deletions lib/Controller/PageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use OCA\Mail\Service\AliasesService;
use OCA\Mail\Service\Classification\ClassificationSettingsService;
use OCA\Mail\Service\ContextChat\ContextChatSettingsService;
use OCA\Mail\Service\GovernanceLabelService;
use OCA\Mail\Service\InternalAddressService;
use OCA\Mail\Service\OutboxService;
use OCA\Mail\Service\QuickActionsService;
Expand Down Expand Up @@ -89,6 +90,7 @@ public function __construct(
private IAppManager $appManager,
private ContextChatSettingsService $contextChatSettingsService,
private ClassificationSettingsService $classificationSettingsService,
private GovernanceLabelService $governanceLabelService,
) {
parent::__construct($appName, $request);

Expand Down Expand Up @@ -139,6 +141,11 @@ public function index(): TemplateResponse {
$this->appManager->getAppVersion('mail'),
);

$this->initialStateService->provideInitialState(
'governance-labels-available',
$this->governanceLabelService->isGovernanceAvailable(),
);

$mailAccounts = $this->accountService->findByUserId($this->userId);
$accountsJson = [];
foreach ($mailAccounts as $mailAccount) {
Expand Down
6 changes: 6 additions & 0 deletions lib/Db/LocalMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
* @method setRaw(string|null $raw)
* @method bool getRequestMdn()
* @method setRequestMdn(bool $mdn)
* @method string|null getGovernanceLabelId()
* @method void setGovernanceLabelId(?string $governanceLabelId)
*/
class LocalMessage extends Entity implements JsonSerializable {
public const TYPE_OUTGOING = 0;
Expand Down Expand Up @@ -141,6 +143,9 @@ class LocalMessage extends Entity implements JsonSerializable {
/** @var bool */
protected $requestMdn;

/** @var string|null */
protected $governanceLabelId;

public function __construct() {
$this->addType('type', 'integer');
$this->addType('accountId', 'integer');
Expand Down Expand Up @@ -195,6 +200,7 @@ public function jsonSerialize() {
'status' => $this->getStatus(),
'raw' => $this->getRaw(),
'requestMdn' => $this->getRequestMdn(),
'governanceLabelId' => $this->getGovernanceLabelId(),
];
}

Expand Down
4 changes: 4 additions & 0 deletions lib/Db/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@
* @method void setEncrypted(bool|null $encrypted)
* @method bool getMentionsMe()
* @method void setMentionsMe(bool $isMentionned)
* @method string|null getGovernanceLabelId()
* @method void setGovernanceLabelId(?string $governanceLabelId)
*/
class Message extends Entity implements JsonSerializable {
private const MUTABLE_FLAGS = [
Expand Down Expand Up @@ -117,6 +119,7 @@ class Message extends Entity implements JsonSerializable {
protected $imipProcessed = false;
protected $imipError = false;
protected $mentionsMe = false;
protected $governanceLabelId;

/**
* @var bool|null
Expand Down Expand Up @@ -375,6 +378,7 @@ public function jsonSerialize() {
'avatar' => $this->avatar?->jsonSerialize(),
'fetchAvatarFromClient' => $this->fetchAvatarFromClient,
'attachments' => $this->getAttachments(),
'governanceLabelId' => $this->getGovernanceLabelId(),
];
}
}
6 changes: 6 additions & 0 deletions lib/Db/MessageMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,7 @@ public function updatePreviewDataBulk(Message ...$messages): array {
->set('imip_message', $query->createParameter('imip_message'))
->set('encrypted', $query->createParameter('encrypted'))
->set('mentions_me', $query->createParameter('mentions_me'))
->set('governance_label_id', $query->createParameter('governance_label_id'))
->where($query->expr()->andX(
$query->expr()->eq('uid', $query->createParameter('uid')),
$query->expr()->eq('mailbox_id', $query->createParameter('mailbox_id'))
Expand Down Expand Up @@ -613,6 +614,11 @@ public function updatePreviewDataBulk(Message ...$messages): array {
$query->setParameter('imip_message', $message->isImipMessage(), IQueryBuilder::PARAM_BOOL);
$query->setParameter('encrypted', $message->isEncrypted(), IQueryBuilder::PARAM_BOOL);
$query->setParameter('mentions_me', $message->getMentionsMe(), IQueryBuilder::PARAM_BOOL);
$query->setParameter(
'governance_label_id',
$message->getGovernanceLabelId(),
$message->getGovernanceLabelId() === null ? IQueryBuilder::PARAM_NULL : IQueryBuilder::PARAM_STR
);

$query->executeStatement();
}
Expand Down
13 changes: 10 additions & 3 deletions lib/IMAP/MessageMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
use OCA\Mail\Html\Parser;
use OCA\Mail\IMAP\Charset\Converter;
use OCA\Mail\Model\IMAPMessage;
use OCA\Mail\Service\GovernanceLabelService;
use OCA\Mail\Service\SmimeService;
use OCA\Mail\Support\PerformanceLoggerTask;
use OCP\AppFramework\Db\DoesNotExistException;
Expand Down Expand Up @@ -891,6 +892,10 @@ public function getBodyStructureData(Horde_Imap_Client_Socket $client,
$isEncrypted = true;
}

/** @var Horde_Mime_Headers $messageHeaders */
$messageHeaders = $fetchData->getHeaderText('0', Horde_Imap_Client_Data_Fetch::HEADER_PARSE);
$governanceLabelHeader = $messageHeaders->getHeader(GovernanceLabelService::HEADER)?->value_single;

$structure = $fetchData->getStructure();

/** @var Horde_Mime_Part $part */
Expand All @@ -909,7 +914,7 @@ public function getBodyStructureData(Horde_Imap_Client_Socket $client,
$textBodyId = $structure->findBody() ?? $structure->findBody('text');
$htmlBodyId = $structure->findBody('html');
if ($textBodyId === null && $htmlBodyId === null) {
return new MessageStructureData($hasAttachments, $text, $isImipMessage, $isEncrypted, false);
return new MessageStructureData($hasAttachments, $text, $isImipMessage, $isEncrypted, false, $governanceLabelHeader);
}
$partsQuery = new Horde_Imap_Client_Fetch_Query();
if ($htmlBodyId !== null) {
Expand All @@ -935,7 +940,7 @@ public function getBodyStructureData(Horde_Imap_Client_Socket $client,
$part = $parts[$fetchData->getUid()];
// This is sus - why does this even happen? A delete / move in the middle of this processing?
if ($part === null) {
return new MessageStructureData($hasAttachments, $text, $isImipMessage, $isEncrypted, false);
return new MessageStructureData($hasAttachments, $text, $isImipMessage, $isEncrypted, false, $governanceLabelHeader);
}

/** Convert a given binary body to utf-8 according to the applicable
Expand Down Expand Up @@ -988,6 +993,7 @@ public function getBodyStructureData(Horde_Imap_Client_Socket $client,
$isImipMessage,
$isEncrypted,
$mentionsUser,
$governanceLabelHeader,
);
}

Expand All @@ -1000,9 +1006,10 @@ public function getBodyStructureData(Horde_Imap_Client_Socket $client,
$isImipMessage,
$isEncrypted,
false,
$governanceLabelHeader,
);
}
return new MessageStructureData($hasAttachments, $text, $isImipMessage, $isEncrypted, false);
return new MessageStructureData($hasAttachments, $text, $isImipMessage, $isEncrypted, false, $governanceLabelHeader);
}, iterator_to_array($structures->getIterator()));
}
private function checkLinks(string $body, string $mailAddress) : bool {
Expand Down
5 changes: 5 additions & 0 deletions lib/IMAP/MessageStructureData.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public function __construct(
private bool $isImipMessage,
private bool $isEncrypted,
private bool $mentionsMe,
private ?string $governanceLabelHeader = null,
) {
}

Expand All @@ -38,4 +39,8 @@ public function isEncrypted(): bool {
public function getMentionsMe(): bool {
return $this->mentionsMe;
}

public function getGovernanceLabelHeader(): ?string {
return $this->governanceLabelHeader;
}
}
7 changes: 6 additions & 1 deletion lib/IMAP/PreviewEnhancer.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use OCA\Mail\Service\Attachment\AttachmentService;
use OCA\Mail\Service\Avatar\Avatar;
use OCA\Mail\Service\AvatarService;
use OCA\Mail\Service\GovernanceLabelService;
use Psr\Log\LoggerInterface;
use function array_key_exists;
use function array_map;
Expand All @@ -32,6 +33,7 @@ public function __construct(
private LoggerInterface $logger,
private AvatarService $avatarService,
private AttachmentService $attachmentService,
private GovernanceLabelService $governanceLabelService,
) {
}

Expand Down Expand Up @@ -95,7 +97,7 @@ public function process(Account $account, Mailbox $mailbox, array $messages, boo
$client->logout();
}

return $this->mapper->updatePreviewDataBulk(...array_map(static function (Message $message) use ($data) {
return $this->mapper->updatePreviewDataBulk(...array_map(function (Message $message) use ($data) {
if (!array_key_exists($message->getUid(), $data)) {
// Nothing to do
return $message;
Expand All @@ -108,6 +110,9 @@ public function process(Account $account, Mailbox $mailbox, array $messages, boo
$message->setImipMessage($structureData->isImipMessage());
$message->setEncrypted($structureData->isEncrypted());
$message->setMentionsMe($structureData->getMentionsMe());
$message->setGovernanceLabelId(
$this->governanceLabelService->resolveHeaderLabelId($structureData->getGovernanceLabelHeader())
);

return $message;
}, $messages));
Expand Down
53 changes: 53 additions & 0 deletions lib/Migration/Version5200Date20260714000000.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Mail\Migration;

use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;

/**
* @psalm-api
*/
class Version5200Date20260714000000 extends SimpleMigrationStep {

/**
* @param IOutput $output
* @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
* @return null|ISchemaWrapper
*/
#[\Override]
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
$schema = $schemaClosure();

$localMessagesTable = $schema->getTable('mail_local_messages');
if (!$localMessagesTable->hasColumn('governance_label_id')) {
$localMessagesTable->addColumn('governance_label_id', Types::STRING, [
'notnull' => false,
'length' => 64,
'default' => null,
]);
}

$messagesTable = $schema->getTable('mail_messages');
if (!$messagesTable->hasColumn('governance_label_id')) {
$messagesTable->addColumn('governance_label_id', Types::STRING, [
'notnull' => false,
'length' => 64,
'default' => null,
]);
}

return $schema;
}
}
Loading
Loading