Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: filter messages by mention #10243

Merged
merged 1 commit into from
Oct 15, 2024
Merged
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>4.1.0-alpha.0</version>
hamza221 marked this conversation as resolved.
Show resolved Hide resolved
<version>4.1.1-alpha.0</version>
<licence>agpl</licence>
<author homepage="https://github.com/ChristophWurst">Christoph Wurst</author>
<author homepage="https://github.com/GretaD">GretaD</author>
Expand Down
4 changes: 4 additions & 0 deletions lib/Db/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -323,6 +326,7 @@ static function (Tag $tag) {
'imipMessage' => $this->isImipMessage(),
'previewText' => $this->getPreviewText(),
'encrypted' => ($this->isEncrypted() === true),
'mentionsMe' => $this->getMentionsMe(),
];
}
}
8 changes: 8 additions & 0 deletions lib/Db/MessageMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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))
Expand Down
34 changes: 27 additions & 7 deletions lib/IMAP/MessageMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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([
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}


Expand All @@ -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);
Expand All @@ -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();
hamza221 marked this conversation as resolved.
Show resolved Hide resolved
$anchors = $dom->getElementsByTagName('a');
foreach ($anchors as $anchor) {
$href = $anchor->getAttribute('href');
if ($href === 'mailto:' . $mailAddress) {
return true;
}
}
return false;
}
}
9 changes: 8 additions & 1 deletion lib/IMAP/MessageStructureData.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -46,4 +49,8 @@ public function isImipMessage(): bool {
public function isEncrypted(): bool {
return $this->isEncrypted;
}

public function getMentionsMe(): bool {
return $this->mentionsMe;
}
}
4 changes: 3 additions & 1 deletion lib/IMAP/PreviewEnhancer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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));
Expand Down
43 changes: 43 additions & 0 deletions lib/Migration/Version4001Date20241009140707.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 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;

class Version4001Date20241009140707 extends SimpleMigrationStep {


/**
* @param IOutput $output
* @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {

/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();

$messagesTable = $schema->getTable('mail_messages');
if (!$messagesTable->hasColumn('mentions_me')) {
$messagesTable->addColumn('mentions_me', Types::BOOLEAN, [
'notnull' => false,
'default' => false,
]);
}

return $schema;
}

}
5 changes: 5 additions & 0 deletions lib/Service/Search/FilterStringParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
17 changes: 17 additions & 0 deletions lib/Service/Search/SearchQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ class SearchQuery {
/** @var bool */
private $hasAttachments = false;

/** @var bool */
private $mentionsMe = false;

private string $match = 'allof';

/**
Expand Down Expand Up @@ -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;
}
}
11 changes: 8 additions & 3 deletions src/components/SearchMessages.vue
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,11 @@
{{ t('mail', 'Has attachments') }}
</NcCheckboxRadioSwitch>
</div>
<div class="modal-inner-inline">
<NcCheckboxRadioSwitch :checked.sync="mentionsMe">
{{ t('mail', 'Mentions me') }}
</NcCheckboxRadioSwitch>
</div>
</div>
</div>
</div>
Expand Down Expand Up @@ -346,6 +351,7 @@ export default {
searchInSubject: null,
searchInMessageBody: null,
searchFlags: [],
mentionsMe: false,
hasAttachmentActive: false,
hasLast7daysActive: false,
hasFromMeActive: false,
Expand Down Expand Up @@ -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(),
}
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -661,9 +669,6 @@ export default {
display: inline-block;
width: 32%;

&:last-child {
width: 100%;
}
}
.range {
display: flex;
Expand Down
Loading