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

KDE's Doxygen guidelines are available online.