diff --git a/appinfo/info.xml b/appinfo/info.xml
index fb9237f058..3bca47814c 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -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/).
]]>
- 4.1.0-alpha.0
+ 4.1.1-alpha.0
agpl
Christoph Wurst
GretaD
diff --git a/lib/Db/Message.php b/lib/Db/Message.php
index 3e4a01f76e..229c26f395 100644
--- a/lib/Db/Message.php
+++ b/lib/Db/Message.php
@@ -68,6 +68,8 @@
* @method void setImipError(bool $imipError)
* @method bool|null isEncrypted()
* @method void setEncrypted(bool|null $encrypted)
+ * @method bool getMentionsMe()
+ * @method void setMentionsMe(bool $isMentionned)
*/
class Message extends Entity implements JsonSerializable {
private const MUTABLE_FLAGS = [
@@ -109,6 +111,7 @@ class Message extends Entity implements JsonSerializable {
protected $imipMessage = false;
protected $imipProcessed = false;
protected $imipError = false;
+ protected $mentionsMe = false;
/**
* @var bool|null
@@ -323,6 +326,7 @@ static function (Tag $tag) {
'imipMessage' => $this->isImipMessage(),
'previewText' => $this->getPreviewText(),
'encrypted' => ($this->isEncrypted() === true),
+ 'mentionsMe' => $this->getMentionsMe(),
];
}
}
diff --git a/lib/Db/MessageMapper.php b/lib/Db/MessageMapper.php
index 0084ecbf95..b9b63af0b0 100644
--- a/lib/Db/MessageMapper.php
+++ b/lib/Db/MessageMapper.php
@@ -563,6 +563,7 @@ public function updatePreviewDataBulk(Message ...$messages): array {
->set('updated_at', $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT))
->set('imip_message', $query->createParameter('imip_message'))
->set('encrypted', $query->createParameter('encrypted'))
+ ->set('mentions_me', $query->createParameter('mentions_me'))
->where($query->expr()->andX(
$query->expr()->eq('uid', $query->createParameter('uid')),
$query->expr()->eq('mailbox_id', $query->createParameter('mailbox_id'))
@@ -593,6 +594,7 @@ 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->executeStatement();
}
@@ -955,6 +957,12 @@ public function findIdsByQuery(Mailbox $mailbox, SearchQuery $query, string $sor
);
}
+ if ($query->getMentionsMe()) {
+ $select->andWhere(
+ $qb->expr()->eq('m.mentions_me', $qb->createNamedParameter($query->getMentionsMe(), IQueryBuilder::PARAM_BOOL))
+ );
+ }
+
if ($query->getCursor() !== null && $sortOrder === IMailSearch::ORDER_NEWEST_FIRST) {
$select->andWhere(
$qb->expr()->lt('m.sent_at', $qb->createNamedParameter($query->getCursor(), IQueryBuilder::PARAM_INT))
diff --git a/lib/IMAP/MessageMapper.php b/lib/IMAP/MessageMapper.php
index 6738f6d963..e1442e72c0 100644
--- a/lib/IMAP/MessageMapper.php
+++ b/lib/IMAP/MessageMapper.php
@@ -52,7 +52,7 @@ class MessageMapper {
public function __construct(LoggerInterface $logger,
SmimeService $smimeService,
- ImapMessageFetcherFactory $imapMessageFactory) {
+ ImapMessageFetcherFactory $imapMessageFactory, ) {
$this->logger = $logger;
$this->smimeService = $smimeService;
$this->imapMessageFactory = $imapMessageFactory;
@@ -858,7 +858,8 @@ private function buildAttachmentsPartsQuery(Horde_Mime_Part $structure, array $a
*/
public function getBodyStructureData(Horde_Imap_Client_Socket $client,
string $mailbox,
- array $uids): array {
+ array $uids,
+ string $emailAddress): array {
$structureQuery = new Horde_Imap_Client_Fetch_Query();
$structureQuery->structure();
$structureQuery->headerText([
@@ -870,8 +871,7 @@ public function getBodyStructureData(Horde_Imap_Client_Socket $client,
$structures = $client->fetch($mailbox, $structureQuery, [
'ids' => new Horde_Imap_Client_Ids($uids),
]);
-
- return array_map(function (Horde_Imap_Client_Data_Fetch $fetchData) use ($mailbox, $client) {
+ return array_map(function (Horde_Imap_Client_Data_Fetch $fetchData) use ($mailbox, $client, $emailAddress) {
$hasAttachments = false;
$text = '';
$isImipMessage = false;
@@ -901,7 +901,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);
+ return new MessageStructureData($hasAttachments, $text, $isImipMessage, $isEncrypted, false);
}
$partsQuery = new Horde_Imap_Client_Fetch_Query();
if ($htmlBodyId !== null) {
@@ -927,7 +927,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);
+ return new MessageStructureData($hasAttachments, $text, $isImipMessage, $isEncrypted, false);
}
@@ -939,12 +939,14 @@ public function getBodyStructureData(Horde_Imap_Client_Socket $client,
$structure->setContents($htmlBody);
$htmlBody = $structure->getContents();
}
+ $mentionsUser = $this->checkLinks($htmlBody, $emailAddress);
$html = new Html2Text($htmlBody, ['do_links' => 'none','alt_image' => 'hide']);
return new MessageStructureData(
$hasAttachments,
trim($html->getText()),
$isImipMessage,
$isEncrypted,
+ $mentionsUser,
);
}
$textBody = $part->getBodyPart($textBodyId);
@@ -961,9 +963,27 @@ public function getBodyStructureData(Horde_Imap_Client_Socket $client,
$textBody,
$isImipMessage,
$isEncrypted,
+ false,
);
}
- return new MessageStructureData($hasAttachments, $text, $isImipMessage, $isEncrypted);
+ return new MessageStructureData($hasAttachments, $text, $isImipMessage, $isEncrypted, false);
}, iterator_to_array($structures->getIterator()));
}
+ private function checkLinks(string $body, string $mailAddress) : bool {
+ if (empty($body)) {
+ return false;
+ }
+ $dom = new \DOMDocument();
+ libxml_use_internal_errors(true);
+ $dom->loadHTML($body);
+ libxml_use_internal_errors();
+ $anchors = $dom->getElementsByTagName('a');
+ foreach ($anchors as $anchor) {
+ $href = $anchor->getAttribute('href');
+ if($href === 'mailto:'.$mailAddress) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/lib/IMAP/MessageStructureData.php b/lib/IMAP/MessageStructureData.php
index 3447831d2d..fc58ef9c84 100644
--- a/lib/IMAP/MessageStructureData.php
+++ b/lib/IMAP/MessageStructureData.php
@@ -20,15 +20,18 @@ class MessageStructureData {
private $isImipMessage;
private bool $isEncrypted;
+ private bool $mentionsMe;
public function __construct(bool $hasAttachments,
string $previewText,
bool $isImipMessage,
- bool $isEncrypted) {
+ bool $isEncrypted,
+ bool $mentionsMe) {
$this->hasAttachments = $hasAttachments;
$this->previewText = $previewText;
$this->isImipMessage = $isImipMessage;
$this->isEncrypted = $isEncrypted;
+ $this->mentionsMe = $mentionsMe;
}
public function hasAttachments(): bool {
@@ -46,4 +49,8 @@ public function isImipMessage(): bool {
public function isEncrypted(): bool {
return $this->isEncrypted;
}
+
+ public function getMentionsMe(): bool {
+ return $this->mentionsMe;
+ }
}
diff --git a/lib/IMAP/PreviewEnhancer.php b/lib/IMAP/PreviewEnhancer.php
index 99ce4b54e2..d9273bbfd3 100644
--- a/lib/IMAP/PreviewEnhancer.php
+++ b/lib/IMAP/PreviewEnhancer.php
@@ -69,7 +69,8 @@ public function process(Account $account, Mailbox $mailbox, array $messages): ar
$data = $this->imapMapper->getBodyStructureData(
$client,
$mailbox->getName(),
- $needAnalyze
+ $needAnalyze,
+ $account->getEMailAddress()
);
} catch (Horde_Imap_Client_Exception $e) {
// Ignore for now, but log
@@ -94,6 +95,7 @@ public function process(Account $account, Mailbox $mailbox, array $messages): ar
$message->setStructureAnalyzed(true);
$message->setImipMessage($structureData->isImipMessage());
$message->setEncrypted($structureData->isEncrypted());
+ $message->setMentionsMe($structureData->getMentionsMe());
return $message;
}, $messages));
diff --git a/lib/Migration/Version4001Date20241009140707.php b/lib/Migration/Version4001Date20241009140707.php
new file mode 100644
index 0000000000..155d6310f2
--- /dev/null
+++ b/lib/Migration/Version4001Date20241009140707.php
@@ -0,0 +1,43 @@
+getTable('mail_messages');
+ if (!$messagesTable->hasColumn('mentions_me')) {
+ $messagesTable->addColumn('mentions_me', Types::BOOLEAN, [
+ 'notnull' => false,
+ 'default' => false,
+ ]);
+ }
+
+ return $schema;
+ }
+
+}
diff --git a/lib/Service/Search/FilterStringParser.php b/lib/Service/Search/FilterStringParser.php
index cadcf2d791..f2bd4f5470 100644
--- a/lib/Service/Search/FilterStringParser.php
+++ b/lib/Service/Search/FilterStringParser.php
@@ -106,6 +106,11 @@ private function parseFilterToken(SearchQuery $query, string $token): bool {
case 'match':
$query->setMatch($param);
return true;
+ case 'mentions':
+ if ($param === 'true') {
+ $query->setMentionsMe(true);
+ }
+ return true;
case 'flags':
$flagArray = explode(',', $param);
foreach ($flagArray as $flagItem) {
diff --git a/lib/Service/Search/SearchQuery.php b/lib/Service/Search/SearchQuery.php
index 4058e5d61a..d3731d1a20 100644
--- a/lib/Service/Search/SearchQuery.php
+++ b/lib/Service/Search/SearchQuery.php
@@ -52,6 +52,9 @@ class SearchQuery {
/** @var bool */
private $hasAttachments = false;
+ /** @var bool */
+ private $mentionsMe = false;
+
private string $match = 'allof';
/**
@@ -230,4 +233,18 @@ public function getHasAttachments(): ?bool {
public function setHasAttachments(bool $hasAttachments): void {
$this->hasAttachments = $hasAttachments;
}
+
+ /**
+ * @return bool
+ */
+ public function getMentionsMe(): bool {
+ return $this->mentionsMe;
+ }
+
+ /**
+ * @param bool $hasAttachments
+ */
+ public function setMentionsMe(bool $mentionsMe): void {
+ $this->mentionsMe = $mentionsMe;
+ }
}
diff --git a/src/components/SearchMessages.vue b/src/components/SearchMessages.vue
index cbc759164e..aea61917de 100644
--- a/src/components/SearchMessages.vue
+++ b/src/components/SearchMessages.vue
@@ -252,6 +252,11 @@
{{ t('mail', 'Has attachments') }}
+
+
+ {{ t('mail', 'Mentions me') }}
+
+
@@ -346,6 +351,7 @@ export default {
searchInSubject: null,
searchInMessageBody: null,
searchFlags: [],
+ mentionsMe: false,
hasAttachmentActive: false,
hasLast7daysActive: false,
hasFromMeActive: false,
@@ -406,6 +412,7 @@ export default {
body: this.searchInMessageBody !== null && this.searchInMessageBody.length > 1 ? this.searchInMessageBody : '',
tags: this.selectedTags.length > 0 ? this.selectedTags.map(item => item.id) : '',
flags: this.searchFlags.length > 0 ? this.searchFlags.map(item => item) : '',
+ mentions: this.mentionsMe,
start: this.prepareStart(),
end: this.prepareEnd(),
}
@@ -537,6 +544,7 @@ export default {
this.searchFlags = []
this.startDate = null
this.endDate = null
+ this.mentionsMe = false
// Need if there is only tag filter or recipients filter
if (prevQuery === '') {
this.sendQueryEvent()
@@ -661,9 +669,6 @@ export default {
display: inline-block;
width: 32%;
- &:last-child {
- width: 100%;
- }
}
.range {
display: flex;