mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Add group search
* Allow searching by group using the `group:` field. * Group hierarchies can be searched by including a '/' in the search term.
This commit is contained in:
parent
485852c9db
commit
f1080d633e
@ -54,7 +54,6 @@ QList<Entry*> EntrySearcher::search(const QList<SearchTerm>& searchTerms, const
|
|||||||
QList<Entry*> EntrySearcher::search(const QString& searchString, const Group* baseGroup, bool forceSearch)
|
QList<Entry*> EntrySearcher::search(const QString& searchString, const Group* baseGroup, bool forceSearch)
|
||||||
{
|
{
|
||||||
Q_ASSERT(baseGroup);
|
Q_ASSERT(baseGroup);
|
||||||
|
|
||||||
parseSearchTerms(searchString);
|
parseSearchTerms(searchString);
|
||||||
return repeat(baseGroup, forceSearch);
|
return repeat(baseGroup, forceSearch);
|
||||||
}
|
}
|
||||||
@ -73,7 +72,7 @@ QList<Entry*> EntrySearcher::repeat(const Group* baseGroup, bool forceSearch)
|
|||||||
QList<Entry*> results;
|
QList<Entry*> results;
|
||||||
for (const auto group : baseGroup->groupsRecursive(true)) {
|
for (const auto group : baseGroup->groupsRecursive(true)) {
|
||||||
if (forceSearch || group->resolveSearchingEnabled()) {
|
if (forceSearch || group->resolveSearchingEnabled()) {
|
||||||
for (auto* entry : group->entries()) {
|
for (const auto entry : group->entries()) {
|
||||||
if (searchEntryImpl(entry)) {
|
if (searchEntryImpl(entry)) {
|
||||||
results.append(entry);
|
results.append(entry);
|
||||||
}
|
}
|
||||||
@ -142,12 +141,14 @@ bool EntrySearcher::isCaseSensitive()
|
|||||||
return m_caseSensitive;
|
return m_caseSensitive;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntrySearcher::searchEntryImpl(Entry* entry)
|
bool EntrySearcher::searchEntryImpl(const Entry* entry)
|
||||||
{
|
{
|
||||||
// Pre-load in case they are needed
|
// Pre-load in case they are needed
|
||||||
auto attributes_keys = entry->attributes()->customKeys();
|
auto attributes_keys = entry->attributes()->customKeys();
|
||||||
auto attributes = QStringList(attributes_keys + entry->attributes()->values(attributes_keys));
|
auto attributes = QStringList(attributes_keys + entry->attributes()->values(attributes_keys));
|
||||||
auto attachments = QStringList(entry->attachments()->keys());
|
auto attachments = QStringList(entry->attachments()->keys());
|
||||||
|
// Build a group hierarchy to allow searching for e.g. /group1/subgroup*
|
||||||
|
auto hierarchy = entry->group()->hierarchy().join('/').prepend("/");
|
||||||
|
|
||||||
bool found;
|
bool found;
|
||||||
for (const auto& term : m_searchTerms) {
|
for (const auto& term : m_searchTerms) {
|
||||||
@ -181,6 +182,14 @@ bool EntrySearcher::searchEntryImpl(Entry* entry)
|
|||||||
found = entry->attributes()->contains(term.word)
|
found = entry->attributes()->contains(term.word)
|
||||||
&& term.regex.match(entry->attributes()->value(term.word)).hasMatch();
|
&& term.regex.match(entry->attributes()->value(term.word)).hasMatch();
|
||||||
break;
|
break;
|
||||||
|
case Field::Group:
|
||||||
|
// Match against the full hierarchy if the word contains a '/' otherwise just the group name
|
||||||
|
if (term.word.contains('/')) {
|
||||||
|
found = term.regex.match(hierarchy).hasMatch();
|
||||||
|
} else {
|
||||||
|
found = term.regex.match(entry->group()->name()).hasMatch();
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// Terms without a specific field try to match title, username, url, and notes
|
// Terms without a specific field try to match title, username, url, and notes
|
||||||
found = term.regex.match(entry->resolvePlaceholder(entry->title())).hasMatch()
|
found = term.regex.match(entry->resolvePlaceholder(entry->title())).hasMatch()
|
||||||
@ -209,7 +218,8 @@ void EntrySearcher::parseSearchTerms(const QString& searchString)
|
|||||||
{QStringLiteral("title"), Field::Title},
|
{QStringLiteral("title"), Field::Title},
|
||||||
{QStringLiteral("u"), Field::Username}, // u: stands for username rather than url
|
{QStringLiteral("u"), Field::Username}, // u: stands for username rather than url
|
||||||
{QStringLiteral("url"), Field::Url},
|
{QStringLiteral("url"), Field::Url},
|
||||||
{QStringLiteral("username"), Field::Username}};
|
{QStringLiteral("username"), Field::Username},
|
||||||
|
{QStringLiteral("group"), Field::Group}};
|
||||||
|
|
||||||
m_searchTerms.clear();
|
m_searchTerms.clear();
|
||||||
auto results = m_termParser.globalMatch(searchString);
|
auto results = m_termParser.globalMatch(searchString);
|
||||||
|
@ -38,7 +38,8 @@ public:
|
|||||||
Notes,
|
Notes,
|
||||||
AttributeKV,
|
AttributeKV,
|
||||||
Attachment,
|
Attachment,
|
||||||
AttributeValue
|
AttributeValue,
|
||||||
|
Group
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SearchTerm
|
struct SearchTerm
|
||||||
@ -64,7 +65,7 @@ public:
|
|||||||
bool isCaseSensitive();
|
bool isCaseSensitive();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool searchEntryImpl(Entry* entry);
|
bool searchEntryImpl(const Entry* entry);
|
||||||
void parseSearchTerms(const QString& searchString);
|
void parseSearchTerms(const QString& searchString);
|
||||||
|
|
||||||
bool m_caseSensitive;
|
bool m_caseSensitive;
|
||||||
|
@ -235,6 +235,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QLabel" name="label_16">
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">group (g)</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -262,3 +262,60 @@ void TestEntrySearcher::testCustomAttributesAreSearched()
|
|||||||
m_searchResult = m_entrySearcher.search("_testAttribute:test _testProtected:testP2", m_rootGroup);
|
m_searchResult = m_entrySearcher.search("_testAttribute:test _testProtected:testP2", m_rootGroup);
|
||||||
QCOMPARE(m_searchResult.count(), 2);
|
QCOMPARE(m_searchResult.count(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestEntrySearcher::testGroup()
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Root
|
||||||
|
* - group1 (1 entry)
|
||||||
|
* - subgroup1 (2 entries)
|
||||||
|
* - group2
|
||||||
|
* - subgroup2 (1 entry)
|
||||||
|
*/
|
||||||
|
Group* group1 = new Group();
|
||||||
|
Group* group2 = new Group();
|
||||||
|
|
||||||
|
group1->setParent(m_rootGroup);
|
||||||
|
group1->setName("group1");
|
||||||
|
group2->setParent(m_rootGroup);
|
||||||
|
group2->setName("group2");
|
||||||
|
|
||||||
|
Group* subgroup1 = new Group();
|
||||||
|
subgroup1->setName("subgroup1");
|
||||||
|
subgroup1->setParent(group1);
|
||||||
|
|
||||||
|
Group* subgroup2 = new Group();
|
||||||
|
subgroup2->setName("subgroup2");
|
||||||
|
subgroup2->setParent(group2);
|
||||||
|
|
||||||
|
Entry* eGroup1 = new Entry();
|
||||||
|
eGroup1->setTitle("Entry Group 1");
|
||||||
|
eGroup1->setGroup(group1);
|
||||||
|
|
||||||
|
Entry* eSub1 = new Entry();
|
||||||
|
eSub1->setTitle("test search term test");
|
||||||
|
eSub1->setGroup(subgroup1);
|
||||||
|
|
||||||
|
Entry* eSub2 = new Entry();
|
||||||
|
eSub2->setNotes("test test");
|
||||||
|
eSub2->setGroup(subgroup1);
|
||||||
|
|
||||||
|
Entry* eSub3 = new Entry();
|
||||||
|
eSub3->setNotes("test term test");
|
||||||
|
eSub3->setGroup(subgroup2);
|
||||||
|
|
||||||
|
m_searchResult = m_entrySearcher.search("group:subgroup", m_rootGroup);
|
||||||
|
QCOMPARE(m_searchResult.count(), 3);
|
||||||
|
|
||||||
|
m_searchResult = m_entrySearcher.search("g:subgroup1", m_rootGroup);
|
||||||
|
QCOMPARE(m_searchResult.count(), 2);
|
||||||
|
|
||||||
|
m_searchResult = m_entrySearcher.search("g:subgroup1 search", m_rootGroup);
|
||||||
|
QCOMPARE(m_searchResult.count(), 1);
|
||||||
|
|
||||||
|
m_searchResult = m_entrySearcher.search("g:*1/sub*1", m_rootGroup);
|
||||||
|
QCOMPARE(m_searchResult.count(), 2);
|
||||||
|
|
||||||
|
m_searchResult = m_entrySearcher.search("g:/group1 search", m_rootGroup);
|
||||||
|
QCOMPARE(m_searchResult.count(), 1);
|
||||||
|
}
|
||||||
|
@ -36,6 +36,7 @@ private slots:
|
|||||||
void testAllAttributesAreSearched();
|
void testAllAttributesAreSearched();
|
||||||
void testSearchTermParser();
|
void testSearchTermParser();
|
||||||
void testCustomAttributesAreSearched();
|
void testCustomAttributesAreSearched();
|
||||||
|
void testGroup();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Group* m_rootGroup;
|
Group* m_rootGroup;
|
||||||
|
Loading…
Reference in New Issue
Block a user