Mailcommon

searchrule.cpp
1/*
2 SPDX-FileCopyrightText: 2015-2024 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
55SearchRule::SearchRule(const QByteArray &field, Function func, const QString &contents)
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
101SearchRule::Ptr SearchRule::createInstance(const QByteArray &field, const char *func, const QString &contents)
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 FuncStartWith:
221 str = i18n("start with");
222 break;
223 case FuncNotStartWith:
224 str = i18n("not start with");
225 break;
226 case FuncEndWith:
227 str = i18n("end with");
228 break;
229 case FuncNotEndWith:
230 str = i18n("not end with");
231 break;
232 case FuncNone:
233 str = i18n("none");
234 break;
235 case FuncContains:
236 str = i18n("contains");
237 break;
238 case FuncContainsNot:
239 str = i18n("not contains");
240 break;
241 case FuncRegExp:
242 str = i18n("has regexp");
243 break;
244 case FuncNotRegExp:
245 str = i18n("not regexp");
246 break;
247 }
248 return str;
249}
250
251void SearchRule::generateSieveScript(QStringList &requireModules, QString &code)
252{
253 QString contentStr = mContents;
254 if (mField == "<size>") {
255 QString comparison;
256 int offset = 0;
257 switch (mFunction) {
258 case FuncEquals:
259 comparison = QLatin1Char('"') + i18n("size equals not supported") + QLatin1Char('"');
260 break;
261 case FuncNotEqual:
262 comparison = QLatin1Char('"') + i18n("size not equals not supported") + QLatin1Char('"');
263 break;
264 case FuncIsGreater:
265 comparison = QStringLiteral(":over");
266 break;
267 case FuncIsLessOrEqual:
268 comparison = QStringLiteral(":under");
269 offset = 1;
270 break;
271 case FuncIsLess:
272 comparison = QStringLiteral(":under");
273 break;
274 case FuncIsGreaterOrEqual:
275 comparison = QStringLiteral(":over");
276 offset = -1;
277 break;
278 case FuncIsInAddressbook:
279 case FuncIsNotInAddressbook:
280 case FuncIsInCategory:
281 case FuncIsNotInCategory:
282 case FuncHasAttachment:
283 case FuncHasNoAttachment:
284 case FuncStartWith:
285 case FuncNotStartWith:
286 case FuncEndWith:
287 case FuncNotEndWith:
288 case FuncNone:
289 case FuncContains:
290 case FuncContainsNot:
291 case FuncRegExp:
292 case FuncNotRegExp:
293 code += QLatin1Char('"') + i18n("\"%1\" is not supported with condition \"%2\"", QLatin1StringView(mField), conditionToString(mFunction))
294 + QLatin1Char('"');
295 return;
296 }
297 code += QStringLiteral("size %1 %2K").arg(comparison).arg(QString::number(mContents.toInt() + offset));
298 } else if (mField == "<status>") {
299 // TODO ?
300 code += QLatin1Char('"') + i18n("<status> not implemented/supported") + QLatin1Char('"');
301 } else if (mField == "<any header>") {
302 // TODO ?
303 code += QLatin1Char('"') + i18n("<any header> not implemented/supported") + QLatin1Char('"');
304 } else if (mField == "contents") {
305 // TODO ?
306 code += QLatin1Char('"') + i18n("<contents> not implemented/supported") + QLatin1Char('"');
307 } else if (mField == "<age in days>") {
308 // TODO ?
309 code += QLatin1Char('"') + i18n("<age in days> not implemented/supported") + QLatin1Char('"');
310 } else if (mField == "<date>") {
311 // TODO ?
312 code += QLatin1Char('"') + i18n("<date> not implemented/supported") + QLatin1Char('"');
313 } else if (mField == "<recipients>") {
314 // TODO ?
315 code += QLatin1Char('"') + i18n("<recipients> not implemented/supported") + QLatin1Char('"');
316 } else if (mField == "<tag>") {
317 code += QLatin1Char('"') + i18n("<Tag> is not supported") + QLatin1Char('"');
318 } else if (mField == "<message>") {
319 // TODO ?
320 code += i18n("<message> not implemented/supported");
321 } else if (mField == "<body>") {
322 if (!requireModules.contains(QLatin1StringView("body"))) {
323 requireModules << QStringLiteral("body");
324 }
325 QString comparison;
326 bool negative = false;
327 switch (mFunction) {
328 case FuncNone:
329 break;
330 case FuncContains:
331 comparison = QStringLiteral(":contains");
332 break;
333 case FuncContainsNot:
334 negative = true;
335 comparison = QStringLiteral(":contains");
336 break;
337 case FuncEquals:
338 comparison = QStringLiteral(":is");
339 break;
340 case FuncNotEqual:
341 comparison = QStringLiteral(":is");
342 negative = true;
343 break;
344 case FuncRegExp:
345 comparison = QStringLiteral(":regex");
346 if (!requireModules.contains(QLatin1StringView("regex"))) {
347 requireModules << QStringLiteral("regex");
348 }
349 break;
350 case FuncNotRegExp:
351 if (!requireModules.contains(QLatin1StringView("regex"))) {
352 requireModules << QStringLiteral("regex");
353 }
354 comparison = QStringLiteral(":regex");
355 negative = true;
356 break;
357 case FuncStartWith:
358 comparison = QStringLiteral(":regex");
359 if (!requireModules.contains(QLatin1StringView("regex"))) {
360 requireModules << QStringLiteral("regex");
361 }
362 contentStr = QLatin1Char('^') + contentStr;
363 break;
364 case FuncNotStartWith:
365 comparison = QStringLiteral(":regex");
366 if (!requireModules.contains(QLatin1StringView("regex"))) {
367 requireModules << QStringLiteral("regex");
368 }
369 comparison = QStringLiteral(":regex");
370 contentStr = QLatin1Char('^') + contentStr;
371 negative = true;
372 break;
373 case FuncEndWith:
374 comparison = QStringLiteral(":regex");
375 if (!requireModules.contains(QLatin1StringView("regex"))) {
376 requireModules << QStringLiteral("regex");
377 }
378 comparison = QStringLiteral(":regex");
379 contentStr = contentStr + QLatin1Char('$');
380 break;
381 case FuncNotEndWith:
382 comparison = QStringLiteral(":regex");
383 if (!requireModules.contains(QLatin1StringView("regex"))) {
384 requireModules << QStringLiteral("regex");
385 }
386 comparison = QStringLiteral(":regex");
387 contentStr = contentStr + QLatin1Char('$');
388 negative = true;
389 break;
390 case FuncIsGreater:
391 case FuncIsLessOrEqual:
392 case FuncIsLess:
393 case FuncIsGreaterOrEqual:
394 case FuncIsInAddressbook:
395 case FuncIsNotInAddressbook:
396 case FuncIsInCategory:
397 case FuncIsNotInCategory:
398 case FuncHasAttachment:
399 case FuncHasNoAttachment:
400 code += QLatin1Char('"') + i18n("\"%1\" is not supported with condition \"%2\"", QLatin1StringView(mField), conditionToString(mFunction))
401 + QLatin1Char('"');
402 return;
403 }
404 code += (negative ? QStringLiteral("not ") : QString()) + QStringLiteral("body :text %1 \"%2\"").arg(comparison, contentStr);
405 } else {
406 QString comparison;
407 bool negative = false;
408 switch (mFunction) {
409 case FuncNone:
410 break;
411 case FuncContains:
412 comparison = QStringLiteral(":contains");
413 break;
414 case FuncContainsNot:
415 negative = true;
416 comparison = QStringLiteral(":contains");
417 break;
418 case FuncEquals:
419 comparison = QStringLiteral(":is");
420 break;
421 case FuncNotEqual:
422 comparison = QStringLiteral(":is");
423 negative = true;
424 break;
425 case FuncRegExp:
426 comparison = QStringLiteral(":regex");
427 if (!requireModules.contains(QLatin1StringView("regex"))) {
428 requireModules << QStringLiteral("regex");
429 }
430 break;
431 case FuncNotRegExp:
432 if (!requireModules.contains(QLatin1StringView("regex"))) {
433 requireModules << QStringLiteral("regex");
434 }
435 comparison = QStringLiteral(":regex");
436 negative = true;
437 break;
438 case FuncStartWith:
439 comparison = QStringLiteral(":regex");
440 if (!requireModules.contains(QLatin1StringView("regex"))) {
441 requireModules << QStringLiteral("regex");
442 }
443 contentStr = QLatin1Char('^') + contentStr;
444 break;
445 case FuncNotStartWith:
446 comparison = QStringLiteral(":regex");
447 if (!requireModules.contains(QLatin1StringView("regex"))) {
448 requireModules << QStringLiteral("regex");
449 }
450 comparison = QStringLiteral(":regex");
451 contentStr = QLatin1Char('^') + contentStr;
452 negative = true;
453 break;
454 case FuncEndWith:
455 comparison = QStringLiteral(":regex");
456 if (!requireModules.contains(QLatin1StringView("regex"))) {
457 requireModules << QStringLiteral("regex");
458 }
459 comparison = QStringLiteral(":regex");
460 contentStr = contentStr + QLatin1Char('$');
461 break;
462 case FuncNotEndWith:
463 comparison = QStringLiteral(":regex");
464 if (!requireModules.contains(QLatin1StringView("regex"))) {
465 requireModules << QStringLiteral("regex");
466 }
467 comparison = QStringLiteral(":regex");
468 contentStr = contentStr + QLatin1Char('$');
469 negative = true;
470 break;
471
472 case FuncIsGreater:
473 case FuncIsLessOrEqual:
474 case FuncIsLess:
475 case FuncIsGreaterOrEqual:
476 case FuncIsInAddressbook:
477 case FuncIsNotInAddressbook:
478 case FuncIsInCategory:
479 case FuncIsNotInCategory:
480 case FuncHasAttachment:
481 case FuncHasNoAttachment:
482 code += QLatin1Char('"') + i18n("\"%1\" is not supported with condition \"%2\"", QLatin1StringView(mField), conditionToString(mFunction))
483 + QLatin1Char('"');
484 return;
485 }
486 code += (negative ? QStringLiteral("not ") : QString())
487 + QStringLiteral("header %1 \"%2\" \"%3\"").arg(comparison).arg(QLatin1StringView(mField)).arg(contentStr);
488 }
489}
490
492{
493 mFunction = function;
494}
495
497{
498 return mFunction;
499}
500
502{
503 mField = field;
504}
505
507{
508 return mField;
509}
510
511void SearchRule::setContents(const QString &contents)
512{
513 mContents = contents;
514}
515
517{
518 return mContents;
519}
520
522{
523 QString result = QLatin1StringView("\"") + QString::fromLatin1(mField) + QLatin1StringView("\" <");
524 result += functionToString(mFunction);
525 result += QLatin1StringView("> \"") + mContents + QLatin1StringView("\"");
526
527 return result;
528}
529
530Akonadi::SearchTerm::Condition SearchRule::akonadiComparator() const
531{
532 switch (function()) {
533 case SearchRule::FuncContains:
534 case SearchRule::FuncContainsNot:
535 return Akonadi::SearchTerm::CondContains;
536
537 case SearchRule::FuncEquals:
538 case SearchRule::FuncNotEqual:
539 return Akonadi::SearchTerm::CondEqual;
540
541 case SearchRule::FuncIsGreater:
542 return Akonadi::SearchTerm::CondGreaterThan;
543
544 case SearchRule::FuncIsGreaterOrEqual:
545 return Akonadi::SearchTerm::CondGreaterOrEqual;
546
547 case SearchRule::FuncIsLess:
548 return Akonadi::SearchTerm::CondLessThan;
549
550 case SearchRule::FuncIsLessOrEqual:
551 return Akonadi::SearchTerm::CondLessOrEqual;
552
553 case SearchRule::FuncRegExp:
554 case SearchRule::FuncNotRegExp:
555 // TODO is this sufficient?
556 return Akonadi::SearchTerm::CondContains;
557
558 case SearchRule::FuncStartWith:
559 case SearchRule::FuncNotStartWith:
560 case SearchRule::FuncEndWith:
561 case SearchRule::FuncNotEndWith:
562 // TODO is this sufficient?
563 return Akonadi::SearchTerm::CondContains;
564 default:
565 qCDebug(MAILCOMMON_LOG) << "Unhandled function type: " << function();
566 }
567
568 return Akonadi::SearchTerm::CondEqual;
569}
570
572{
573 bool negate = false;
574 switch (function()) {
575 case SearchRule::FuncContainsNot:
576 case SearchRule::FuncNotEqual:
577 case SearchRule::FuncNotRegExp:
578 case SearchRule::FuncHasNoAttachment:
579 case SearchRule::FuncIsNotInCategory:
580 case SearchRule::FuncIsNotInAddressbook:
581 case SearchRule::FuncNotStartWith:
582 case SearchRule::FuncNotEndWith:
583 negate = true;
584 default:
585 break;
586 }
587 return negate;
588}
589
590QDataStream &SearchRule::operator>>(QDataStream &s) const
591{
592 s << mField << functionToString(mFunction) << mContents;
593 return s;
594}
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)
int toInt(bool *ok, int base) const const
QByteArray toLatin1() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:00:25 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.