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

KDE's Doxygen guidelines are available online.