diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 4bd77fabad294..d999868509a2a 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -43,6 +43,7 @@ set(client_UI_SRCS wizard/owncloudconnectionmethoddialog.ui wizard/owncloudhttpcredspage.ui wizard/owncloudsetupnocredspage.ui + wizard/termsofservicecheckwidget.ui wizard/webview.ui wizard/welcomepage.ui ) @@ -240,6 +241,10 @@ set(client_SRCS wizard/flow2authwidget.cpp wizard/owncloudsetuppage.h wizard/owncloudsetuppage.cpp + wizard/termsofservicecheckwidget.h + wizard/termsofservicecheckwidget.cpp + wizard/termsofservicewizardpage.h + wizard/termsofservicewizardpage.cpp wizard/owncloudwizardcommon.h wizard/owncloudwizardcommon.cpp wizard/owncloudwizard.h diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index 579b053a0dcaa..0a5a9f52765e5 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -1346,7 +1346,7 @@ void AccountSettings::slotAccountStateChanged() Q_UNREACHABLE(); break; case AccountState::NeedToSignTermsOfService: - showConnectionLabel(tr("You need to accept the terms of service")); + showConnectionLabel(tr("You need to accept the terms of service at %1.").arg(server)); break; } } else { diff --git a/src/gui/accountstate.cpp b/src/gui/accountstate.cpp index d167f76f87ec3..a59fed2731750 100644 --- a/src/gui/accountstate.cpp +++ b/src/gui/accountstate.cpp @@ -222,6 +222,11 @@ bool AccountState::isConnected() const return _state == Connected; } +bool AccountState::needsToSignTermsOfService() const +{ + return _state == NeedToSignTermsOfService; +} + void AccountState::tagLastSuccessfullETagRequest(const QDateTime &tp) { _timeOfLastETagCheck = tp; @@ -316,7 +321,7 @@ void AccountState::checkConnectivity() _connectionErrors.clear(); connect(conValidator, &ConnectionValidator::connectionResult, this, &AccountState::slotConnectionValidatorResult); - if (isConnected()) { + if (isConnected() || needsToSignTermsOfService()) { // Use a small authed propfind as a minimal ping when we're // already connected. conValidator->checkAuthentication(); diff --git a/src/gui/accountstate.h b/src/gui/accountstate.h index de4311dbe6059..0d52f77873bf1 100644 --- a/src/gui/accountstate.h +++ b/src/gui/accountstate.h @@ -127,6 +127,8 @@ class AccountState : public QObject, public QSharedData bool isConnected() const; + bool needsToSignTermsOfService() const; + /** Returns a new settings object for this account, already in the right groups. */ std::unique_ptr settings(); diff --git a/src/gui/connectionvalidator.cpp b/src/gui/connectionvalidator.cpp index ebe1c1af525f6..61ba6ee447510 100644 --- a/src/gui/connectionvalidator.cpp +++ b/src/gui/connectionvalidator.cpp @@ -221,13 +221,20 @@ void ConnectionValidator::slotAuthFailed(QNetworkReply *reply) stat = CredentialsWrong; } else if (reply->error() != QNetworkReply::NoError) { - _errors << job->errorStringParsingBody(); + QByteArray body; + _errors << job->errorStringParsingBody(&body); const int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (httpStatus == 503) { _errors.clear(); stat = ServiceUnavailable; + } else if (httpStatus == 403) { + const auto davException = job->errorStringParsingBodyException(body); + if (davException == QStringLiteral(R"(OCA\TermsOfService\TermsNotSignedException)")) { + qCInfo(lcConnectionValidator) << "The terms of service need to be signed"; + stat = NeedToSignTermsOfService; + } } } @@ -383,8 +390,8 @@ void TermsOfServiceChecker::slotServerTermsOfServiceRecieved(const QJsonDocument if (reply.object().contains("ocs")) { const auto needToSign = !reply.object().value("ocs").toObject().value("data").toObject().value("hasSigned").toBool(false); if (needToSign != _needToSign) { - qCInfo(lcConnectionValidator) << "_needToSign" << (_needToSign ? "need to sign" : "no need to sign"); _needToSign = needToSign; + qCInfo(lcConnectionValidator) << "_needToSign" << (_needToSign ? "need to sign" : "no need to sign"); emit needToSignChanged(); } } else if (_needToSign) { diff --git a/src/gui/owncloudsetupwizard.cpp b/src/gui/owncloudsetupwizard.cpp index 6b36e0fe7e478..2105ef6848f42 100644 --- a/src/gui/owncloudsetupwizard.cpp +++ b/src/gui/owncloudsetupwizard.cpp @@ -13,13 +13,6 @@ * for more details. */ -#include -#include -#include -#include -#include -#include - #include "accessmanager.h" #include "account.h" #include "accountmanager.h" @@ -31,6 +24,7 @@ #include "networkjobs.h" #include "owncloudgui.h" #include "owncloudsetupwizard.h" +#include "owncloudpropagator_p.h" #include "sslerrordialog.h" #include "wizard/owncloudwizard.h" #include "wizard/owncloudwizardcommon.h" @@ -39,6 +33,13 @@ #include "creds/abstractcredentials.h" #include "creds/dummycredentials.h" +#include +#include +#include +#include +#include +#include + namespace OCC { OwncloudSetupWizard::OwncloudSetupWizard(QObject *parent) @@ -358,8 +359,7 @@ void OwncloudSetupWizard::slotConnectToOCUrl(const QString &url) _ocWizard->setField(QLatin1String("OCUrl"), url); _ocWizard->appendToConfigurationLog(tr("Trying to connect to %1 at %2 …") - .arg(Theme::instance()->appNameGUI()) - .arg(url)); + .arg(Theme::instance()->appNameGUI(), url)); testOwnCloudConnect(); }); @@ -377,7 +377,22 @@ void OwncloudSetupWizard::testOwnCloudConnect() job->setFollowRedirects(false); job->setProperties(QList() << "getlastmodified"); connect(job, &PropfindJob::result, _ocWizard, &OwncloudWizard::successfulStep); - connect(job, &PropfindJob::finishedWithError, this, &OwncloudSetupWizard::slotAuthError); + connect(job, &PropfindJob::finishedWithError, this, [this] (QNetworkReply *reply) -> void { + if (reply && reply->error() == QNetworkReply::ContentAccessDenied) { + // A 403 might indicate that the terms of service need to be signed. + // catch this special case here, fall back to the standard error handler if it's not TOS-related + auto davException = OCC::getExceptionFromReply(reply); + if (!davException.first.isEmpty() && davException.first == QStringLiteral(R"(OCA\TermsOfService\TermsNotSignedException)")) { + // authentication was successful, but the user hasn't signed the terms of service yet. Prompt for that in the next step + qCInfo(lcWizard) << "Terms of service not accepted yet! Will prompt the user in the next step"; + _ocWizard->_needsToAcceptTermsOfService = true; + _ocWizard->successfulStep(); + return; + } + } + slotAuthError(); + }); + job->start(); } @@ -414,14 +429,15 @@ void OwncloudSetupWizard::slotAuthError() "\"%1\". The URL is bad, the server is misconfigured.") .arg(Utility::escape(redirectUrl.toString())); + } else if (reply->error() == QNetworkReply::ContentNotFoundError) { // A 404 is actually a success: we were authorized to know that the folder does // not exist. It will be created later... - } else if (reply->error() == QNetworkReply::ContentNotFoundError) { _ocWizard->successfulStep(); return; - // Provide messages for other errors, such as invalid credentials. } else if (reply->error() != QNetworkReply::NoError) { + // Provide messages for other errors, such as invalid credentials. + if (!_ocWizard->account()->credentials()->stillValid(reply)) { errorMsg = tr("Access forbidden by server. To verify that you have proper access, " "click here to access the service with your browser.") @@ -430,8 +446,8 @@ void OwncloudSetupWizard::slotAuthError() errorMsg = job->errorStringParsingBody(); } - // Something else went wrong, maybe the response was 200 but with invalid data. } else { + // Something else went wrong, maybe the response was 200 but with invalid data. errorMsg = tr("There was an invalid response to an authenticated WebDAV request"); } diff --git a/src/gui/owncloudsetupwizard.h b/src/gui/owncloudsetupwizard.h index d7288ee6c8b77..b3ad88a8f9680 100644 --- a/src/gui/owncloudsetupwizard.h +++ b/src/gui/owncloudsetupwizard.h @@ -30,6 +30,7 @@ namespace OCC { class AccountState; +class TermsOfServiceChecker; class OwncloudWizard; @@ -44,6 +45,7 @@ class OwncloudSetupWizard : public QObject /** Run the wizard */ static void runWizard(QObject *obj, const char *amember, QWidget *parent = nullptr); static bool bringWizardToFrontIfVisible(); + signals: // overall dialog close signal. void ownCloudWizardDone(int); @@ -80,7 +82,7 @@ private slots: AccountState *applyAccountChanges(); bool checkDowngradeAdvised(QNetworkReply *reply); - OwncloudWizard *_ocWizard; + OwncloudWizard *_ocWizard = nullptr; QString _initLocalFolder; QString _remoteFolder; }; diff --git a/src/gui/tray/SyncStatus.qml b/src/gui/tray/SyncStatus.qml index b482c5586dab7..fa106f96ccf2f 100644 --- a/src/gui/tray/SyncStatus.qml +++ b/src/gui/tray/SyncStatus.qml @@ -119,4 +119,15 @@ RowLayout { enabled: visible onClicked: NC.Systray.createResolveConflictsDialog(activityModel.allConflicts); } + + Button { + Layout.rightMargin: Style.trayHorizontalMargin + + text: qsTr("Open browser") + + visible: NC.UserModel.currentUser.needsToSignTermsOfService + enabled: visible + + onClicked: NC.UserModel.openCurrentAccountServer() + } } diff --git a/src/gui/tray/syncstatussummary.cpp b/src/gui/tray/syncstatussummary.cpp index 7ec4da78cbebb..950ea2eba2cc7 100644 --- a/src/gui/tray/syncstatussummary.cpp +++ b/src/gui/tray/syncstatussummary.cpp @@ -14,6 +14,7 @@ #include "syncstatussummary.h" #include "accountfwd.h" +#include "accountstate.h" #include "folderman.h" #include "navigationpanehelper.h" #include "networkjobs.h" @@ -128,6 +129,14 @@ void SyncStatusSummary::clearFolderErrors() void SyncStatusSummary::setSyncStateForFolder(const Folder *folder) { if (_accountState && !_accountState->isConnected()) { + setSyncing(false); + setTotalFiles(0); + setSyncStatusString(tr("Offline")); + setSyncStatusDetailString(""); + if (_accountState->state() == AccountState::NeedToSignTermsOfService) { + setSyncStatusDetailString(tr("You need to accept the terms of service")); + } + setSyncIcon(Theme::instance()->folderOffline()); return; } diff --git a/src/gui/tray/usermodel.cpp b/src/gui/tray/usermodel.cpp index b24ae990df338..01260f275c1de 100644 --- a/src/gui/tray/usermodel.cpp +++ b/src/gui/tray/usermodel.cpp @@ -1130,6 +1130,11 @@ bool User::isConnected() const return (_account->connectionStatus() == AccountState::ConnectionStatus::Connected); } +bool User::needsToSignTermsOfService() const +{ + return _account->connectionStatus() == AccountState::ConnectionStatus::NeedToSignTermsOfService; +} + bool User::isDesktopNotificationsAllowed() const { diff --git a/src/gui/tray/usermodel.h b/src/gui/tray/usermodel.h index 3f2170ab0748e..2d56f01210595 100644 --- a/src/gui/tray/usermodel.h +++ b/src/gui/tray/usermodel.h @@ -61,6 +61,7 @@ class User : public QObject Q_PROPERTY(QString featuredAppAccessibleName READ featuredAppAccessibleName NOTIFY featuredAppChanged) Q_PROPERTY(QString avatar READ avatarUrl NOTIFY avatarChanged) Q_PROPERTY(bool isConnected READ isConnected NOTIFY accountStateChanged) + Q_PROPERTY(bool needsToSignTermsOfService READ needsToSignTermsOfService NOTIFY accountStateChanged) Q_PROPERTY(UnifiedSearchResultsListModel* unifiedSearchResultsListModel READ getUnifiedSearchResultsListModel CONSTANT) Q_PROPERTY(QVariantList groupFolders READ groupFolders NOTIFY groupFoldersChanged) @@ -71,6 +72,7 @@ class User : public QObject [[nodiscard]] AccountStatePtr accountState() const; [[nodiscard]] bool isConnected() const; + [[nodiscard]] bool needsToSignTermsOfService() const; [[nodiscard]] bool isCurrentUser() const; void setCurrentUser(const bool &isCurrent); [[nodiscard]] Folder *getFolder() const; diff --git a/src/gui/wizard/flow2authcredspage.cpp b/src/gui/wizard/flow2authcredspage.cpp index e03b9a50faba5..8eef42fab0f4b 100644 --- a/src/gui/wizard/flow2authcredspage.cpp +++ b/src/gui/wizard/flow2authcredspage.cpp @@ -104,6 +104,12 @@ void Flow2AuthCredsPage::slotFlow2AuthResult(Flow2Auth::Result r, const QString int Flow2AuthCredsPage::nextId() const { + const auto ocWizard = qobject_cast(wizard()); + Q_ASSERT(ocWizard); + if (ocWizard->needsToAcceptTermsOfService()) { + return WizardCommon::Page_TermsOfService; + } + return WizardCommon::Page_AdvancedSetup; } diff --git a/src/gui/wizard/owncloudadvancedsetuppage.cpp b/src/gui/wizard/owncloudadvancedsetuppage.cpp index bb51e447438d0..e3fb2b8e62b2e 100644 --- a/src/gui/wizard/owncloudadvancedsetuppage.cpp +++ b/src/gui/wizard/owncloudadvancedsetuppage.cpp @@ -176,6 +176,7 @@ void OwncloudAdvancedSetupPage::initializePage() quotaJob->setProperties(QList() << "http://owncloud.org/ns:size"); connect(quotaJob, &PropfindJob::result, this, &OwncloudAdvancedSetupPage::slotQuotaRetrieved); + connect(quotaJob, &PropfindJob::finishedWithError, this, &OwncloudAdvancedSetupPage::slotQuotaRetrievedWithError); quotaJob->start(); @@ -547,6 +548,17 @@ void OwncloudAdvancedSetupPage::slotQuotaRetrieved(const QVariantMap &result) updateStatus(); } +void OwncloudAdvancedSetupPage::slotQuotaRetrievedWithError(QNetworkReply *reply) +{ + if (reply && reply->error() == QNetworkReply::ContentAccessDenied) { + + } + _rSize = -1; + _ui.lSyncEverythingSizeLabel->setText({}); + + updateStatus(); +} + qint64 OwncloudAdvancedSetupPage::availableLocalSpace() const { QString localDir = localFolder(); diff --git a/src/gui/wizard/owncloudadvancedsetuppage.h b/src/gui/wizard/owncloudadvancedsetuppage.h index 2c0f23bb42b37..d01ebe4d953d6 100644 --- a/src/gui/wizard/owncloudadvancedsetuppage.h +++ b/src/gui/wizard/owncloudadvancedsetuppage.h @@ -23,6 +23,7 @@ #include "elidedlabel.h" class QProgressIndicator; +class QNetworkReply; namespace OCC { @@ -63,6 +64,7 @@ private slots: void slotSelectiveSyncClicked(); void slotVirtualFileSyncClicked(); void slotQuotaRetrieved(const QVariantMap &result); + void slotQuotaRetrievedWithError(QNetworkReply *reply); private: void setRadioChecked(QRadioButton *radio); diff --git a/src/gui/wizard/owncloudhttpcredspage.cpp b/src/gui/wizard/owncloudhttpcredspage.cpp index ed514b72a459c..436b46b3da871 100644 --- a/src/gui/wizard/owncloudhttpcredspage.cpp +++ b/src/gui/wizard/owncloudhttpcredspage.cpp @@ -153,6 +153,12 @@ bool OwncloudHttpCredsPage::validatePage() int OwncloudHttpCredsPage::nextId() const { + const auto ocWizard = qobject_cast(wizard()); + Q_ASSERT(ocWizard); + if (ocWizard->needsToAcceptTermsOfService()) { + return WizardCommon::Page_TermsOfService; + } + return WizardCommon::Page_AdvancedSetup; } diff --git a/src/gui/wizard/owncloudwizard.cpp b/src/gui/wizard/owncloudwizard.cpp index 9368b3d162800..3a209312bd0b7 100644 --- a/src/gui/wizard/owncloudwizard.cpp +++ b/src/gui/wizard/owncloudwizard.cpp @@ -27,6 +27,7 @@ #include "wizard/welcomepage.h" #include "wizard/owncloudsetuppage.h" #include "wizard/owncloudhttpcredspage.h" +#include "wizard/termsofservicewizardpage.h" #include "wizard/owncloudadvancedsetuppage.h" #include "wizard/webviewpage.h" #include "wizard/flow2authcredspage.h" @@ -53,6 +54,7 @@ OwncloudWizard::OwncloudWizard(QWidget *parent) , _setupPage(new OwncloudSetupPage(this)) , _httpCredsPage(new OwncloudHttpCredsPage(this)) , _flow2CredsPage(new Flow2AuthCredsPage) + , _termsOfServicePage(new TermsOfServiceWizardPage) , _advancedSetupPage(new OwncloudAdvancedSetupPage(this)) #ifdef WITH_WEBENGINE , _webViewPage(new WebViewPage(this)) @@ -71,6 +73,7 @@ OwncloudWizard::OwncloudWizard(QWidget *parent) setPage(WizardCommon::Page_ServerSetup, _setupPage); setPage(WizardCommon::Page_HttpCreds, _httpCredsPage); setPage(WizardCommon::Page_Flow2AuthCreds, _flow2CredsPage); + setPage(WizardCommon::Page_TermsOfService, _termsOfServicePage); setPage(WizardCommon::Page_AdvancedSetup, _advancedSetupPage); #ifdef WITH_WEBENGINE if (!useFlow2()) { @@ -116,6 +119,7 @@ OwncloudWizard::OwncloudWizard(QWidget *parent) connect(this, &OwncloudWizard::styleChanged, _setupPage, &OwncloudSetupPage::slotStyleChanged); connect(this, &OwncloudWizard::styleChanged, _advancedSetupPage, &OwncloudAdvancedSetupPage::slotStyleChanged); connect(this, &OwncloudWizard::styleChanged, _flow2CredsPage, &Flow2AuthCredsPage::slotStyleChanged); + connect(this, &OwncloudWizard::styleChanged, _termsOfServicePage, &TermsOfServiceWizardPage::styleChanged); customizeStyle(); @@ -216,6 +220,11 @@ bool OwncloudWizard::isConfirmBigFolderChecked() const return _advancedSetupPage->isConfirmBigFolderChecked(); } +bool OwncloudWizard::needsToAcceptTermsOfService() const +{ + return _needsToAcceptTermsOfService; +} + QString OwncloudWizard::ocUrl() const { QString url = field("OCUrl").toString().simplified(); @@ -239,9 +248,12 @@ void OwncloudWizard::setRemoteFolder(const QString &remoteFolder) void OwncloudWizard::successfulStep() { - const int id(currentId()); + const WizardCommon::Pages id{static_cast(currentId())}; switch (id) { + case WizardCommon::Page_Welcome: + break; + case WizardCommon::Page_HttpCreds: _httpCredsPage->setConnected(); break; @@ -258,6 +270,10 @@ void OwncloudWizard::successfulStep() break; #endif // WITH_WEBENGINE + case WizardCommon::Page_TermsOfService: + // nothing to do here + break; + case WizardCommon::Page_AdvancedSetup: _advancedSetupPage->directoriesCreated(); break; @@ -284,6 +300,9 @@ void OwncloudWizard::slotCustomButtonClicked(const int which) } else if (which == WizardButton::CustomButton2) { // Because QWizard doesn't have a way of directly going to a specific page (!!!) restart(); + + // in case the wizard had been cancelled at a page where the need for signing the TOS got checked: + _needsToAcceptTermsOfService = false; } } @@ -328,7 +347,8 @@ void OwncloudWizard::slotCurrentPageChanged(int id) #ifdef WITH_WEBENGINE id == WizardCommon::Page_WebView || #endif // WITH_WEBENGINE - id == WizardCommon::Page_Flow2AuthCreds) { + id == WizardCommon::Page_Flow2AuthCreds || + id == WizardCommon::Page_TermsOfService) { setButtonLayout({ QWizard::BackButton, QWizard::Stretch }); } else if (id == WizardCommon::Page_AdvancedSetup) { setButtonLayout({ QWizard::CustomButton2, QWizard::Stretch, QWizard::CustomButton1, QWizard::FinishButton }); @@ -351,7 +371,13 @@ void OwncloudWizard::slotCurrentPageChanged(int id) void OwncloudWizard::displayError(const QString &msg, bool retryHTTPonly) { - switch (currentId()) { + switch (static_cast(currentId())) { + case WizardCommon::Page_Welcome: + case WizardCommon::Page_Flow2AuthCreds: + case WizardCommon::Page_WebView: + case WizardCommon::Page_TermsOfService: + break; + case WizardCommon::Page_ServerSetup: _setupPage->setErrorString(msg, retryHTTPonly); break; diff --git a/src/gui/wizard/owncloudwizard.h b/src/gui/wizard/owncloudwizard.h index da614b92d7e5c..df8e8c6fe10b8 100644 --- a/src/gui/wizard/owncloudwizard.h +++ b/src/gui/wizard/owncloudwizard.h @@ -33,6 +33,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcWizard) class WelcomePage; class OwncloudSetupPage; class OwncloudHttpCredsPage; +class TermsOfServiceWizardPage; class OwncloudAdvancedSetupPage; class OwncloudWizardResultPage; class AbstractCredentials; @@ -68,6 +69,7 @@ class OwncloudWizard : public QWizard [[nodiscard]] bool useFlow2() const; [[nodiscard]] bool useVirtualFileSync() const; [[nodiscard]] bool isConfirmBigFolderChecked() const; + [[nodiscard]] bool needsToAcceptTermsOfService() const; void displayError(const QString &, bool retryHTTPonly); [[nodiscard]] AbstractCredentials *getCredentials() const; @@ -123,14 +125,15 @@ public slots: [[nodiscard]] QList calculateWizardPageSizes() const; AccountPtr _account; - WelcomePage *_welcomePage; - OwncloudSetupPage *_setupPage; - OwncloudHttpCredsPage *_httpCredsPage; - Flow2AuthCredsPage *_flow2CredsPage; - OwncloudAdvancedSetupPage *_advancedSetupPage; + WelcomePage *_welcomePage = nullptr; + OwncloudSetupPage *_setupPage = nullptr; + OwncloudHttpCredsPage *_httpCredsPage = nullptr; + Flow2AuthCredsPage *_flow2CredsPage = nullptr; + TermsOfServiceWizardPage *_termsOfServicePage = nullptr; + OwncloudAdvancedSetupPage *_advancedSetupPage = nullptr; OwncloudWizardResultPage *_resultPage = nullptr; AbstractCredentialsWizardPage *_credentialsPage = nullptr; - WebViewPage *_webViewPage = nullptr; + WebViewPage*_webViewPage = nullptr; QStringList _setupLog; @@ -138,6 +141,8 @@ public slots: bool _useFlow2 = ConfigFile().forceLoginV2(); + bool _needsToAcceptTermsOfService = false; + friend class OwncloudSetupWizard; }; diff --git a/src/gui/wizard/owncloudwizardcommon.h b/src/gui/wizard/owncloudwizardcommon.h index f5d7ba530f436..7c771492052da 100644 --- a/src/gui/wizard/owncloudwizardcommon.h +++ b/src/gui/wizard/owncloudwizardcommon.h @@ -50,6 +50,7 @@ namespace WizardCommon { #ifdef WITH_WEBENGINE Page_WebView, #endif // WITH_WEBENGINE + Page_TermsOfService, Page_AdvancedSetup, }; diff --git a/src/gui/wizard/termsofservicecheckwidget.cpp b/src/gui/wizard/termsofservicecheckwidget.cpp new file mode 100644 index 0000000000000..a5f625bccbf1b --- /dev/null +++ b/src/gui/wizard/termsofservicecheckwidget.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (C) by Jyrki Gadinger + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "termsofservicecheckwidget.h" + +#include "wizard/owncloudwizardcommon.h" +#include "theme.h" +#include "configfile.h" + +#include "QProgressIndicator.h" + +#include +#include + +namespace OCC { + +Q_LOGGING_CATEGORY(lcTosCheckWidget, "nextcloud.gui.wizard.termsofservicecheckwidget", QtInfoMsg) + + +TermsOfServiceCheckWidget::TermsOfServiceCheckWidget(QWidget *parent) + : QWidget(parent) + , _progressIndicator(new QProgressIndicator(this)) +{ + _pollTimer.setInterval(1000); + QObject::connect(&_pollTimer, &QTimer::timeout, this, &TermsOfServiceCheckWidget::slotPollTimerTimeout); + + _ui.setupUi(this); + + connect(_ui.openLinkButton, &QPushButton::clicked, this, &TermsOfServiceCheckWidget::slotOpenBrowser); + connect(_ui.copyLinkButton, &QPushButton::clicked, this, &TermsOfServiceCheckWidget::slotCopyLinkToClipboard); + + auto sizePolicy = _progressIndicator->sizePolicy(); + sizePolicy.setRetainSizeWhenHidden(true); + _progressIndicator->setSizePolicy(sizePolicy); + + _ui.progressLayout->addWidget(_progressIndicator); + stopSpinner(false); + + customizeStyle(); +} + +TermsOfServiceCheckWidget::~TermsOfServiceCheckWidget() { +} + +void TermsOfServiceCheckWidget::start() +{ + ConfigFile cfg; + std::chrono::milliseconds polltime = cfg.remotePollInterval(); + qCInfo(lcTosCheckWidget) << "setting remote poll timer interval to" << polltime.count() << "msec"; + _secondsInterval = (polltime.count() / 1000); + _secondsLeft = _secondsInterval; + + _pollTimer.start(); + // open browser when the wizard page is shown + slotOpenBrowser(); +} + +void TermsOfServiceCheckWidget::setUrl(const QUrl &url) +{ + _url = url; +} + +void TermsOfServiceCheckWidget::termsNotAcceptedYet() +{ + _secondsLeft = _secondsInterval; + _isBusy = false; + statusChanged(Status::statusPollCountdown); +} + +void TermsOfServiceCheckWidget::setLogo() +{ + const auto backgroundColor = palette().window().color(); + const auto logoIconFileName = Theme::instance()->isBranded() ? Theme::hidpiFileName("external.png", backgroundColor) + : Theme::hidpiFileName(":/client/theme/colored/external.png"); + _ui.logoLabel->setPixmap(logoIconFileName); +} + +void TermsOfServiceCheckWidget::slotStyleChanged() +{ + customizeStyle(); +} + +void TermsOfServiceCheckWidget::slotPollTimerTimeout() +{ + if (_isBusy) { + return; + } + + _isBusy = true; + + _secondsLeft--; + if (_secondsLeft > 0) { + statusChanged(Status::statusPollCountdown); + _isBusy = false; + return; + } + + statusChanged(Status::statusPollNow); + Q_EMIT pollNow(); +} + +void TermsOfServiceCheckWidget::slotOpenBrowser() +{ + QDesktopServices::openUrl(_url); +} + +void TermsOfServiceCheckWidget::slotCopyLinkToClipboard() +{ + statusChanged(Status::statusCopyLinkToClipboard); + QApplication::clipboard()->setText(_url.toString(QUrl::FullyEncoded)); +} + +void TermsOfServiceCheckWidget::statusChanged(Status status) +{ + switch (status) + { + case statusPollCountdown: + if (_statusUpdateSkipCount > 0) { + _statusUpdateSkipCount--; + return; + } + + _ui.statusLabel->setText(tr("Waiting for terms to be accepted") + QStringLiteral("… (%1)").arg(_secondsLeft)); + stopSpinner(true); + return; + + case statusPollNow: + _statusUpdateSkipCount = 0; + _ui.statusLabel->setText(tr("Polling") + QStringLiteral("…")); + startSpinner(); + return; + + case statusCopyLinkToClipboard: + _statusUpdateSkipCount = 3; + _ui.statusLabel->setText(tr("Link copied to clipboard.")); + stopSpinner(true); + return; + } +} + +void TermsOfServiceCheckWidget::startSpinner() +{ + _ui.progressLayout->setEnabled(true); + _ui.statusLabel->setVisible(true); + _progressIndicator->setVisible(true); + _progressIndicator->startAnimation(); + + _ui.openLinkButton->setEnabled(false); + _ui.copyLinkButton->setEnabled(false); +} + +void TermsOfServiceCheckWidget::stopSpinner(bool showStatusLabel) +{ + _ui.progressLayout->setEnabled(false); + _ui.statusLabel->setVisible(showStatusLabel); + _progressIndicator->setVisible(false); + _progressIndicator->stopAnimation(); + + _ui.openLinkButton->setEnabled(_statusUpdateSkipCount == 0); + _ui.copyLinkButton->setEnabled(_statusUpdateSkipCount == 0); +} + +void TermsOfServiceCheckWidget::customizeStyle() +{ + setLogo(); + + if (_progressIndicator) { + const auto isDarkBackground = Theme::isDarkColor(palette().window().color()); + if (isDarkBackground) { + _progressIndicator->setColor(Qt::white); + } else { + _progressIndicator->setColor(Qt::black); + } + } + + _ui.openLinkButton->setText(tr("Open Browser")); + + _ui.copyLinkButton->setText(tr("Copy Link")); + + WizardCommon::customizeHintLabel(_ui.statusLabel); +} + +} // namespace OCC diff --git a/src/gui/wizard/termsofservicecheckwidget.h b/src/gui/wizard/termsofservicecheckwidget.h new file mode 100644 index 0000000000000..d718e4cf5bd88 --- /dev/null +++ b/src/gui/wizard/termsofservicecheckwidget.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) by Jyrki Gadinger + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#pragma once + +#include +#include + +#include "ui_termsofservicecheckwidget.h" + +class QProgressIndicator; + +namespace OCC { + +class TermsOfServiceCheckWidget : public QWidget +{ + Q_OBJECT +public: + enum Status { + statusPollCountdown = 1, + statusPollNow, + statusCopyLinkToClipboard, + }; + + TermsOfServiceCheckWidget(QWidget *parent = nullptr); + ~TermsOfServiceCheckWidget() override; + + void start(); + void setUrl(const QUrl &url); + void termsNotAcceptedYet(); + +public Q_SLOTS: + void slotStyleChanged(); + +Q_SIGNALS: + void pollNow(); + +private Q_SLOTS: + void slotPollTimerTimeout(); + void slotOpenBrowser(); + void slotCopyLinkToClipboard(); + +private: + Ui_TermsOfServiceCheckWidget _ui{}; + QTimer _pollTimer; + QProgressIndicator *_progressIndicator; + int _statusUpdateSkipCount = 0; + qint64 _secondsLeft = 0LL; + qint64 _secondsInterval = 0LL; + bool _isBusy = false; + QUrl _url; + + void statusChanged(Status status); + void startSpinner(); + void stopSpinner(bool showStatusLabel); + void customizeStyle(); + void setLogo(); + +}; + +} // namespace OCC diff --git a/src/gui/wizard/termsofservicecheckwidget.ui b/src/gui/wizard/termsofservicecheckwidget.ui new file mode 100644 index 0000000000000..be52d44b11ddc --- /dev/null +++ b/src/gui/wizard/termsofservicecheckwidget.ui @@ -0,0 +1,248 @@ + + + TermsOfServiceCheckWidget + + + + 0 + 0 + 597 + 387 + + + + + 0 + 0 + + + + + 500 + 280 + + + + Terms of Service + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + Logo + + + Qt::AlignmentFlag::AlignCenter + + + + + + + Qt::Orientation::Vertical + + + + 20 + 32 + + + + + + + + + 12 + true + + + + Switch to your browser to accept the terms of service + + + Qt::AlignmentFlag::AlignCenter + + + true + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::Expanding + + + + 20 + 8 + + + + + + + + Status + + + Qt::AlignmentFlag::AlignCenter + + + 0 + + + + + + + Qt::Orientation::Vertical + + + + 20 + 32 + + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 64 + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 0 + + + + + 9 + + + + copyLinkButton + + + true + + + false + + + + + + + + 0 + 0 + + + + + 9 + + + + openLinkButton + + + true + + + false + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::Expanding + + + + 20 + 60 + + + + + + + + + diff --git a/src/gui/wizard/termsofservicewizardpage.cpp b/src/gui/wizard/termsofservicewizardpage.cpp new file mode 100644 index 0000000000000..f5b577628150c --- /dev/null +++ b/src/gui/wizard/termsofservicewizardpage.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) by Matthieu Gallien + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "termsofservicewizardpage.h" + +#include "account.h" +#include "owncloudsetupwizard.h" +#include "wizard/owncloudwizard.h" +#include "wizard/owncloudwizardcommon.h" +#include "wizard/termsofservicecheckwidget.h" +#include "connectionvalidator.h" + +#include +#include + +namespace OCC { + +OCC::TermsOfServiceWizardPage::TermsOfServiceWizardPage() + : QWizardPage() +{ + _layout = new QVBoxLayout(this); + + _termsOfServiceCheckWidget = new TermsOfServiceCheckWidget; + _layout->addWidget(_termsOfServiceCheckWidget); + + connect(this, &TermsOfServiceWizardPage::styleChanged, _termsOfServiceCheckWidget, &TermsOfServiceCheckWidget::slotStyleChanged); + connect(_termsOfServiceCheckWidget, &TermsOfServiceCheckWidget::pollNow, this, &TermsOfServiceWizardPage::slotPollNow); +} + +void OCC::TermsOfServiceWizardPage::initializePage() +{ + _ocWizard = qobject_cast(wizard()); + Q_ASSERT(_ocWizard); + + _termsOfServiceChecker = new TermsOfServiceChecker{_ocWizard->account(), this}; + connect(_termsOfServiceChecker, &TermsOfServiceChecker::done, this, &TermsOfServiceWizardPage::termsOfServiceChecked); + + _termsOfServiceCheckWidget->setUrl(_ocWizard->account()->url()); + _termsOfServiceCheckWidget->slotStyleChanged(); + _termsOfServiceCheckWidget->start(); + + connect(_ocWizard, &OwncloudWizard::onActivate, this, &TermsOfServiceWizardPage::slotPollNow); +} + +void OCC::TermsOfServiceWizardPage::cleanupPage() +{ + disconnect(_ocWizard, &OwncloudWizard::onActivate, this, &TermsOfServiceWizardPage::slotPollNow); + + _termsOfServiceChecker->deleteLater(); + _termsOfServiceChecker = nullptr; +} + +int OCC::TermsOfServiceWizardPage::nextId() const +{ + return WizardCommon::Page_AdvancedSetup; +} + +bool OCC::TermsOfServiceWizardPage::isComplete() const +{ + return false; +} + +void TermsOfServiceWizardPage::slotPollNow() +{ + if (!_termsOfServiceChecker) { + return; + } + + _termsOfServiceChecker->start(); +} + +void TermsOfServiceWizardPage::termsOfServiceChecked() +{ + if (_termsOfServiceChecker && _termsOfServiceChecker->needToSign()) { + _termsOfServiceCheckWidget->termsNotAcceptedYet(); + return; + } + _ocWizard->successfulStep(); +} + +} + diff --git a/src/gui/wizard/termsofservicewizardpage.h b/src/gui/wizard/termsofservicewizardpage.h new file mode 100644 index 0000000000000..2312d98668116 --- /dev/null +++ b/src/gui/wizard/termsofservicewizardpage.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) by Matthieu Gallien + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#ifndef TERMSOFSERVICEWIZARDPAGE_H +#define TERMSOFSERVICEWIZARDPAGE_H + +#include + +class QVBoxLayout; + +namespace OCC { + +class OwncloudWizard; +class TermsOfServiceChecker; +class TermsOfServiceCheckWidget; + +class TermsOfServiceWizardPage : public QWizardPage +{ + Q_OBJECT +public: + TermsOfServiceWizardPage(); + + void initializePage() override; + void cleanupPage() override; + [[nodiscard]] int nextId() const override; + [[nodiscard]] bool isComplete() const override; + +public Q_SLOTS: + void slotPollNow(); + +Q_SIGNALS: + void pollNow(); + void styleChanged(); + +private: + QVBoxLayout *_layout = nullptr; + OwncloudWizard *_ocWizard = nullptr; + TermsOfServiceChecker *_termsOfServiceChecker = nullptr; + TermsOfServiceCheckWidget *_termsOfServiceCheckWidget = nullptr; + +private Q_SLOTS: + void termsOfServiceChecked(); +}; + +} // namespace OCC + +#endif // TERMSOFSERVICEWIZARDPAGE_H diff --git a/src/gui/wizard/webviewpage.cpp b/src/gui/wizard/webviewpage.cpp index 18acab0c44e7f..0d47cb23ee42a 100644 --- a/src/gui/wizard/webviewpage.cpp +++ b/src/gui/wizard/webviewpage.cpp @@ -92,6 +92,12 @@ void WebViewPage::cleanupPage() } int WebViewPage::nextId() const { + const auto ocWizard = qobject_cast(wizard()); + Q_ASSERT(ocWizard); + if (ocWizard->needsToAcceptTermsOfService()) { + return WizardCommon::Page_TermsOfService; + } + return WizardCommon::Page_AdvancedSetup; } diff --git a/src/libsync/owncloudpropagator_p.h b/src/libsync/owncloudpropagator_p.h index beb150648db87..95e383493ce67 100644 --- a/src/libsync/owncloudpropagator_p.h +++ b/src/libsync/owncloudpropagator_p.h @@ -69,8 +69,8 @@ inline QPair getExceptionFromReply(QNetworkReply * const return {}; } const auto httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - // only for BadRequest and UnsupportedMediaType - if (httpStatusCode != 400 && httpStatusCode != 415) { + // only for BadRequest, Forbidden, and UnsupportedMediaType + if (!(httpStatusCode == 400 || httpStatusCode == 403 || httpStatusCode == 415)) { return {}; }