introduce new options to customize featurelock dialog

- adds two new options
1. localize the dialog
2. change upsell image shown in dialog using proxy handler
- provides all the settings using dynamic configuration

Signed-off-by: Rash419 <rashesh.padia@collabora.com>
Change-Id: I7e21c1b31c806c88bf54f891de40f02fa342168f
pull/5049/head
Rash419 2022-07-05 17:33:14 +05:30 committed by Rashesh Padia
parent b00961f06a
commit 9bb0b6fabd
13 changed files with 269 additions and 36 deletions

View File

@ -230,7 +230,7 @@ kbd,
}
.vex.vex-theme-plain .vex-content.vex-locking .container .item.illustration {
flex: 1.3;
background: transparent url('images/lock-illustration.svg') no-repeat left 4px /contain;
background: transparent no-repeat left 4px /contain;
height: 288px;
}

View File

@ -30,7 +30,7 @@ L.Map.include({
this.Locking.calcHighlights = _(lockInfo['CalcHighlights']);
this.Locking.impressHighlights = _(lockInfo['ImpressHighlights']);
this.Locking.drawHighlights = _(lockInfo['DrawHighlights']);
this.Locking.unlockImageUrlPath = lockInfo['UnlockImageUrlPath'];
},
// We mark the element disabled for the feature locking
@ -68,7 +68,7 @@ L.Map.include({
vex.dialog.confirm({
unsafeMessage: [
'<div class="container">',
'<div class="item illustration"></div>',
'<div id="unlock-image" class="item illustration"></div>',
'<div class="item">',
'<h1>' + this.Locking.unlockTitle + '</h1>',
'<p>' + this.Locking.unlockDescription + '<p>',
@ -92,6 +92,13 @@ L.Map.include({
$.extend({}, vex.dialog.buttons.NO, { text: _('Cancel') })
]
});
var unlockImage = L.DomUtil.get('unlock-image');
if (this.Locking.unlockImageUrlPath) {
unlockImage.style.backgroundImage = 'url(remote' + this.Locking.unlockImageUrlPath + ')';
} else {
unlockImage.style.backgroundImage = 'url(images/lock-illustration.svg)';
}
},
isLockedItem: function(item) {

View File

@ -21,6 +21,7 @@ std::string LockManager::LockedCommandListString;
Util::RegexListMatcher LockManager::readOnlyWopiHosts;
Util::RegexListMatcher LockManager::disabledCommandWopiHosts;
bool LockManager::lockHostEnabled = false;
std::string LockManager::translationPath = std::string();
LockManager::LockManager() {}
@ -116,6 +117,26 @@ bool LockManager::hostExist(const std::string& host)
return LockManager::lockHostEnabled && LockManager::readOnlyWopiHosts.matchExist(host);
}
void LockManager::setTranslationPath(const std::string& lockedDialogLang)
{
for (size_t i = 0;; ++i)
{
const std::string path =
"feature_lock.translations.language[" + std::to_string(i) + "][@name]";
if (!config::has(path))
{
return;
}
if (config::getString(path, "") == lockedDialogLang)
{
LockManager::translationPath =
"feature_lock.translations.language[" + std::to_string(i) + ']';
return;
}
}
}
bool RestrictionManager::_isRestrictedUser = false;
std::unordered_set<std::string> RestrictionManager::RestrictedCommandList;
std::string RestrictionManager::RestrictedCommandListString;

View File

@ -11,6 +11,9 @@
#include <unordered_set>
#include "ConfigUtil.hpp"
#include <Poco/Util/LayeredConfiguration.h>
#include <Poco/URI.h>
#include <Poco/Exception.h>
#include <Log.hpp>
namespace CommandControl
{
@ -20,6 +23,7 @@ class LockManager
static bool _isLockedUser;
static bool _isHostReadOnly;
static std::string LockedCommandListString;
static std::string translationPath;
static void generateLockedCommandList();
@ -49,32 +53,68 @@ public:
static void setLockedUser(bool isLocked) { _isLockedUser = isLocked; }
static void setHostReadOnly(bool isReadOnly) { _isHostReadOnly = isReadOnly; }
static void setTranslationPath(const std::string& lockedDialogLang);
static std::string getUnlockTitle()
{
if (config::has(translationPath + ".unlock_title"))
return config::getString(translationPath + ".unlock_title", "");
return config::getString("feature_lock.unlock_title", "");
}
static std::string getUnlockLink() { return config::getString("feature_lock.unlock_link", ""); }
static std::string getUnlockLink()
{
return config::getString("feature_lock.unlock_link", "");
}
static std::string getUnlockDescription()
{
if (config::has(translationPath + ".unlock_description"))
return config::getString(translationPath + ".unlock_description", "");
return config::getString("feature_lock.unlock_description", "");
}
static std::string getWriterHighlights()
{
if (config::has(translationPath + ".writer_unlock_highlights"))
return config::getString(translationPath + ".writer_unlock_highlights", "");
return config::getString("feature_lock.writer_unlock_highlights", "");
}
static std::string getCalcHighlights()
{
if (config::has(translationPath + ".calc_unlock_highlights"))
return config::getString(translationPath + ".calc_unlock_highlights", "");
return config::getString("feature_lock.calc_unlock_highlights", "");
}
static std::string getImpressHighlights()
{
if (config::has(translationPath + ".impress_unlock_highlights"))
return config::getString(translationPath + ".impress_unlock_highlights", "");
return config::getString("feature_lock.impress_unlock_highlights", "");
}
static std::string getDrawHighlights()
{
if (config::has(translationPath + ".draw_unlock_highlights"))
return config::getString(translationPath + ".draw_unlock_highlights", "");
return config::getString("feature_lock.draw_unlock_highlights", "");
}
static const Poco::URI getUnlockImageUri()
{
const std::string unlockImageUrl = config::getString("feature_lock.unlock_image", "");
if (!unlockImageUrl.empty())
{
try
{
const Poco::URI unlockImageUri(unlockImageUrl);
return unlockImageUri;
}
catch (Poco::SyntaxException& exc)
{
LOG_ERR("Parsing of unlock_image url failed with " << exc.what());
}
}
return Poco::URI();
}
static void resetTransalatioPath()
{
translationPath = std::string();
}
};
class RestrictionManager

View File

@ -52,6 +52,12 @@ bool getBool(const std::string& key, const bool def)
return Config ? Config->getBool(key, def) : def;
}
bool has(const std::string& key)
{
assert(Config && "Config is not initialized.");
return Config ? Config->has(key) : false;
}
bool isSslEnabled()
{
#if ENABLE_SSL

View File

@ -36,6 +36,9 @@ bool isInitialized();
/// Returns the value of an entry as string or @def if it is not found.
std::string getString(const std::string& key, const std::string& def);
/// Returns true if and only if the property with the given key exists.
bool has(const std::string& key);
/// Returns the value of an entry as string or @def if it is not found.
bool getBool(const std::string& key, const bool def);

View File

@ -1437,10 +1437,25 @@ if test "$enable_feature_lock" = "yes"; then
<unlock_title desc=\"Title to show user when prompting the unlock popup\" default=\"$UNLOCK_TITLE\">$UNLOCK_TITLE</unlock_title>
<unlock_link desc=\"Link from where user can unlock the features\" default=\"$UNLOCK_LINK\">$UNLOCK_LINK</unlock_link>
<unlock_description desc=\"Description about unlocking features\" default=\"$UNLOCK_DESCRIPTION\">$UNLOCK_DESCRIPTION</unlock_description>
<unlock_image desc=\"URL of image shown when unlock dialog get displayed\" default=\"\"></unlock_image><!-- URL structure - https://<hostname>/static/<image_path> -->
<writer_unlock_highlights desc=\"Short note about Writer benefits of unlocking\" default=\"$WRITER_UNLOCK_HIGHLIGHTS\">$WRITER_UNLOCK_HIGHLIGHTS</writer_unlock_highlights>
<calc_unlock_highlights desc=\"Short note about Calc benefits of unlocking\" default=\"$CALC_UNLOCK_HIGHLIGHTS\">$CALC_UNLOCK_HIGHLIGHTS</calc_unlock_highlights>
<impress_unlock_highlights desc=\"Short note about Impress benefits of unlocking\" default=\"$IMPRESS_UNLOCK_HIGHLIGHTS\">$IMPRESS_UNLOCK_HIGHLIGHTS</impress_unlock_highlights>
<draw_unlock_highlights desc=\"Short note about Draw benefits of unlocking\" default=\"$DRAW_UNLOCK_HIGHLIGHTS\">$DRAW_UNLOCK_HIGHLIGHTS</draw_unlock_highlights>
<translations>
<!--
Example to add german language for feature lock dialog
<language name=\"de\">
<unlock_title desc=\"Title to show user when prompting the unlock popup\" default=\"Entfesseln Sie Ihr Potenzial\">Entfesseln Sie Ihr Potenzial</unlock_title>
<unlock_description desc=\"Description about unlocking features\" default=\"Gehen Sie zur Detailseite und entdecken Sie alle Funktionen:\">Gehen Sie zur Detailseite und entdecken Sie alle Funktionen:</unlock_description>
<writer_unlock_highlights desc=\"Short note about Writer benefits of unlocking\" default=\"Überprüfen und schreiben Sie mit Leichtigkeit\">Überprüfen und schreiben Sie mit Leichtigkeit</writer_unlock_highlights>
<calc_unlock_highlights desc=\"Short note about Calc benefits of unlocking\" default=\"Machen Sie sich ein besseres Bild von Ihren Daten\">Machen Sie sich ein besseres Bild von Ihren Daten</calc_unlock_highlights>
<impress_unlock_highlights desc=\"Short note about Impress benefits of unlocking\" default=\"Bringen Sie Ihre nächste Präsentation auf den Punkt\">Bringen Sie Ihre nächste Präsentation auf den Punkt</impress_unlock_highlights>
<draw_unlock_highlights desc=\"Short note about Draw benefits of unlocking\" default=\"Zeichne und organisiere dich\">Zeichne und organisiere dich</draw_unlock_highlights>
</language>
-->
</translations>
</feature_lock>"
fi

View File

@ -1182,6 +1182,8 @@ public:
#ifdef ENABLE_FEATURE_LOCK
fetchLockedHostPatterns(newAppConfig, remoteJson);
fetchLockedTranslations(newAppConfig, remoteJson);
fetchUnlockImageUrl(newAppConfig, remoteJson);
#endif
fetchRemoteFontConfig(newAppConfig, remoteJson);
@ -1427,6 +1429,104 @@ public:
}
}
void fetchLockedTranslations(std::map<std::string, std::string>& newAppConfig,
Poco::JSON::Object::Ptr remoteJson)
{
Poco::JSON::Array::Ptr lockedTranslations;
try
{
lockedTranslations = remoteJson->getObject("feature_locking")->getArray("translations");
std::size_t i;
for (i = 0; i < lockedTranslations->size(); i++)
{
Poco::JSON::Object::Ptr translation = lockedTranslations->getObject(i);
std::string language;
//default values if the one of the entry is missing in json
std::string title = conf.getString("feature_lock.unlock_title", "");
std::string description = conf.getString("feature_lock.unlock_description", "");
std::string writerHighlights =
conf.getString("feature_lock.writer_unlock_highlights", "");
std::string impressHighlights =
conf.getString("feature_lock.impress_unlock_highlights", "");
std::string calcHighlights =
conf.getString("feature_lock.calc_unlock_highlights", "");
std::string drawHighlights =
conf.getString("feature_lock.draw_unlock_highlights", "");
JsonUtil::findJSONValue(translation, "language", language);
JsonUtil::findJSONValue(translation, "unlock_title", title);
JsonUtil::findJSONValue(translation, "unlock_description", description);
JsonUtil::findJSONValue(translation, "writer_unlock_highlights", writerHighlights);
JsonUtil::findJSONValue(translation, "calc_unlock_highlights", calcHighlights);
JsonUtil::findJSONValue(translation, "impress_unlock_highlights",
impressHighlights);
JsonUtil::findJSONValue(translation, "draw_unlock_highlights", drawHighlights);
const std::string path =
"feature_lock.translations.language[" + std::to_string(i) + ']';
newAppConfig.insert(std::make_pair(path + "[@name]", language));
newAppConfig.insert(std::make_pair(path + ".unlock_title", title));
newAppConfig.insert(std::make_pair(path + ".unlock_description", description));
newAppConfig.insert(
std::make_pair(path + ".writer_unlock_highlights", writerHighlights));
newAppConfig.insert(
std::make_pair(path + ".calc_unlock_highlights", calcHighlights));
newAppConfig.insert(
std::make_pair(path + ".impress_unlock_highlights", impressHighlights));
newAppConfig.insert(
std::make_pair(path + ".draw_unlock_highlights", drawHighlights));
}
//if number of translations defined in configuration are greater than number of translation
//fetched from json, overwrite the remaining translations from config file to empty strings
for (;; i++)
{
const std::string path =
"feature_lock.translations.language[" + std::to_string(i) + "][@name]";
if (!conf.has(path))
{
break;
}
newAppConfig.insert(std::make_pair(path, ""));
}
}
catch (const Poco::NullPointerException&)
{
LOG_INF("Not overwriting any translations because feature_locking->translations array "
"does not exist");
return;
}
catch (const std::exception& exc)
{
LOG_ERR("Failed to fetch remote_font_config, please check JSON format: " << exc.what());
}
}
void fetchUnlockImageUrl(std::map<std::string, std::string>& newAppConfig,
Poco::JSON::Object::Ptr remoteJson)
{
try
{
Poco::JSON::Object::Ptr featureLocking = remoteJson->getObject("feature_locking");
std::string unlockImage;
if (JsonUtil::findJSONValue(featureLocking, "unlock_image", unlockImage))
{
newAppConfig.insert(std::make_pair("feature_lock.unlock_image", unlockImage));
}
}
catch (const Poco::NullPointerException&)
{
LOG_INF("Not overwriting the unlock_image URL because the unlock_image entry does not "
"exist");
}
catch (const std::exception& exc)
{
LOG_ERR("Failed to fetch unlock_image, please check JSON format: " << exc.what());
}
}
//sets property to false if it is missing from JSON
//and returns std::string
std::string booleanToString(Poco::Dynamic::Var& booleanFlag)
@ -3475,7 +3575,26 @@ private:
const auto pos = uri.find(ProxyRemoteStatic);
if (pos != std::string::npos)
{
ProxyRequestHandler::handleRequest(uri.substr(pos + ProxyRemoteLen), socket);
if (Util::endsWith(uri, "lokit-extra-img.svg"))
{
ProxyRequestHandler::handleRequest(
uri.substr(pos + ProxyRemoteLen), socket,
ProxyRequestHandler::getProxyRatingServer());
}
#ifdef ENABLE_FEATURE_LOCK
else
{
const Poco::URI unlockImageUri =
CommandControl::LockManager::getUnlockImageUri();
if (!unlockImageUri.empty())
{
const std::string& serverUri =
unlockImageUri.getScheme() + "://" + unlockImageUri.getAuthority();
ProxyRequestHandler::handleRequest(uri.substr(pos + ProxyRemoteLen),
socket, serverUri);
}
}
#endif
}
else
{

View File

@ -18,6 +18,7 @@
#include <Poco/Net/HTTPResponse.h>
#include <Poco/StreamCopier.h>
#include <Poco/URI.h>
#include <Poco/JSON/Object.h>
#include "DocumentBroker.hpp"
#include "COOLWSD.hpp"
@ -28,6 +29,8 @@
#include <common/Session.hpp>
#include <common/TraceEvent.hpp>
#include <common/Util.hpp>
#include <common/CommandControl.hpp>
#if !MOBILEAPP
#include <net/HttpHelper.hpp>
#endif
@ -1139,7 +1142,9 @@ bool ClientSession::loadDocument(const char* /*buffer*/, int /*length*/,
{
oss << " batch=" << getBatchMode();
}
#ifdef ENABLE_FEATURE_LOCK
sendLockedInfo();
#endif
return forwardToChild(oss.str(), docBroker);
}
catch (const Poco::SyntaxException&)
@ -1150,6 +1155,39 @@ bool ClientSession::loadDocument(const char* /*buffer*/, int /*length*/,
return false;
}
#ifdef ENABLE_FEATURE_LOCK
void ClientSession::sendLockedInfo()
{
Poco::JSON::Object::Ptr lockInfo = new Poco::JSON::Object();
CommandControl::LockManager::setTranslationPath(getLang());
lockInfo->set("IsLockedUser", CommandControl::LockManager::isLockedUser());
lockInfo->set("IsLockReadOnly", CommandControl::LockManager::isLockReadOnly());
// Poco:Dynamic:Var does not support std::unordred_set so converted to std::vector
std::vector<std::string> lockedCommandList(
CommandControl::LockManager::getLockedCommandList().begin(),
CommandControl::LockManager::getLockedCommandList().end());
lockInfo->set("LockedCommandList", lockedCommandList);
lockInfo->set("UnlockTitle", CommandControl::LockManager::getUnlockTitle());
lockInfo->set("UnlockLink", CommandControl::LockManager::getUnlockLink());
lockInfo->set("UnlockDescription", CommandControl::LockManager::getUnlockDescription());
lockInfo->set("WriterHighlights", CommandControl::LockManager::getWriterHighlights());
lockInfo->set("CalcHighlights", CommandControl::LockManager::getCalcHighlights());
lockInfo->set("ImpressHighlights", CommandControl::LockManager::getImpressHighlights());
lockInfo->set("DrawHighlights", CommandControl::LockManager::getDrawHighlights());
const Poco::URI unlockImageUri = CommandControl::LockManager::getUnlockImageUri();
if (!unlockImageUri.empty())
lockInfo->set("UnlockImageUrlPath", unlockImageUri.getPath());
CommandControl::LockManager::resetTransalatioPath();
std::ostringstream ossLockInfo;
lockInfo->stringify(ossLockInfo);
const std::string lockInfoString = ossLockInfo.str();
LOG_TRC("Sending feature locking info to client: " << lockInfoString);
sendTextFrame("featurelock: " + lockInfoString);
}
#endif
bool ClientSession::getCommandValues(const char *buffer, int length, const StringVector& tokens,
const std::shared_ptr<DocumentBroker>& docBroker)
{

View File

@ -233,6 +233,11 @@ public:
/// Generate an access token for this session via proxy protocol.
const std::string &getOrCreateProxyAccess();
#ifdef ENABLE_FEATURE_LOCK
void sendLockedInfo();
#endif
private:
std::shared_ptr<ClientSession> client_from_this()
{

View File

@ -836,31 +836,6 @@ bool DocumentBroker::download(const std::shared_ptr<ClientSession>& session, con
}
}
#ifdef ENABLE_FEATURE_LOCK
Object::Ptr lockInfo = new Object();
lockInfo->set("IsLockedUser", CommandControl::LockManager::isLockedUser());
lockInfo->set("IsLockReadOnly", CommandControl::LockManager::isLockReadOnly());
// Poco:Dynamic:Var does not support std::unordred_set so converted to std::vector
std::vector<std::string> lockedCommandList(
CommandControl::LockManager::getLockedCommandList().begin(),
CommandControl::LockManager::getLockedCommandList().end());
lockInfo->set("LockedCommandList", lockedCommandList);
lockInfo->set("UnlockTitle", CommandControl::LockManager::getUnlockTitle());
lockInfo->set("UnlockLink", CommandControl::LockManager::getUnlockLink());
lockInfo->set("UnlockDescription", CommandControl::LockManager::getUnlockDescription());
lockInfo->set("WriterHighlights", CommandControl::LockManager::getWriterHighlights());
lockInfo->set("CalcHighlights", CommandControl::LockManager::getCalcHighlights());
lockInfo->set("ImpressHighlights", CommandControl::LockManager::getImpressHighlights());
lockInfo->set("DrawHighlights", CommandControl::LockManager::getDrawHighlights());
std::ostringstream ossLockInfo;
lockInfo->stringify(ossLockInfo);
const std::string lockInfoString = ossLockInfo.str();
LOG_TRC("Sending feature locking info to client: " << lockInfoString);
session->sendMessage("featurelock: " + lockInfoString);
#endif
#ifdef ENABLE_FEATURE_RESTRICTION
Object::Ptr restrictionInfo = new Object();
restrictionInfo->set("IsRestrictedUser", CommandControl::RestrictionManager::isRestrictedUser());

View File

@ -18,9 +18,11 @@ std::unordered_map<std::string, std::shared_ptr<http::Response>> ProxyRequestHan
std::chrono::system_clock::time_point ProxyRequestHandler::MaxAge;
void ProxyRequestHandler::handleRequest(const std::string& relPath,
const std::shared_ptr<StreamSocket>& socket)
const std::shared_ptr<StreamSocket>& socket,
const std::string& serverUri)
{
Poco::URI uriProxy(ProxyServer);
Poco::URI uriProxy(serverUri);
constexpr const auto zero = std::chrono::system_clock::time_point();
const auto timeNow = std::chrono::system_clock::now();

View File

@ -14,11 +14,13 @@ class ProxyRequestHandler
{
public:
static void handleRequest(const std::string& relPath,
const std::shared_ptr<StreamSocket>& socket);
const std::shared_ptr<StreamSocket>& socket,
const std::string& serverUri);
static std::string getProxyRatingServer() { return ProxyRatingServer; }
private:
static std::chrono::system_clock::time_point MaxAge;
static constexpr auto ProxyServer = "https://rating.collaboraonline.com";
static constexpr auto ProxyRatingServer = "https://rating.collaboraonline.com";
static std::unordered_map<std::string, std::shared_ptr<http::Response>> CacheFileHash;
};