Mailcommon

searchrule.cpp
1/*
2 SPDX-FileCopyrightText: 2015-2025 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6#include "searchrule.h"
7#include "mailcommon_debug.h"
8#include "searchrule/searchruleattachment.h"
9#include "searchrule/searchruledate.h"
10#include "searchrule/searchruleencryption.h"
11#include "searchrule/searchruleinvitation.h"
12#include "searchrule/searchrulenumerical.h"
13#include "searchrule/searchrulestatus.h"
14#include "searchrule/searchrulestring.h"
15
16#include <KConfigGroup>
17
18#include <QDataStream>
19
20#include <algorithm>
21
22using namespace MailCommon;
23
24static const char *const funcConfigNames[] = {"contains",
25 "contains-not",
26 "equals",
27 "not-equal",
28 "regexp",
29 "not-regexp",
30 "greater",
31 "less-or-equal",
32 "less",
33 "greater-or-equal",
34 "is-in-addressbook",
35 "is-not-in-addressbook",
36 "is-in-category",
37 "is-not-in-category",
38 "has-attachment",
39 "has-no-attachment",
40 "start-with",
41 "not-start-with",
42 "end-with",
43 "not-end-with",
44 "has-invitation",
45 "has-no-invitation"};
46
47static const int numFuncConfigNames = sizeof funcConfigNames / sizeof *funcConfigNames;
48
49//==================================================
50//
51// class SearchRule (was: KMFilterRule)
52//
53//==================================================
54
56 : mField(field)
57 , mFunction(func)
58 , mContents(contents)
59{
60}
61
63
64 = default;
65
67{
68 if (this == &other) {
69 return *this;
70 }
71
72 mField = other.mField;
73 mFunction = other.mFunction;
74 mContents = other.mContents;
75
76 return *this;
77}
78
80{
82 if (field == "<status>") {
84 } else if (field == "<age in days>" || field == "<size>") {
86 } else if (field == "<date>") {
87 ret = SearchRule::Ptr(new SearchRuleDate(field, func, contents));
88 } else if (field == "<encryption>") {
89 ret = SearchRule::Ptr(new SearchRuleEncryption(field, func, contents));
90 } else if (field == "<attachment>") {
91 ret = SearchRule::Ptr(new SearchRuleAttachment(field, func, contents));
92 } else if (field == "<invitation>") {
93 ret = SearchRule::Ptr(new SearchRuleInvitation(field, func, contents));
94 } else {
95 ret = SearchRule::Ptr(new SearchRuleString(field, func, contents));
96 }
97
98 return ret;
99}
100
102{
103 return createInstance(field, configValueToFunc(func), contents);
104}
105
107{
108 return createInstance(other.field(), other.function(), other.contents());
109}
110
112{
113 const char cIdx = char(int('A') + aIdx);
114
115 static const QString field = QStringLiteral("field");
116 static const QString func = QStringLiteral("func");
117 static const QString contents = QStringLiteral("contents");
118
119 const QByteArray &field2 = config.readEntry(field + cIdx, QString()).toLatin1();
120 Function func2 = configValueToFunc(config.readEntry(func + cIdx, QString()).toLatin1().constData());
121 const QString &contents2 = config.readEntry(contents + cIdx, QString());
122
123 if (field2 == "<To or Cc>") { // backwards compat
124 return SearchRule::createInstance("<recipients>", func2, contents2);
125 } else {
126 return SearchRule::createInstance(field2, func2, contents2);
127 }
128}
129
131{
133 s >> field;
135 s >> function;
136 Function func = configValueToFunc(function.toUtf8().constData());
138 s >> contents;
139 return createInstance(field, func, contents);
140}
141
142SearchRule::~SearchRule() = default;
143
144SearchRule::Function SearchRule::configValueToFunc(const char *str)
145{
146 if (!str) {
147 return FuncNone;
148 }
149
150 for (int i = 0; i < numFuncConfigNames; ++i) {
151 if (qstricmp(funcConfigNames[i], str) == 0) {
152 return static_cast<Function>(i);
153 }
154 }
155
156 return FuncNone;
157}
158
159QString SearchRule::functionToString(Function function)
160{
161 if (function != FuncNone) {
162 return funcConfigNames[int(function)];
163 } else {
164 return QStringLiteral("invalid");
165 }
166}
167
168void SearchRule::writeConfig(KConfigGroup &config, int aIdx) const
169{
170 const char cIdx = char('A' + aIdx);
171 static const QString field = QStringLiteral("field");
172 static const QString func = QStringLiteral("func");
173 static const QString contents = QStringLiteral("contents");
174
175 config.writeEntry(field + cIdx, /*QString*/ (mField));
176 config.writeEntry(func + cIdx, functionToString(mFunction));
177 config.writeEntry(contents + cIdx, mContents);
178}
179
180QString SearchRule::conditionToString(Function function)
181{
182 QString str;
183 switch (function) {
184 case FuncEquals:
185 str = i18n("equal");
186 break;
187 case FuncNotEqual:
188 str = i18n("not equal");
189 break;
190 case FuncIsGreater:
191 str = i18n("is greater");
192 break;
193 case FuncIsLessOrEqual:
194 str = i18n("is less or equal");
195 break;
196 case FuncIsLess:
197 str = i18n("is less");
198 break;
199 case FuncIsGreaterOrEqual:
200 str = i18n("is greater or equal");
201 break;
202 case FuncIsInAddressbook:
203 str = i18n("is in addressbook");
204 break;
205 case FuncIsNotInAddressbook:
206 str = i18n("is not in addressbook");
207 break;
208 case FuncIsInCategory:
209 str = i18n("is in category");
210 break;
211 case FuncIsNotInCategory:
212 str = i18n("is in category");
213 break;
214 case FuncHasAttachment:
215 str = i18n("has an attachment");
216 break;
217 case FuncHasNoAttachment:
218 str = i18n("has not an attachment");
219 break;
220 case FuncHasInvitation:
221 str = i18n("has an invitation");
222 break;
223 case FuncHasNoInvitation:
224 str = i18n("has not an invitation");
225 break;
226 case FuncStartWith:
227 str = i18n("start with");
228 break;
229 case FuncNotStartWith:
230 str = i18n("not start with");
231 break;
232 case FuncEndWith:
233 str = i18n("end with");
234 break;
235 case FuncNotEndWith:
236 str = i18n("not end with");
237 break;
238 case FuncNone:
239 str = i18n("none");
240 break;
241 case FuncContains:
242 str = i18n("contains");
243 break;
244 case FuncContainsNot:
245 str = i18n("not contains");
246 break;
247 case FuncRegExp:
248 str = i18n("has regexp");
249 break;
250 case FuncNotRegExp:
251 str = i18n("not regexp");
252 break;
253 }
254 return str;
255}
256
257void SearchRule::generateSieveScript(QStringList &requireModules, QString &code)
258{
259 QString contentStr = mContents;
260 if (mField == "<size>") {
261 QString comparison;
262 int offset = 0;
263 switch (mFunction) {
264 case FuncEquals:
265 comparison = QLatin1Char('"') + i18n("size equals not supported") + QLatin1Char('"');
266 break;
267 case FuncNotEqual:
268 comparison = QLatin1Char('"') + i18n("size not equals not supported") + QLatin1Char('"');
269 break;
270 case FuncIsGreater:
271 comparison = QStringLiteral(":over");
272 break;
273 case FuncIsLessOrEqual:
274 comparison = QStringLiteral(":under");
275 offset = 1;
276 break;
277 case FuncIsLess:
278 comparison = QStringLiteral(":under");
279 break;
280 case FuncIsGreaterOrEqual:
281 comparison = QStringLiteral(":over");
282 offset = -1;
283 break;
284 case FuncIsInAddressbook:
285 case FuncIsNotInAddressbook:
286 case FuncIsInCategory:
287 case FuncIsNotInCategory:
288 case FuncHasAttachment:
289 case FuncHasNoAttachment:
290 case FuncHasInvitation:
291 case FuncHasNoInvitation:
292 case FuncStartWith:
293 case FuncNotStartWith:
294 case FuncEndWith:
295 case FuncNotEndWith:
296 case FuncNone:
297 case FuncContains:
298 case FuncContainsNot:
299 case FuncRegExp:
300 case FuncNotRegExp:
301 code += QLatin1Char('"') + i18n("\"%1\" is not supported with condition \"%2\"", QLatin1StringView(mField), conditionToString(mFunction))
302 + QLatin1Char('"');
303 return;
304 }
305 code += QStringLiteral("size %1 %2K").arg(comparison).arg(QString::number(mContents.toInt() + offset));
306 } else if (mField == "<status>") {
307 // TODO ?
308 code += QLatin1Char('"') + i18n("<status> not implemented/supported") + QLatin1Char('"');
309 } else if (mField == "<any header>") {
310 // TODO ?
311 code += QLatin1Char('"') + i18n("<any header> not implemented/supported") + QLatin1Char('"');
312 } else if (mField == "contents") {
313 // TODO ?
314 code += QLatin1Char('"') + i18n("<contents> not implemented/supported") + QLatin1Char('"');
315 } else if (mField == "<age in days>") {
316 // TODO ?
317 code += QLatin1Char('"') + i18n("<age in days> not implemented/supported") + QLatin1Char('"');
318 } else if (mField == "<date>") {
319 // TODO ?
320 code += QLatin1Char('"') + i18n("<date> not implemented/supported") + QLatin1Char('"');
321 } else if (mField == "<recipients>") {
322 // TODO ?
323 code += QLatin1Char('"') + i18n("<recipients> not implemented/supported") + QLatin1Char('"');
324 } else if (mField == "<tag>") {
325 code += QLatin1Char('"') + i18n("<Tag> is not supported") + QLatin1Char('"');
326 } else if (mField == "<message>") {
327 // TODO ?
328 code += i18n("<message> not implemented/supported");
329 } else if (mField == "<body>") {
330 if (!requireModules.contains(QLatin1StringView("body"))) {
331 requireModules << QStringLiteral("body");
332 }
333 QString comparison;
334 bool negative = false;
335 switch (mFunction) {
336 case FuncNone:
337 break;
338 case FuncContains:
339 comparison = QStringLiteral(":contains");
340 break;
341 case FuncContainsNot:
342 negative = true;
343 comparison = QStringLiteral(":contains");
344 break;
345 case FuncEquals:
346 comparison = QStringLiteral(":is");
347 break;
348 case FuncNotEqual:
349 comparison = QStringLiteral(":is");
350 negative = true;
351 break;
352 case FuncRegExp:
353 comparison = QStringLiteral(":regex");
354 if (!requireModules.contains(QLatin1StringView("regex"))) {
355 requireModules << QStringLiteral("regex");
356 }
357 break;
358 case FuncNotRegExp:
359 if (!requireModules.contains(QLatin1StringView("regex"))) {
360 requireModules << QStringLiteral("regex");
361 }
362 comparison = QStringLiteral(":regex");
363 negative = true;
364 break;
365 case FuncStartWith:
366 comparison = QStringLiteral(":regex");
367 if (!requireModules.contains(QLatin1StringView("regex"))) {
368 requireModules << QStringLiteral("regex");
369 }
370 contentStr = QLatin1Char('^') + contentStr;
371 break;
372 case FuncNotStartWith:
373 comparison = QStringLiteral(":regex");
374 if (!requireModules.contains(QLatin1StringView("regex"))) {
375 requireModules << QStringLiteral("regex");
376 }
377 comparison = QStringLiteral(":regex");
378 contentStr = QLatin1Char('^') + contentStr;
379 negative = true;
380 break;
381 case FuncEndWith:
382 comparison = QStringLiteral(":regex");
383 if (!requireModules.contains(QLatin1StringView("regex"))) {
384 requireModules << QStringLiteral("regex");
385 }
386 comparison = QStringLiteral(":regex");
387 contentStr = contentStr + QLatin1Char('$');
388 break;
389 case FuncNotEndWith:
390 comparison = QStringLiteral(":regex");
391 if (!requireModules.contains(QLatin1StringView("regex"))) {
392 requireModules << QStringLiteral("regex");
393 }
394 comparison = QStringLiteral(":regex");
395 contentStr = contentStr + QLatin1Char('$');
396 negative = true;
397 break;
398 case FuncIsGreater:
399 case FuncIsLessOrEqual:
400 case FuncIsLess:
401 case FuncIsGreaterOrEqual:
402 case FuncIsInAddressbook:
403 case FuncIsNotInAddressbook:
404 case FuncIsInCategory:
405 case FuncIsNotInCategory:
406 case FuncHasAttachment:
407 case FuncHasNoAttachment:
408 case FuncHasInvitation:
409 case FuncHasNoInvitation:
410 code += QLatin1Char('"') + i18n("\"%1\" is not supported with condition \"%2\"", QLatin1StringView(mField), conditionToString(mFunction))
411 + QLatin1Char('"');
412 return;
413 }
414 code += (negative ? QStringLiteral("not ") : QString()) + QStringLiteral("body :text %1 \"%2\"").arg(comparison, contentStr);
415 } else {
416 QString comparison;
417 bool negative = false;
418 switch (mFunction) {
419 case FuncNone:
420 break;
421 case FuncContains:
422 comparison = QStringLiteral(":contains");
423 break;
424 case FuncContainsNot:
425 negative = true;
426 comparison = QStringLiteral(":contains");
427 break;
428 case FuncEquals:
429 comparison = QStringLiteral(":is");
430 break;
431 case FuncNotEqual:
432 comparison = QStringLiteral(":is");
433 negative = true;
434 break;
435 case FuncRegExp:
436 comparison = QStringLiteral(":regex");
437 if (!requireModules.contains(QLatin1StringView("regex"))) {
438 requireModules << QStringLiteral("regex");
439 }
440 break;
441 case FuncNotRegExp:
442 if (!requireModules.contains(QLatin1StringView("regex"))) {
443 requireModules << QStringLiteral("regex");
444 }
445 comparison = QStringLiteral(":regex");
446 negative = true;
447 break;
448 case FuncStartWith:
449 comparison = QStringLiteral(":regex");
450 if (!requireModules.contains(QLatin1StringView("regex"))) {
451 requireModules << QStringLiteral("regex");
452 }
453 contentStr = QLatin1Char('^') + contentStr;
454 break;
455 case FuncNotStartWith:
456 comparison = QStringLiteral(":regex");
457 if (!requireModules.contains(QLatin1StringView("regex"))) {
458 requireModules << QStringLiteral("regex");
459 }
460 comparison = QStringLiteral(":regex");
461 contentStr = QLatin1Char('^') + contentStr;
462 negative = true;
463 break;
464 case FuncEndWith:
465 comparison = QStringLiteral(":regex");
466 if (!requireModules.contains(QLatin1StringView("regex"))) {
467 requireModules << QStringLiteral("regex");
468 }
469 comparison = QStringLiteral(":regex");
470 contentStr = contentStr + QLatin1Char('$');
471 break;
472 case FuncNotEndWith:
473 comparison = QStringLiteral(":regex");
474 if (!requireModules.contains(QLatin1StringView("regex"))) {
475 requireModules << QStringLiteral("regex");
476 }
477 comparison = QStringLiteral(":regex");
478 contentStr = contentStr + QLatin1Char('$');
479 negative = true;
480 break;
481
482 case FuncIsGreater:
483 case FuncIsLessOrEqual:
484 case FuncIsLess:
485 case FuncIsGreaterOrEqual:
486 case FuncIsInAddressbook:
487 case FuncIsNotInAddressbook:
488 case FuncIsInCategory:
489 case FuncIsNotInCategory:
490 case FuncHasInvitation:
491 case FuncHasNoInvitation:
492 case FuncHasAttachment:
493 case FuncHasNoAttachment:
494 code += QLatin1Char('"') + i18n("\"%1\" is not supported with condition \"%2\"", QLatin1StringView(mField), conditionToString(mFunction))
495 + QLatin1Char('"');
496 return;
497 }
498 code += (negative ? QStringLiteral("not ") : QString())
499 + QStringLiteral("header %1 \"%2\" \"%3\"").arg(comparison).arg(QLatin1StringView(mField)).arg(contentStr);
500 }
501}
502
504{
505 mFunction = function;
506}
507
509{
510 return mFunction;
511}
512
514{
515 mField = field;
516}
517
519{
520 return mField;
521}
522
524{
525 mContents = contents;
526}
527
529{
530 return mContents;
531}
532
534{
535 QString result = QLatin1StringView("\"") + QString::fromLatin1(mField) + QLatin1StringView("\" <");
536 result += functionToString(mFunction);
537 result += QLatin1StringView("> \"") + mContents + QLatin1StringView("\"");
538
539 return result;
540}
541
542Akonadi::SearchTerm::Condition SearchRule::akonadiComparator() const
543{
544 switch (function()) {
545 case SearchRule::FuncContains:
546 case SearchRule::FuncContainsNot:
547 return Akonadi::SearchTerm::CondContains;
548
549 case SearchRule::FuncEquals:
550 case SearchRule::FuncNotEqual:
551 return Akonadi::SearchTerm::CondEqual;
552
553 case SearchRule::FuncIsGreater:
554 return Akonadi::SearchTerm::CondGreaterThan;
555
556 case SearchRule::FuncIsGreaterOrEqual:
557 return Akonadi::SearchTerm::CondGreaterOrEqual;
558
559 case SearchRule::FuncIsLess:
560 return Akonadi::SearchTerm::CondLessThan;
561
562 case SearchRule::FuncIsLessOrEqual:
563 return Akonadi::SearchTerm::CondLessOrEqual;
564
565 case SearchRule::FuncRegExp:
566 case SearchRule::FuncNotRegExp:
567 // TODO is this sufficient?
568 return Akonadi::SearchTerm::CondContains;
569
570 case SearchRule::FuncStartWith:
571 case SearchRule::FuncNotStartWith:
572 case SearchRule::FuncEndWith:
573 case SearchRule::FuncNotEndWith:
574 // TODO is this sufficient?
575 return Akonadi::SearchTerm::CondContains;
576 default:
577 qCDebug(MAILCOMMON_LOG) << "Unhandled function type: " << function();
578 }
579
580 return Akonadi::SearchTerm::CondEqual;
581}
582
584{
585 bool negate = false;
586 switch (function()) {
587 case SearchRule::FuncContainsNot:
588 case SearchRule::FuncNotEqual:
589 case SearchRule::FuncNotRegExp:
590 case SearchRule::FuncHasNoAttachment:
591 case SearchRule::FuncHasNoInvitation:
592 case SearchRule::FuncIsNotInCategory:
593 case SearchRule::FuncIsNotInAddressbook:
594 case SearchRule::FuncNotStartWith:
595 case SearchRule::FuncNotEndWith:
596 negate = true;
597 default:
598 break;
599 }
600 return negate;
601}
602
603QDataStream &SearchRule::operator>>(QDataStream &s) const
604{
605 s << mField << functionToString(mFunction) << mContents;
606 return s;
607}
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
QString readEntry(const char *key, const char *aDefault=nullptr) const
This class represents a search pattern rule operating on numerical values.
This class represents a search to be performed against the status of a message.
This class represents one search pattern rule.
Definition searchrule.h:24
QByteArray field() const
Returns the message header field name (without the trailing ':').
static SearchRule::Ptr createInstanceFromConfig(const KConfigGroup &group, int index)
Creates a new search rule from a given config group.
Function function() const
Returns the filter function of the rule.
QString contents() const
Returns the contents of the rule.
void setField(const QByteArray &name)
Sets the message header field name.
std::shared_ptr< SearchRule > Ptr
Defines a pointer to a search rule.
Definition searchrule.h:29
Akonadi::SearchTerm::Condition akonadiComparator() const
Converts the rule function into the corresponding Akonadi query operator.
void setFunction(Function function)
Sets the filter function of the rule.
virtual ~SearchRule()
Destroys the search rule.
Function
Describes operators for comparison of field and contents.
Definition searchrule.h:40
SearchRule(const QByteArray &field=QByteArray(), Function function=FuncContains, const QString &contents=QString())
Creates new new search rule.
bool isNegated() const
Helper that returns whether the rule has a negated function.
const SearchRule & operator=(const SearchRule &other)
Initializes this rule with an other rule.
const QString asString() const
Returns the rule as string for debugging purpose.
void setContents(const QString &contents)
Set the contents of the rule.
void writeConfig(KConfigGroup &group, int index) const
Saves the object into a given config group.
static SearchRule::Ptr createInstance(const QByteArray &field=QByteArray(), Function function=FuncContains, const QString &contents=QString())
Creates a new search rule of a certain type by instantiating the appropriate subclass depending on th...
QString i18n(const char *text, const TYPE &arg...)
The filter dialog.
QString arg(Args &&... args) const const
QString fromLatin1(QByteArrayView str)
QString number(double n, char format, int precision)
QByteArray toLatin1() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:56:35 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.