File indexing completed on 2025-01-05 04:58:22

0001 /*
0002  * This file is part of libkldap.
0003  *
0004  * SPDX-FileCopyrightText: 2002 Klarälvdalens Datakonsult AB
0005  * SPDX-FileContributor: Steffen Hansen <hansen@kde.org>
0006  *
0007  * SPDX-License-Identifier: LGPL-2.0-or-later
0008  */
0009 
0010 #include "ldapsearchdialog.h"
0011 
0012 #include <KLDAPWidgets/LdapClient>
0013 
0014 #include <KLDAPWidgets/LdapClientSearchConfig>
0015 #include <KLDAPWidgets/LdapSearchClientReadConfigServerJob>
0016 #include <KLineEditEventHandler>
0017 #include <Libkdepim/ProgressIndicatorLabel>
0018 
0019 #include <QApplication>
0020 #include <QCheckBox>
0021 #include <QClipboard>
0022 #include <QCloseEvent>
0023 #include <QFrame>
0024 #include <QGridLayout>
0025 #include <QGroupBox>
0026 #include <QHeaderView>
0027 #include <QLabel>
0028 #include <QMenu>
0029 #include <QPair>
0030 #include <QPointer>
0031 #include <QPushButton>
0032 #include <QSortFilterProxyModel>
0033 #include <QTableView>
0034 #include <QVBoxLayout>
0035 
0036 #include <KCMultiDialog>
0037 #include <KConfig>
0038 #include <KConfigGroup>
0039 #include <KMessageBox>
0040 #include <KPluginMetaData>
0041 #include <QComboBox>
0042 #include <QDialogButtonBox>
0043 #include <QLineEdit>
0044 #include <kldapcore/ldapobject.h>
0045 #include <kldapcore/ldapserver.h>
0046 
0047 #include <KConfigGroup>
0048 #include <KGuiItem>
0049 #include <KLocalizedString>
0050 #include <QDialogButtonBox>
0051 #include <QLocale>
0052 
0053 using namespace PimCommon;
0054 static QString asUtf8(const QByteArray &val)
0055 {
0056     if (val.isEmpty()) {
0057         return {};
0058     }
0059 
0060     const char *data = val.data();
0061 
0062     // QString::fromUtf8() bug workaround
0063     if (data[val.size() - 1] == '\0') {
0064         return QString::fromUtf8(data, val.size() - 1);
0065     } else {
0066         return QString::fromUtf8(data, val.size());
0067     }
0068 }
0069 
0070 static QString join(const KLDAPCore::LdapAttrValue &lst, const QString &sep)
0071 {
0072     QString res;
0073     bool already = false;
0074     KLDAPCore::LdapAttrValue::ConstIterator end(lst.constEnd());
0075     for (KLDAPCore::LdapAttrValue::ConstIterator it = lst.constBegin(); it != end; ++it) {
0076         if (already) {
0077             res += sep;
0078         }
0079 
0080         already = true;
0081         res += asUtf8(*it);
0082     }
0083 
0084     return res;
0085 }
0086 
0087 static QMap<QString, QString> &adrbookattr2ldap()
0088 {
0089     static QMap<QString, QString> keys;
0090 
0091     if (keys.isEmpty()) {
0092         keys[i18nc("@item LDAP search key", "Title")] = QStringLiteral("title");
0093         keys[i18n("Full Name")] = QStringLiteral("cn");
0094         keys[i18nc("@item LDAP search key", "Email")] = QStringLiteral("mail");
0095         keys[i18n("Home Number")] = QStringLiteral("homePhone");
0096         keys[i18n("Work Number")] = QStringLiteral("telephoneNumber");
0097         keys[i18n("Mobile Number")] = QStringLiteral("mobile");
0098         keys[i18n("Fax Number")] = QStringLiteral("facsimileTelephoneNumber");
0099         keys[i18n("Pager")] = QStringLiteral("pager");
0100         keys[i18n("Street")] = QStringLiteral("street");
0101         keys[i18nc("@item LDAP search key", "State")] = QStringLiteral("st");
0102         keys[i18n("Country")] = QStringLiteral("co");
0103         keys[i18n("City")] = QStringLiteral("l"); // krazy:exclude=doublequote_chars
0104         keys[i18n("Organization")] = QStringLiteral("o"); // krazy:exclude=doublequote_chars
0105         keys[i18n("Company")] = QStringLiteral("Company");
0106         keys[i18n("Department")] = QStringLiteral("department");
0107         keys[i18n("Zip Code")] = QStringLiteral("postalCode");
0108         keys[i18n("Postal Address")] = QStringLiteral("postalAddress");
0109         keys[i18n("Description")] = QStringLiteral("description");
0110         keys[i18n("User ID")] = QStringLiteral("uid");
0111     }
0112 
0113     return keys;
0114 }
0115 
0116 static QString makeFilter(const QString &query, LdapSearchDialog::FilterType attr, bool startsWith)
0117 {
0118     /* The reasoning behind this filter is:
0119      * If it's a person, or a distlist, show it, even if it doesn't have an email address.
0120      * If it's not a person, or a distlist, only show it if it has an email attribute.
0121      * This allows both resource accounts with an email address which are not a person and
0122      * person entries without an email address to show up, while still not showing things
0123      * like structural entries in the ldap tree. */
0124     QString result(QStringLiteral("&(|(objectclass=person)(objectclass=groupofnames)(mail=*))("));
0125     if (query.isEmpty()) {
0126         // Return a filter that matches everything
0127         return result + QStringLiteral("|(cn=*)(sn=*)") + QLatin1Char(')');
0128     }
0129 
0130     if (attr == LdapSearchDialog::Name) {
0131         result += startsWith ? QStringLiteral("|(cn=%1*)(sn=%2*)") : QStringLiteral("|(cn=*%1*)(sn=*%2*)");
0132         result = result.arg(query, query);
0133     } else {
0134         result += startsWith ? QStringLiteral("%1=%2*") : QStringLiteral("%1=*%2*");
0135         if (attr == LdapSearchDialog::Email) {
0136             result = result.arg(QStringLiteral("mail"), query);
0137         } else if (attr == LdapSearchDialog::HomeNumber) {
0138             result = result.arg(QStringLiteral("homePhone"), query);
0139         } else if (attr == LdapSearchDialog::WorkNumber) {
0140             result = result.arg(QStringLiteral("telephoneNumber"), query);
0141         } else {
0142             // Error?
0143             result.clear();
0144             return result;
0145         }
0146     }
0147     result += QLatin1Char(')');
0148     return result;
0149 }
0150 
0151 static KContacts::Addressee convertLdapAttributesToAddressee(const KLDAPCore::LdapAttrMap &attrs)
0152 {
0153     KContacts::Addressee addr;
0154 
0155     // name
0156     if (!attrs.value(QStringLiteral("cn")).isEmpty()) {
0157         addr.setNameFromString(asUtf8(attrs[QStringLiteral("cn")].first()));
0158     }
0159 
0160     // email
0161     KLDAPCore::LdapAttrValue lst = attrs[QStringLiteral("mail")];
0162     KLDAPCore::LdapAttrValue::ConstIterator it = lst.constBegin();
0163     bool pref = true;
0164     while (it != lst.constEnd()) {
0165         KContacts::Email email(asUtf8(*it));
0166         email.setPreferred(pref);
0167         addr.addEmail(email);
0168         pref = false;
0169         ++it;
0170     }
0171 
0172     if (!attrs.value(QStringLiteral("o")).isEmpty()) {
0173         addr.setOrganization(asUtf8(attrs[QStringLiteral("o")].first()));
0174     }
0175     if (addr.organization().isEmpty() && !attrs.value(QStringLiteral("Company")).isEmpty()) {
0176         addr.setOrganization(asUtf8(attrs[QStringLiteral("Company")].first()));
0177     }
0178 
0179     // Address
0180     KContacts::Address workAddr(KContacts::Address::Work);
0181 
0182     if (!attrs.value(QStringLiteral("department")).isEmpty()) {
0183         addr.setDepartment(asUtf8(attrs[QStringLiteral("department")].first()));
0184     }
0185 
0186     if (!workAddr.isEmpty()) {
0187         addr.insertAddress(workAddr);
0188     }
0189 
0190     // phone
0191     if (!attrs.value(QStringLiteral("homePhone")).isEmpty()) {
0192         KContacts::PhoneNumber homeNr = asUtf8(attrs[QStringLiteral("homePhone")].first());
0193         homeNr.setType(KContacts::PhoneNumber::Home);
0194         addr.insertPhoneNumber(homeNr);
0195     }
0196 
0197     if (!attrs.value(QStringLiteral("telephoneNumber")).isEmpty()) {
0198         KContacts::PhoneNumber workNr = asUtf8(attrs[QStringLiteral("telephoneNumber")].first());
0199         workNr.setType(KContacts::PhoneNumber::Work);
0200         addr.insertPhoneNumber(workNr);
0201     }
0202 
0203     if (!attrs.value(QStringLiteral("facsimileTelephoneNumber")).isEmpty()) {
0204         KContacts::PhoneNumber faxNr = asUtf8(attrs[QStringLiteral("facsimileTelephoneNumber")].first());
0205         faxNr.setType(KContacts::PhoneNumber::Fax);
0206         addr.insertPhoneNumber(faxNr);
0207     }
0208 
0209     if (!attrs.value(QStringLiteral("mobile")).isEmpty()) {
0210         KContacts::PhoneNumber cellNr = asUtf8(attrs[QStringLiteral("mobile")].first());
0211         cellNr.setType(KContacts::PhoneNumber::Cell);
0212         addr.insertPhoneNumber(cellNr);
0213     }
0214 
0215     if (!attrs.value(QStringLiteral("pager")).isEmpty()) {
0216         KContacts::PhoneNumber pagerNr = asUtf8(attrs[QStringLiteral("pager")].first());
0217         pagerNr.setType(KContacts::PhoneNumber::Pager);
0218         addr.insertPhoneNumber(pagerNr);
0219     }
0220 
0221     return addr;
0222 }
0223 
0224 class ContactListModel : public QAbstractTableModel
0225 {
0226 public:
0227     enum Role { ServerRole = Qt::UserRole + 1 };
0228 
0229     explicit ContactListModel(QObject *parent)
0230         : QAbstractTableModel(parent)
0231     {
0232     }
0233 
0234     void addContact(const KLDAPCore::LdapAttrMap &contact, const QString &server)
0235     {
0236         beginResetModel();
0237         mContactList.append(contact);
0238         mServerList.append(server);
0239         endResetModel();
0240     }
0241 
0242     [[nodiscard]] QPair<KLDAPCore::LdapAttrMap, QString> contact(const QModelIndex &index) const
0243     {
0244         if (!index.isValid() || index.row() < 0 || index.row() >= mContactList.count()) {
0245             return qMakePair(KLDAPCore::LdapAttrMap(), QString());
0246         }
0247 
0248         return qMakePair(mContactList.at(index.row()), mServerList.at(index.row()));
0249     }
0250 
0251     [[nodiscard]] QString email(const QModelIndex &index) const
0252     {
0253         if (!index.isValid() || index.row() < 0 || index.row() >= mContactList.count()) {
0254             return {};
0255         }
0256 
0257         return asUtf8(mContactList.at(index.row()).value(QStringLiteral("mail")).first()).trimmed();
0258     }
0259 
0260     [[nodiscard]] QString fullName(const QModelIndex &index) const
0261     {
0262         if (!index.isValid() || index.row() < 0 || index.row() >= mContactList.count()) {
0263             return {};
0264         }
0265 
0266         return asUtf8(mContactList.at(index.row()).value(QStringLiteral("cn")).first()).trimmed();
0267     }
0268 
0269     void clear()
0270     {
0271         beginResetModel();
0272         mContactList.clear();
0273         mServerList.clear();
0274         endResetModel();
0275     }
0276 
0277     [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override
0278     {
0279         if (!parent.isValid()) {
0280             return mContactList.count();
0281         } else {
0282             return 0;
0283         }
0284     }
0285 
0286     [[nodiscard]] int columnCount(const QModelIndex &parent = QModelIndex()) const override
0287     {
0288         if (!parent.isValid()) {
0289             return 18;
0290         } else {
0291             return 0;
0292         }
0293     }
0294 
0295     [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override
0296     {
0297         if (orientation == Qt::Vertical || role != Qt::DisplayRole || section < 0 || section > 17) {
0298             return {};
0299         }
0300 
0301         switch (section) {
0302         case 0:
0303             return i18n("Full Name");
0304         case 1:
0305             return i18nc("@title:column Column containing email addresses", "Email");
0306         case 2:
0307             return i18n("Home Number");
0308         case 3:
0309             return i18n("Work Number");
0310         case 4:
0311             return i18n("Mobile Number");
0312         case 5:
0313             return i18n("Fax Number");
0314         case 6:
0315             return i18n("Company");
0316         case 7:
0317             return i18n("Organization");
0318         case 8:
0319             return i18n("Street");
0320         case 9:
0321             return i18nc("@title:column Column containing the residential state of the address", "State");
0322         case 10:
0323             return i18n("Country");
0324         case 11:
0325             return i18n("Zip Code");
0326         case 12:
0327             return i18n("Postal Address");
0328         case 13:
0329             return i18n("City");
0330         case 14:
0331             return i18n("Department");
0332         case 15:
0333             return i18n("Description");
0334         case 16:
0335             return i18n("User ID");
0336         case 17:
0337             return i18nc("@title:column Column containing title of the person", "Title");
0338         default:
0339             return {};
0340         }
0341     }
0342 
0343     [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
0344     {
0345         if (!index.isValid()) {
0346             return {};
0347         }
0348 
0349         if (index.row() < 0 || index.row() >= mContactList.count() || index.column() < 0 || index.column() > 17) {
0350             return {};
0351         }
0352 
0353         if (role == ServerRole) {
0354             return mServerList.at(index.row());
0355         }
0356 
0357         if ((role != Qt::DisplayRole) && (role != Qt::ToolTipRole)) {
0358             return {};
0359         }
0360 
0361         const KLDAPCore::LdapAttrMap map = mContactList.at(index.row());
0362 
0363         switch (index.column()) {
0364         case 0:
0365             return join(map.value(QStringLiteral("cn")), QStringLiteral(", "));
0366         case 1:
0367             return join(map.value(QStringLiteral("mail")), QStringLiteral(", "));
0368         case 2:
0369             return join(map.value(QStringLiteral("homePhone")), QStringLiteral(", "));
0370         case 3:
0371             return join(map.value(QStringLiteral("telephoneNumber")), QStringLiteral(", "));
0372         case 4:
0373             return join(map.value(QStringLiteral("mobile")), QStringLiteral(", "));
0374         case 5:
0375             return join(map.value(QStringLiteral("facsimileTelephoneNumber")), QStringLiteral(", "));
0376         case 6:
0377             return join(map.value(QStringLiteral("Company")), QStringLiteral(", "));
0378         case 7:
0379             return join(map.value(QStringLiteral("o")), QStringLiteral(", "));
0380         case 8:
0381             return join(map.value(QStringLiteral("street")), QStringLiteral(", "));
0382         case 9:
0383             return join(map.value(QStringLiteral("st")), QStringLiteral(", "));
0384         case 10:
0385             return join(map.value(QStringLiteral("co")), QStringLiteral(", "));
0386         case 11:
0387             return join(map.value(QStringLiteral("postalCode")), QStringLiteral(", "));
0388         case 12:
0389             return join(map.value(QStringLiteral("postalAddress")), QStringLiteral(", "));
0390         case 13:
0391             return join(map.value(QStringLiteral("l")), QStringLiteral(", "));
0392         case 14:
0393             return join(map.value(QStringLiteral("department")), QStringLiteral(", "));
0394         case 15:
0395             return join(map.value(QStringLiteral("description")), QStringLiteral(", "));
0396         case 16:
0397             return join(map.value(QStringLiteral("uid")), QStringLiteral(", "));
0398         case 17:
0399             return join(map.value(QStringLiteral("title")), QStringLiteral(", "));
0400         default:
0401             return {};
0402         }
0403     }
0404 
0405 private:
0406     QList<KLDAPCore::LdapAttrMap> mContactList;
0407     QStringList mServerList;
0408 };
0409 
0410 class Q_DECL_HIDDEN LdapSearchDialog::LdapSearchDialogPrivate
0411 {
0412 public:
0413     LdapSearchDialogPrivate(LdapSearchDialog *qq)
0414         : q(qq)
0415     {
0416     }
0417 
0418     QList<QPair<KLDAPCore::LdapAttrMap, QString>> selectedItems()
0419     {
0420         QList<QPair<KLDAPCore::LdapAttrMap, QString>> contacts;
0421 
0422         const QModelIndexList selected = mResultView->selectionModel()->selectedRows();
0423         const int numberOfSelectedElement(selected.count());
0424         contacts.reserve(numberOfSelectedElement);
0425         for (int i = 0; i < numberOfSelectedElement; ++i) {
0426             contacts.append(mModel->contact(sortproxy->mapToSource(selected.at(i))));
0427         }
0428 
0429         return contacts;
0430     }
0431 
0432     void saveSettings();
0433     void restoreSettings();
0434     void cancelQuery();
0435 
0436     void slotAddResult(const KLDAPWidgets::LdapClient &, const KLDAPCore::LdapObject &);
0437     void slotSetScope(bool);
0438     void slotStartSearch();
0439     void slotStopSearch();
0440     void slotSearchDone();
0441     void slotError(const QString &);
0442     void slotSelectAll();
0443     void slotUnselectAll();
0444     void slotSelectionChanged();
0445 
0446     LdapSearchDialog *const q;
0447     KGuiItem startSearchGuiItem;
0448     KGuiItem stopSearchGuiItem;
0449     int mNumHosts = 0;
0450     QList<KLDAPWidgets::LdapClient *> mLdapClientList;
0451     bool mIsConfigured = false;
0452     KContacts::Addressee::List mSelectedContacts;
0453 
0454     QComboBox *mFilterCombo = nullptr;
0455     QComboBox *mSearchType = nullptr;
0456     QLineEdit *mSearchEdit = nullptr;
0457 
0458     QCheckBox *mRecursiveCheckbox = nullptr;
0459     QTableView *mResultView = nullptr;
0460     QPushButton *mSearchButton = nullptr;
0461     ContactListModel *mModel = nullptr;
0462     KPIM::ProgressIndicatorLabel *progressIndication = nullptr;
0463     QSortFilterProxyModel *sortproxy = nullptr;
0464     QLineEdit *searchLine = nullptr;
0465     QPushButton *user1Button = nullptr;
0466 };
0467 
0468 LdapSearchDialog::LdapSearchDialog(QWidget *parent)
0469     : QDialog(parent)
0470     , d(new LdapSearchDialogPrivate(this))
0471 {
0472     setWindowTitle(i18nc("@title:window", "Import Contacts from LDAP"));
0473     auto mainLayout = new QVBoxLayout(this);
0474 
0475     auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel, this);
0476     d->user1Button = new QPushButton;
0477     buttonBox->addButton(d->user1Button, QDialogButtonBox::ActionRole);
0478 
0479     auto user2Button = new QPushButton;
0480     buttonBox->addButton(user2Button, QDialogButtonBox::ActionRole);
0481 
0482     connect(d->user1Button, &QPushButton::clicked, this, &LdapSearchDialog::slotUser1);
0483     connect(user2Button, &QPushButton::clicked, this, &LdapSearchDialog::slotUser2);
0484     connect(buttonBox, &QDialogButtonBox::rejected, this, &LdapSearchDialog::slotCancelClicked);
0485     d->user1Button->setDefault(true);
0486     setModal(false);
0487     KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::close());
0488     auto page = new QFrame(this);
0489     mainLayout->addWidget(page);
0490     mainLayout->addWidget(buttonBox);
0491 
0492     auto topLayout = new QVBoxLayout(page);
0493     topLayout->setContentsMargins({});
0494 
0495     auto groupBox = new QGroupBox(i18n("Search for Addresses in Directory"), page);
0496     auto boxLayout = new QGridLayout();
0497     groupBox->setLayout(boxLayout);
0498     boxLayout->setColumnStretch(1, 1);
0499 
0500     auto label = new QLabel(i18n("Search for:"), groupBox);
0501     boxLayout->addWidget(label, 0, 0);
0502 
0503     d->mSearchEdit = new QLineEdit(groupBox);
0504     d->mSearchEdit->setClearButtonEnabled(true);
0505     boxLayout->addWidget(d->mSearchEdit, 0, 1);
0506     label->setBuddy(d->mSearchEdit);
0507 
0508     label = new QLabel(i18nc("In LDAP attribute", "in"), groupBox);
0509     boxLayout->addWidget(label, 0, 2);
0510 
0511     d->mFilterCombo = new QComboBox(groupBox);
0512     d->mFilterCombo->addItem(i18nc("@item:inlistbox Name of the contact", "Name"), QVariant::fromValue(Name));
0513     d->mFilterCombo->addItem(i18nc("@item:inlistbox email address of the contact", "Email"), QVariant::fromValue(Email));
0514     d->mFilterCombo->addItem(i18nc("@item:inlistbox", "Home Number"), QVariant::fromValue(HomeNumber));
0515     d->mFilterCombo->addItem(i18nc("@item:inlistbox", "Work Number"), QVariant::fromValue(WorkNumber));
0516     boxLayout->addWidget(d->mFilterCombo, 0, 3);
0517     d->startSearchGuiItem = KGuiItem(i18nc("@action:button Start searching", "&Search"), QStringLiteral("edit-find"));
0518     d->stopSearchGuiItem = KStandardGuiItem::stop();
0519 
0520     QSize buttonSize;
0521     d->mSearchButton = new QPushButton(groupBox);
0522     KGuiItem::assign(d->mSearchButton, d->startSearchGuiItem);
0523 
0524     buttonSize = d->mSearchButton->sizeHint();
0525     if (buttonSize.width() < d->mSearchButton->sizeHint().width()) {
0526         buttonSize = d->mSearchButton->sizeHint();
0527     }
0528     d->mSearchButton->setFixedWidth(buttonSize.width());
0529 
0530     d->mSearchButton->setDefault(true);
0531     boxLayout->addWidget(d->mSearchButton, 0, 4);
0532 
0533     d->mRecursiveCheckbox = new QCheckBox(i18n("Recursive search"), groupBox);
0534     d->mRecursiveCheckbox->setChecked(true);
0535     boxLayout->addWidget(d->mRecursiveCheckbox, 1, 0, 1, 5);
0536 
0537     d->mSearchType = new QComboBox(groupBox);
0538     d->mSearchType->addItem(i18n("Contains"));
0539     d->mSearchType->addItem(i18n("Starts With"));
0540     boxLayout->addWidget(d->mSearchType, 1, 3, 1, 2);
0541 
0542     topLayout->addWidget(groupBox);
0543 
0544     auto quickSearchLineLayout = new QHBoxLayout;
0545     quickSearchLineLayout->addStretch();
0546     d->searchLine = new QLineEdit;
0547     KLineEditEventHandler::catchReturnKey(d->searchLine);
0548     d->searchLine->setClearButtonEnabled(true);
0549     d->searchLine->setPlaceholderText(i18n("Search in result"));
0550     quickSearchLineLayout->addWidget(d->searchLine);
0551     topLayout->addLayout(quickSearchLineLayout);
0552 
0553     d->mResultView = new QTableView(page);
0554     d->mResultView->setSelectionMode(QTableView::MultiSelection);
0555     d->mResultView->setSelectionBehavior(QTableView::SelectRows);
0556     d->mModel = new ContactListModel(d->mResultView);
0557 
0558     d->sortproxy = new QSortFilterProxyModel(this);
0559     d->sortproxy->setFilterKeyColumn(-1); // Search in all column
0560     d->sortproxy->setSourceModel(d->mModel);
0561     d->sortproxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
0562     connect(d->searchLine, &QLineEdit::textChanged, d->sortproxy, &QSortFilterProxyModel::setFilterFixedString);
0563 
0564     d->mResultView->setModel(d->sortproxy);
0565     d->mResultView->verticalHeader()->hide();
0566     d->mResultView->setSortingEnabled(true);
0567     d->mResultView->horizontalHeader()->setSortIndicatorShown(true);
0568     connect(d->mResultView, &QTableView::clicked, this, [this]() {
0569         d->slotSelectionChanged();
0570     });
0571     topLayout->addWidget(d->mResultView);
0572 
0573     d->mResultView->setContextMenuPolicy(Qt::CustomContextMenu);
0574     connect(d->mResultView, &QTableView::customContextMenuRequested, this, &LdapSearchDialog::slotCustomContextMenuRequested);
0575 
0576     auto buttonLayout = new QHBoxLayout;
0577     buttonLayout->setContentsMargins({});
0578     topLayout->addLayout(buttonLayout);
0579 
0580     d->progressIndication = new KPIM::ProgressIndicatorLabel(i18n("Searching..."));
0581     buttonLayout->addWidget(d->progressIndication);
0582 
0583     auto buttons = new QDialogButtonBox(page);
0584     QPushButton *button = buttons->addButton(i18n("Select All"), QDialogButtonBox::ActionRole);
0585     connect(button, &QPushButton::clicked, this, [this]() {
0586         d->slotSelectAll();
0587     });
0588     button = buttons->addButton(i18n("Unselect All"), QDialogButtonBox::ActionRole);
0589     connect(button, &QPushButton::clicked, this, [this]() {
0590         d->slotUnselectAll();
0591     });
0592 
0593     buttonLayout->addWidget(buttons);
0594 
0595     d->user1Button->setText(i18n("Add Selected"));
0596     user2Button->setText(i18n("Configure LDAP Servers..."));
0597 
0598     connect(d->mRecursiveCheckbox, &QCheckBox::toggled, this, [this](bool state) {
0599         d->slotSetScope(state);
0600     });
0601     connect(d->mSearchButton, SIGNAL(clicked()), this, SLOT(slotStartSearch()));
0602 
0603     setTabOrder(d->mSearchEdit, d->mFilterCombo);
0604     setTabOrder(d->mFilterCombo, d->mSearchButton);
0605     d->mSearchEdit->setFocus();
0606 
0607     d->slotSelectionChanged();
0608     d->restoreSettings();
0609 }
0610 
0611 LdapSearchDialog::~LdapSearchDialog()
0612 {
0613     d->saveSettings();
0614 }
0615 
0616 void LdapSearchDialog::setSearchText(const QString &text)
0617 {
0618     d->mSearchEdit->setText(text);
0619 }
0620 
0621 KContacts::Addressee::List LdapSearchDialog::selectedContacts() const
0622 {
0623     return d->mSelectedContacts;
0624 }
0625 
0626 void LdapSearchDialog::slotCustomContextMenuRequested(const QPoint &pos)
0627 {
0628     const QModelIndex index = d->mResultView->indexAt(pos);
0629     if (index.isValid()) {
0630         QMenu menu(this);
0631         QAction *act = menu.addAction(i18n("Copy"));
0632         if (menu.exec(QCursor::pos()) == act) {
0633             QClipboard *cb = QApplication::clipboard();
0634             cb->setText(index.data().toString(), QClipboard::Clipboard);
0635         }
0636     }
0637 }
0638 
0639 void LdapSearchDialog::LdapSearchDialogPrivate::slotSelectionChanged()
0640 {
0641     user1Button->setEnabled(mResultView->selectionModel()->hasSelection());
0642 }
0643 
0644 void LdapSearchDialog::LdapSearchDialogPrivate::restoreSettings()
0645 {
0646     // Create one KLDAP::LdapClient per selected server and configure it.
0647 
0648     // First clean the list to make sure it is empty at
0649     // the beginning of the process
0650     qDeleteAll(mLdapClientList);
0651     mLdapClientList.clear();
0652 
0653     KConfig *config = KLDAPWidgets::LdapClientSearchConfig::config();
0654 
0655     KConfigGroup searchGroup(config, QStringLiteral("LDAPSearch"));
0656     mSearchType->setCurrentIndex(searchGroup.readEntry("SearchType", 0));
0657 
0658     // then read the config file and register all selected
0659     // server in the list
0660     KConfigGroup group(config, QStringLiteral("LDAP"));
0661     mNumHosts = group.readEntry("NumSelectedHosts", 0);
0662     if (!mNumHosts) {
0663         mIsConfigured = false;
0664     } else {
0665         mIsConfigured = true;
0666         auto clientSearchConfig = new KLDAPWidgets::LdapClientSearchConfig;
0667         for (int j = 0; j < mNumHosts; ++j) {
0668             auto ldapClient = new KLDAPWidgets::LdapClient(0, q);
0669             auto job = new KLDAPWidgets::LdapSearchClientReadConfigServerJob(q);
0670             job->setCurrentIndex(j);
0671             job->setActive(true);
0672             job->setConfig(group);
0673             job->setLdapClient(ldapClient);
0674             job->start();
0675             QStringList attrs;
0676 
0677             QMap<QString, QString>::ConstIterator end(adrbookattr2ldap().constEnd());
0678             for (QMap<QString, QString>::ConstIterator it = adrbookattr2ldap().constBegin(); it != end; ++it) {
0679                 attrs << *it;
0680             }
0681 
0682             ldapClient->setAttributes(attrs);
0683 
0684             // clang-format off
0685             q->connect(ldapClient, SIGNAL(result(KLDAPWidgets::LdapClient,KLDAPCore::LdapObject)), q, SLOT(slotAddResult(KLDAPWidgets::LdapClient,KLDAPCore::LdapObject)));
0686             // clang-format on
0687             q->connect(ldapClient, SIGNAL(done()), q, SLOT(slotSearchDone()));
0688             q->connect(ldapClient, &KLDAPWidgets::LdapClient::error, q, [this](const QString &err) {
0689                 slotError(err);
0690             });
0691 
0692             mLdapClientList.append(ldapClient);
0693         }
0694         delete clientSearchConfig;
0695 
0696         mModel->clear();
0697     }
0698     KConfigGroup groupHeader(config, QStringLiteral("Headers"));
0699     mResultView->horizontalHeader()->restoreState(groupHeader.readEntry("HeaderState", QByteArray()));
0700 
0701     KConfigGroup groupSize(config, QStringLiteral("Size"));
0702     const QSize dialogSize = groupSize.readEntry("Size", QSize());
0703     if (dialogSize.isValid()) {
0704         q->resize(dialogSize);
0705     } else {
0706         q->resize(QSize(600, 400).expandedTo(q->minimumSizeHint()));
0707     }
0708 }
0709 
0710 void LdapSearchDialog::LdapSearchDialogPrivate::saveSettings()
0711 {
0712     KConfig *config = KLDAPWidgets::LdapClientSearchConfig::config();
0713     KConfigGroup group(config, QStringLiteral("LDAPSearch"));
0714     group.writeEntry("SearchType", mSearchType->currentIndex());
0715 
0716     KConfigGroup groupHeader(config, QStringLiteral("Headers"));
0717     groupHeader.writeEntry("HeaderState", mResultView->horizontalHeader()->saveState());
0718     groupHeader.sync();
0719 
0720     KConfigGroup size(config, QStringLiteral("Size"));
0721     size.writeEntry("Size", q->size());
0722     size.sync();
0723 
0724     group.sync();
0725 }
0726 
0727 void LdapSearchDialog::LdapSearchDialogPrivate::cancelQuery()
0728 {
0729     for (KLDAPWidgets::LdapClient *client : std::as_const(mLdapClientList)) {
0730         client->cancelQuery();
0731     }
0732 }
0733 
0734 void LdapSearchDialog::LdapSearchDialogPrivate::slotAddResult(const KLDAPWidgets::LdapClient &client, const KLDAPCore::LdapObject &obj)
0735 {
0736     mModel->addContact(obj.attributes(), client.server().host());
0737 }
0738 
0739 void LdapSearchDialog::LdapSearchDialogPrivate::slotSetScope(bool rec)
0740 {
0741     for (KLDAPWidgets::LdapClient *client : std::as_const(mLdapClientList)) {
0742         if (rec) {
0743             client->setScope(QStringLiteral("sub"));
0744         } else {
0745             client->setScope(QStringLiteral("one"));
0746         }
0747     }
0748 }
0749 
0750 void LdapSearchDialog::LdapSearchDialogPrivate::slotStartSearch()
0751 {
0752     cancelQuery();
0753 
0754     if (!mIsConfigured) {
0755         KMessageBox::error(q, i18n("You must select an LDAP server before searching."));
0756         q->slotUser2();
0757         return;
0758     }
0759 
0760 #ifndef QT_NO_CURSOR
0761     QApplication::setOverrideCursor(Qt::WaitCursor);
0762 #endif
0763     KGuiItem::assign(mSearchButton, stopSearchGuiItem);
0764     progressIndication->start();
0765 
0766     q->disconnect(mSearchButton, SIGNAL(clicked()), q, SLOT(slotStartSearch()));
0767     q->connect(mSearchButton, SIGNAL(clicked()), q, SLOT(slotStopSearch()));
0768 
0769     const bool startsWith = (mSearchType->currentIndex() == 1);
0770 
0771     const QString filter = makeFilter(mSearchEdit->text().trimmed(), mFilterCombo->currentData().value<FilterType>(), startsWith);
0772 
0773     // loop in the list and run the KLDAP::LdapClients
0774     mModel->clear();
0775     for (KLDAPWidgets::LdapClient *client : std::as_const(mLdapClientList)) {
0776         client->startQuery(filter);
0777     }
0778 
0779     saveSettings();
0780 }
0781 
0782 void LdapSearchDialog::LdapSearchDialogPrivate::slotStopSearch()
0783 {
0784     cancelQuery();
0785     slotSearchDone();
0786 }
0787 
0788 void LdapSearchDialog::LdapSearchDialogPrivate::slotSearchDone()
0789 {
0790     // If there are no more active clients, we are done.
0791     for (KLDAPWidgets::LdapClient *client : std::as_const(mLdapClientList)) {
0792         if (client->isActive()) {
0793             return;
0794         }
0795     }
0796 
0797     q->disconnect(mSearchButton, SIGNAL(clicked()), q, SLOT(slotStopSearch()));
0798     q->connect(mSearchButton, SIGNAL(clicked()), q, SLOT(slotStartSearch()));
0799 
0800     KGuiItem::assign(mSearchButton, startSearchGuiItem);
0801     progressIndication->stop();
0802 #ifndef QT_NO_CURSOR
0803     QApplication::restoreOverrideCursor();
0804 #endif
0805 }
0806 
0807 void LdapSearchDialog::LdapSearchDialogPrivate::slotError(const QString &error)
0808 {
0809 #ifndef QT_NO_CURSOR
0810     QApplication::restoreOverrideCursor();
0811 #endif
0812     KMessageBox::error(q, error);
0813 }
0814 
0815 void LdapSearchDialog::closeEvent(QCloseEvent *e)
0816 {
0817     d->slotStopSearch();
0818     e->accept();
0819 }
0820 
0821 void LdapSearchDialog::LdapSearchDialogPrivate::slotUnselectAll()
0822 {
0823     mResultView->clearSelection();
0824     slotSelectionChanged();
0825 }
0826 
0827 void LdapSearchDialog::LdapSearchDialogPrivate::slotSelectAll()
0828 {
0829     mResultView->selectAll();
0830     slotSelectionChanged();
0831 }
0832 
0833 void LdapSearchDialog::slotUser1()
0834 {
0835     // Import selected items
0836 
0837     d->mSelectedContacts.clear();
0838 
0839     const QList<QPair<KLDAPCore::LdapAttrMap, QString>> &items = d->selectedItems();
0840 
0841     if (!items.isEmpty()) {
0842         const QDateTime now = QDateTime::currentDateTime();
0843 
0844         for (int i = 0; i < items.count(); ++i) {
0845             KContacts::Addressee contact = convertLdapAttributesToAddressee(items.at(i).first);
0846 
0847             // set a comment where the contact came from
0848             contact.setNote(i18nc("arguments are host name, datetime",
0849                                   "Imported from LDAP directory %1 on %2",
0850                                   items.at(i).second,
0851                                   QLocale().toString(now, QLocale::ShortFormat)));
0852 
0853             d->mSelectedContacts.append(contact);
0854         }
0855     }
0856 
0857     d->slotStopSearch();
0858     Q_EMIT contactsAdded();
0859 
0860     accept();
0861 }
0862 
0863 void LdapSearchDialog::slotUser2()
0864 {
0865     // Configure LDAP servers
0866 
0867     QPointer<KCMultiDialog> dialog = new KCMultiDialog(this);
0868     dialog->setWindowTitle(i18nc("@title:window", "Configure the Address Book LDAP Settings"));
0869     dialog->addModule(KPluginMetaData(QStringLiteral("pim6/kcms/kaddressbook/kcm_ldap")));
0870 
0871     if (dialog->exec()) { // krazy:exclude=crashy
0872         d->restoreSettings();
0873     }
0874     delete dialog;
0875 }
0876 
0877 void LdapSearchDialog::slotCancelClicked()
0878 {
0879     d->slotStopSearch();
0880     reject();
0881 }
0882 
0883 #include "moc_ldapsearchdialog.cpp"