2008 lines
68 KiB
PHP
2008 lines
68 KiB
PHP
<?php
|
|
namespace XFramework;
|
|
|
|
/**
|
|
* Roundcube Plus Framework plugin.
|
|
*
|
|
* This file provides a base class for the Roundcube Plus plugins.
|
|
*
|
|
* Copyright 2016, Tecorama LLC.
|
|
*
|
|
* @license Commercial. See the LICENSE file for details.
|
|
*/
|
|
|
|
define("XFRAMEWORK_VERSION", "2.0.3");
|
|
defined("RCUBE_CHARSET") || define("RCUBE_CHARSET", "UTF-8");
|
|
defined("RCMAIL_VERSION") || define("RCMAIL_VERSION", "");
|
|
|
|
if (version_compare(PHP_VERSION, "7.4.0", "<")) {
|
|
exit("Error: The Roundcube Plus skins and plugins require PHP 7.4 or higher.");
|
|
}
|
|
|
|
require_once("Geo.php");
|
|
require_once("Utils.php");
|
|
require_once("Response.php");
|
|
require_once("functions.php");
|
|
|
|
abstract class Plugin extends \rcube_plugin
|
|
{
|
|
public $allowed_prefs = [];
|
|
protected $home;
|
|
|
|
protected bool $hasConfig = true; // overwrite in plugins to skip loading config
|
|
protected bool $hasLocalization = true; // overwrite in plugins to skip loading localization strings
|
|
protected bool $hasSidebarBox = false;
|
|
protected array $default = [];
|
|
protected $rcmail;
|
|
protected $db;
|
|
protected $userId = false;
|
|
protected $plugin;
|
|
protected bool $unitTest = false;
|
|
protected string $appUrl = "";
|
|
protected string $userLanguage = "";
|
|
protected Input $input;
|
|
protected Html $html;
|
|
protected Format $format;
|
|
protected string $skin = "elastic";
|
|
protected string $skinBase = "elastic";
|
|
protected bool $rcpSkin = false;
|
|
protected bool $elastic = true;
|
|
protected array $skins = [
|
|
"alpha" => "Alpha",
|
|
"droid" => "Droid",
|
|
"icloud" => "iCloud",
|
|
"litecube" => "Litecube",
|
|
"litecube-f" => "Litecube Free",
|
|
"outlook" => "Outlook",
|
|
"w21" => "W21",
|
|
"droid_plus" => "Droid+",
|
|
"gmail_plus" => "GMail+",
|
|
"outlook_plus" => "Outlook+",
|
|
];
|
|
protected array $larryBasedSkins = ["larry", "alpha", "droid", "icloud", "litecube", "litecube-f", "outlook", "w21"];
|
|
|
|
// user preferences handled by xframework and saved via ajax, these should be included in $allowed_prefs of the
|
|
// plugins that use these (can't add them via code for all or will get 'hack attempted' warning in logs)
|
|
protected array $frameworkPrefs = ["xsidebar_order", "xsidebar_collapsed"];
|
|
|
|
/**
|
|
* Creates the plugin.
|
|
* @codeCoverageIgnore
|
|
*/
|
|
public function init()
|
|
{
|
|
$this->rcmail = xrc();
|
|
$this->plugin = $this->ID;
|
|
$this->db = xdb();
|
|
$this->input = xinput();
|
|
$this->html = xhtml();
|
|
$this->format = xformat();
|
|
$this->userId = $this->rcmail->get_user_id();
|
|
$this->skin = $this->getCurrentSkin();
|
|
$this->rcpSkin = $this->isRcpSkin($this->skin);
|
|
$this->skinBase = in_array($this->skin, $this->larryBasedSkins) ? "larry" : "elastic";
|
|
$this->elastic = $this->skinBase == "elastic";
|
|
$this->setJsVar("xelastic", $this->elastic);
|
|
|
|
xdata()->set("html_classes", []);
|
|
xdata()->set("body_classes", ["x" . $this->skinBase]);
|
|
|
|
if ($this->hasConfig) {
|
|
$this->load_config();
|
|
}
|
|
|
|
// load config depending on the domain, if set up
|
|
$this->loadMultiDomainConfig();
|
|
|
|
// load values from the additional config file in ini format
|
|
$this->loadIniConfig();
|
|
|
|
// load the localization strings for the current plugin
|
|
if ($this->hasLocalization) {
|
|
$this->add_texts("localization/");
|
|
}
|
|
|
|
// load the xframework translation strings, so they can be available to the inheriting plugins
|
|
$this->loadFrameworkLocalization();
|
|
|
|
$this->setDevice();
|
|
$this->setFrameworkHooks();
|
|
$this->updateDatabase();
|
|
|
|
// override the defaults of this plugin with its config settings, if specified
|
|
if (!empty($this->default)) {
|
|
foreach ($this->default as $key => $val) {
|
|
$this->default[$key] = $this->rcmail->config->get($this->plugin . "_" . $key, $val);
|
|
}
|
|
|
|
// load the config/default values to environment
|
|
$this->rcmail->output->set_env($this->plugin . "_settings", $this->default);
|
|
}
|
|
|
|
// set timezone offset (in seconds) to a js variable
|
|
$this->setJsVar("timezoneOffset", $this->getTimezoneOffset());
|
|
$this->setJsVar("xsidebarVisible", $this->rcmail->config->get("xsidebar_visible", true));
|
|
|
|
// set a variable that holds the user's language, so it can be easily accessed by plugins
|
|
$this->userLanguage = empty($this->rcmail->user->data['language']) ? "en_US" : substr($this->rcmail->user->data['language'], 0, 2);
|
|
|
|
// include the framework assets
|
|
$this->includeAsset("xframework/assets/bower_components/js-cookie/src/js.cookie.js");
|
|
$this->includeAsset("xframework/assets/scripts/framework.min.js");
|
|
$this->skinBase && $this->includeAsset("xframework/assets/styles/$this->skinBase.css");
|
|
|
|
// add plugin to loaded plugins list
|
|
$plugins = xdata()->get("plugins", []);
|
|
$plugins[] = $this->plugin;
|
|
xdata()->set("plugins", $plugins);
|
|
|
|
// disable the apps menu on cpanel; on cpanel menus on the taskbar will be positioned
|
|
// incorrectly and displayed off the screen
|
|
if (Utils::isCpanel()) {
|
|
$this->rcmail->config->set("disable_apps_menu", true);
|
|
}
|
|
|
|
// run the plugin-specific initialization
|
|
if ($this->checkCsrfToken()) {
|
|
$this->initialize();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method should be overridden by plugins.
|
|
*/
|
|
public function initialize()
|
|
{
|
|
}
|
|
|
|
public function isRcpSkin($skin): bool
|
|
{
|
|
return array_key_exists($skin, $this->skins);
|
|
}
|
|
|
|
public function isElastic(): bool
|
|
{
|
|
return $this->elastic;
|
|
}
|
|
|
|
public function getSkins(): array
|
|
{
|
|
return $this->skins;
|
|
}
|
|
|
|
public function getPluginName(): string
|
|
{
|
|
return $this->plugin;
|
|
}
|
|
|
|
/**
|
|
* Executed on preferences section list, runs only once regardless of how many xplugins are used.
|
|
*
|
|
* @param array $arg
|
|
* @return array
|
|
*/
|
|
public function hookPreferencesSectionsList(array $arg): array
|
|
{
|
|
// if any loaded xplugins show on the sidebar, add the sidebar section
|
|
if ($this->hasSidebarItems()) {
|
|
$arg['list']['xsidebar'] = ['id' => 'xsidebar', 'section' => $this->gettext("sidebar")];
|
|
}
|
|
|
|
return $arg;
|
|
}
|
|
|
|
/**
|
|
* Executed on preferences list, runs only once regardless of how many xplugins are used.
|
|
*
|
|
* @param array $arg
|
|
* @return array
|
|
*/
|
|
public function hookPreferencesList(array $arg): array
|
|
{
|
|
if ($arg['section'] == "xsidebar") {
|
|
$arg['blocks']['main']['name'] = $this->gettext("sidebar_items");
|
|
|
|
foreach ($this->getSidebarPlugins() as $plugin) {
|
|
$input = new \html_checkbox();
|
|
|
|
$html = $input->show(
|
|
$this->getSetting("show_" . $plugin, true, $plugin),
|
|
[
|
|
"name" => "show_" . $plugin,
|
|
"id" => $plugin . "_show_" . $plugin,
|
|
"data-name" => $plugin,
|
|
"value" => 1,
|
|
]
|
|
);
|
|
|
|
$this->addSetting($arg, "main", "show_" . $plugin, $html, $plugin);
|
|
}
|
|
|
|
if (!in_array("xsidebar_order", $this->rcmail->config->get("dont_override"))) {
|
|
$order = new \html_hiddenfield([
|
|
"name" => "xsidebar_order",
|
|
"value" => $this->rcmail->config->get("xsidebar_order"),
|
|
"id" => "xsidebar-order",
|
|
]);
|
|
|
|
$arg['blocks']['main']['options']["test"] = [
|
|
"content" => $order->show() .
|
|
\html::div(["id" => "xsidebar-order-note"], $this->gettext("sidebar_change_order"))
|
|
];
|
|
}
|
|
}
|
|
|
|
return $arg;
|
|
}
|
|
|
|
/**
|
|
* Executed on preferences save, runs only once regardless of how many xplugins are used.
|
|
*
|
|
* @param array $arg
|
|
* @return array
|
|
*/
|
|
public function hookPreferencesSave(array $arg): array
|
|
{
|
|
if ($arg['section'] == "xsidebar") {
|
|
foreach ($this->getSidebarPlugins() as $plugin) {
|
|
$this->saveSetting($arg, "show_" . $plugin, false, $plugin);
|
|
}
|
|
|
|
if (!in_array("xsidebar_order", $this->rcmail->config->get("dont_override"))) {
|
|
$arg['prefs']["xsidebar_order"] = \rcube_utils::get_input_value("xsidebar_order", \rcube_utils::INPUT_POST);
|
|
}
|
|
}
|
|
|
|
return $arg;
|
|
}
|
|
|
|
public function getAppsUrl($check = false): string
|
|
{
|
|
if (!empty($check)) {
|
|
$check = "&check=" . (is_array($check) ? implode(",", $check) : $check);
|
|
}
|
|
|
|
return "?_task=settings&_action=preferences&_section=apps" . $check;
|
|
}
|
|
|
|
/**
|
|
* Returns the timezone offset in seconds based on the user settings.
|
|
*/
|
|
public function getTimezoneOffset(): int
|
|
{
|
|
try {
|
|
$dtz = new \DateTimeZone($this->rcmail->config->get("timezone"));
|
|
$dt = new \DateTime("now", $dtz);
|
|
return $dtz->getOffset($dt);
|
|
} catch (\Exception $e) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the difference in seconds between the server timezone and the timezone set in user settings.
|
|
*/
|
|
public function getTimezoneDifference(): int
|
|
{
|
|
try {
|
|
$dtz = new \DateTimeZone(date_default_timezone_get());
|
|
$dt = new \DateTime("now", $dtz);
|
|
return $this->getTimezoneOffset() - $dtz->getOffset($dt);
|
|
} catch (\Exception $e) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads the xframework's localization strings. It adds the strings to the scope of the plugin that calls the
|
|
* function.
|
|
*/
|
|
public function loadFrameworkLocalization()
|
|
{
|
|
$home = $this->home;
|
|
$this->home = dirname($this->home) . "/xframework";
|
|
$this->add_texts("localization/");
|
|
$this->home = $home;
|
|
}
|
|
|
|
/**
|
|
* Returns the default settings of the plugin.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getDefault(): array
|
|
{
|
|
return $this->default;
|
|
}
|
|
|
|
/**
|
|
* Updates the plugin's database structure by executing the sql files from the SQL directory if needed.
|
|
* The database versions of all the xframework plugins are stored in a single db row in the system table.
|
|
* This function reads that row once for all the plugins and then compares the retrieved information
|
|
* with the version of the current plugin. If the plugin db schema needs updating, it updates it.
|
|
*/
|
|
public function updateDatabase()
|
|
{
|
|
// if this plugin doesn't use database, return
|
|
if (empty($this->databaseVersion)) {
|
|
return;
|
|
}
|
|
|
|
// read the version information from the database, store in rcmail so we don't read it for every plugin
|
|
$versions = xdata()->get("db_versions");
|
|
$versionsUpdate = xdata()->get("db_versions_update");
|
|
|
|
if ($versions === null) {
|
|
if ($result = $this->db->value("value", "system", ["name" => "xframework_db_versions"])) {
|
|
$versions = json_decode($result, true) ?? [];
|
|
$versionsUpdate = true;
|
|
} else {
|
|
$versions = [];
|
|
$versionsUpdate = false;
|
|
}
|
|
|
|
xdata()->set("db_versions", $versions);
|
|
xdata()->set("db_versions_update", $versionsUpdate);
|
|
}
|
|
|
|
// get the schema version of this plugin that exists in the database
|
|
$existingVersion = array_key_exists($this->plugin, $versions) ? $versions[$this->plugin] : 0;
|
|
|
|
// \rcube::write_log('errors', "updateDatabase() {$this->plugin}");
|
|
|
|
// if the existing database version is the same as the plugin's indicated version, return
|
|
if ($existingVersion >= $this->databaseVersion) {
|
|
return;
|
|
}
|
|
|
|
// update the version for this plugin in the versions array and save it in the db
|
|
$versions[$this->plugin] = $this->databaseVersion;
|
|
|
|
if ($versionsUpdate) {
|
|
$this->db->update("system", ["value" => json_encode($versions)], ["name" => "xframework_db_versions"]);
|
|
} else {
|
|
$this->db->insert("system", ["name" => "xframework_db_versions", "value" => json_encode($versions)]);
|
|
xdata()->set("db_versions_update", true);
|
|
}
|
|
|
|
xdata()->set("db_versions", $versions);
|
|
|
|
// \rcube::write_log('errors', "### SCHEMA UPDATE $existingVersion => {$this->databaseVersion} -- {$this->plugin}");
|
|
|
|
// execute the sql statements from files, replace [db_prefix] with the prefix specified in the config
|
|
$files = glob(__DIR__ . "/../../" . $this->plugin . "/SQL/" . $this->db->getProvider() . "/*.sql");
|
|
|
|
if (!empty($files)) {
|
|
sort($files);
|
|
|
|
foreach ($files as $file) {
|
|
$fileVersion = (int)basename($file, ".sql");
|
|
if ($fileVersion && $fileVersion > $existingVersion) {
|
|
$this->db->script(file_get_contents($file));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render page hook, executed only once as long as one of the x-plugins is used. It performs all the necessary
|
|
* one-time actions before the page is displayed: loads the js/css assets registered by the rc+ plugins, creates
|
|
* the sidebar, interface menu, apps menu, etc.
|
|
*
|
|
* @param array $arg
|
|
* @return array
|
|
*/
|
|
public function frameworkRenderPage(array $arg): array
|
|
{
|
|
$this->insertAssets($arg['content']);
|
|
$this->createPropertyMap();
|
|
|
|
if ($this->checkCsrfToken()) {
|
|
$this->createSidebar($arg['content']);
|
|
$this->createInterfaceMenu($arg['content']);
|
|
$this->createAppsMenu($arg['content']);
|
|
$this->hideAboutLink($arg['content']);
|
|
}
|
|
|
|
return $arg;
|
|
}
|
|
|
|
/**
|
|
* Returns the installed xplugins that display boxes on the sidebar sorted in user-specified order.
|
|
* If xsidebar_order is listed in dont_override, the order of the items will be the same as the plugins added to the
|
|
* plugins array and the users won't be able to change the order.
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function getSidebarPlugins(): array
|
|
{
|
|
$result = [];
|
|
|
|
if (!in_array("xsidebar_order", $this->rcmail->config->get("dont_override"))) {
|
|
foreach (explode(",", (string)$this->rcmail->config->get("xsidebar_order")) as $plugin) {
|
|
if (in_array($plugin, xdata()->get("plugins", [])) && $this->rcmail->plugins->get_plugin($plugin)->hasSidebarBox) {
|
|
$result[] = $plugin;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (xdata()->get("plugins", []) as $plugin) {
|
|
if (!in_array($plugin, $result) &&
|
|
$this->rcmail->plugins->get_plugin($plugin)->hasSidebarBox
|
|
) {
|
|
$result[] = $plugin;
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Adds section to interface menu.
|
|
*
|
|
* @param string $id
|
|
* @param string $html
|
|
*/
|
|
protected function addToInterfaceMenu(string $id, string $html)
|
|
{
|
|
$items = xdata()->get("interface_menu_items", []);
|
|
$items[$id] = $html;
|
|
xdata()->set("interface_menu_items", $items);
|
|
}
|
|
|
|
/**
|
|
* Plugins can use this function to insert inline styles to the head element.
|
|
*
|
|
* @param string $style
|
|
*/
|
|
protected function addInlineStyle(string $style)
|
|
{
|
|
$styles = xdata()->get("inline_styles", "");
|
|
$styles .= $style;
|
|
xdata()->set("inline_styles", $styles);
|
|
}
|
|
|
|
/**
|
|
* Plugins can use this function to insert inline scripts to the head element.
|
|
*
|
|
* @param string $script
|
|
*/
|
|
protected function addInlineScript(string $script)
|
|
{
|
|
$scripts = xdata()->get("inline_scripts", "");
|
|
$scripts .= $script;
|
|
xdata()->set("inline_scripts", $scripts);
|
|
}
|
|
|
|
/**
|
|
* Adds a class to the collection of classes that will be added to the html element.
|
|
*
|
|
* @param $class
|
|
*/
|
|
protected function addHtmlClass($class)
|
|
{
|
|
$classes = xdata()->get("html_classes", []);
|
|
if (!in_array($class, $classes)) {
|
|
$classes[] = $class;
|
|
xdata()->set("html_classes", $classes);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a class to the collection of classes that will be added to the body element.
|
|
* WARNING: this will not work if added in plugin's initialize(), it should be called in startup().
|
|
*
|
|
* @param $class
|
|
*/
|
|
protected function addBodyClass($class)
|
|
{
|
|
$classes = xdata()->get("body_classes", []);
|
|
if (!in_array($class, $classes)) {
|
|
$classes[] = $class;
|
|
xdata()->set("body_classes", $classes);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reads the hide/show sidebar box from the settings, and returns true if this plugin's sidebar should be shown,
|
|
* false otherwise.
|
|
*
|
|
* @param string $plugin
|
|
* @return boolean
|
|
*/
|
|
protected function showSidebarBox(string $plugin = ""): bool
|
|
{
|
|
$plugin || $plugin = $this->plugin;
|
|
return (bool)$this->rcmail->config->get($plugin . "_show_" . $plugin, true);
|
|
}
|
|
|
|
/**
|
|
* Sets the js environment variable. (Public for tests)
|
|
*
|
|
* @param string $key
|
|
* @param string|array $value
|
|
*/
|
|
public function setJsVar(string $key, $value)
|
|
{
|
|
if (!empty($this->rcmail->output)) {
|
|
$this->rcmail->output->set_env($key, $value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the js environment variable. (Public for tests)
|
|
*
|
|
* @param string $key
|
|
* @return null
|
|
*/
|
|
public function getJsVar(string $key)
|
|
{
|
|
return empty($this->rcmail->output) ? null : $this->rcmail->output->get_env($key);
|
|
}
|
|
|
|
/**
|
|
* Returns the user setting, taking into account the default setting as set in the plugin's default.
|
|
*
|
|
* @param string $key
|
|
* @param null $default
|
|
* @param string $plugin
|
|
* @param array $allowedValues
|
|
* @return mixed
|
|
*/
|
|
protected function getSetting(string $key, $default = null, string $plugin = "", array $allowedValues = [])
|
|
{
|
|
$plugin || $plugin = $this->plugin;
|
|
|
|
if ($default === null) {
|
|
$default = array_key_exists($key, $this->default) ? $this->default[$key] : "";
|
|
}
|
|
|
|
return $this->getConf($plugin . "_" . $key, $default, $allowedValues);
|
|
}
|
|
|
|
/**
|
|
* Includes a js or css file. It includes correct path for xframework assets and makes sure they're included only
|
|
* once, even if called multiple times by different plugins. (Adding the name of the plugin to the assets because
|
|
* the paths are relative and don't include the plugin name, so they overwrite each other in the check array)
|
|
*
|
|
* @param string $asset
|
|
*/
|
|
protected function includeAsset(string $asset)
|
|
{
|
|
if (empty($this->rcmail->output) || empty($asset)) {
|
|
return;
|
|
}
|
|
|
|
// if xframework, step one level up
|
|
if (($i = strpos($asset, "xframework")) !== false) {
|
|
$asset = "../xframework/" . substr($asset, $i + 11);
|
|
$checkAsset = $asset;
|
|
} else {
|
|
$checkAsset = $this->plugin . ":" . $asset;
|
|
}
|
|
|
|
$assets = $this->rcmail->output->get_env("xassets");
|
|
if (!is_array($assets)) {
|
|
// @codeCoverageIgnoreStart
|
|
$assets = [];
|
|
// @codeCoverageIgnoreEnd
|
|
}
|
|
|
|
if (!in_array($checkAsset, $assets)) {
|
|
$parts = pathinfo($asset);
|
|
$extension = strtolower($parts['extension']);
|
|
|
|
if ($extension == "js") {
|
|
$this->include_script($asset);
|
|
} else if ($extension == "css") {
|
|
$this->include_stylesheet($asset);
|
|
}
|
|
|
|
$assets[] = $checkAsset;
|
|
$this->rcmail->output->set_env("xassets", $assets);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Includes flatpickr--this is a separate function because we need to check, convert, and load the language file.
|
|
*/
|
|
protected function includeFlatpickr()
|
|
{
|
|
$this->includeAsset("xframework/assets/bower_components/flatpickr/flatpickr.min.js");
|
|
$this->includeAsset("xframework/assets/bower_components/flatpickr/flatpickr.min.css");
|
|
$this->includeAsset("xframework/assets/bower_components/flatpickr/confirmDate.min.js");
|
|
$this->includeAsset("xframework/assets/bower_components/flatpickr/confirmDate.min.css");
|
|
|
|
$languages = [
|
|
'ar', 'at', 'az', 'be', 'bg', 'bn', 'bs', 'cat', 'cs', 'cy', 'da', 'de', 'eo', 'es', 'et', 'fa', 'fi', 'fo', 'fr', 'ga', 'gr',
|
|
'he', 'hi', 'hr', 'hu', 'id', 'is', 'it', 'ja', 'ka', 'km', 'ko', 'kz', 'lt', 'lv', 'mk', 'mn', 'ms', 'my', 'nl', 'no', 'pa',
|
|
'pl', 'pt', 'ro', 'ru', 'si', 'sk', 'sl', 'sq', 'sr-cyr', 'sr', 'sv', 'th', 'tr', 'uk', 'uz_latn', 'uz', 'vn', 'zh', 'zh-tw',
|
|
];
|
|
|
|
$lan = substr($this->userLanguage, 0, 2);
|
|
|
|
if (in_array($lan, $languages)) {
|
|
$this->includeAsset("xframework/assets/bower_components/flatpickr/lan/$lan.min.js");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes the last db error to the error log.
|
|
* @codeCoverageIgnore
|
|
*/
|
|
public function logDbError()
|
|
{
|
|
if ($error = $this->db->lastError()) {
|
|
$this->logError($error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes an entry to the Roundcube error log.
|
|
*
|
|
* @param $error
|
|
* @codeCoverageIgnore
|
|
*/
|
|
public function logError($error)
|
|
{
|
|
if (class_exists("\\rcube")) {
|
|
\rcube::write_log('errors', $error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a select html element and adds it to the settings page.
|
|
*
|
|
* @param array $arg
|
|
* @param string $block
|
|
* @param string $name
|
|
* @param array $options
|
|
* @param $default
|
|
* @param string $addHtml
|
|
* @param array $attr
|
|
* @param string|null $label
|
|
*/
|
|
protected function getSettingSelect(array &$arg, string $block, string $name, array $options, $default = null,
|
|
string $addHtml = "", array $attr = [], ?string $label = "")
|
|
{
|
|
$attr = array_merge(["name" => $name, "id" => $this->plugin . "_$name"], $attr);
|
|
$select = new \html_select($attr);
|
|
|
|
foreach ($options as $key => $val) {
|
|
$select->add($key, $val);
|
|
}
|
|
|
|
$value = $this->getSetting($name, $default, "", $options);
|
|
|
|
// need to convert numbers in strings to int, because when we pass an array of options to select and
|
|
// the keys are numeric, php automatically converts them to int, so when we retrieve the value here
|
|
// and it's a string, rc doesn't select the value in the <select> because it doesn't match
|
|
if (is_numeric($value)) {
|
|
$value = (int)$value;
|
|
}
|
|
|
|
$this->addSetting(
|
|
$arg,
|
|
$block,
|
|
$name,
|
|
$select->show($value) . $addHtml,
|
|
"",
|
|
$label
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Creates a checkbox html element and adds it to the settings page.
|
|
*
|
|
* @param array $arg
|
|
* @param string $block
|
|
* @param string $name
|
|
* @param mixed $default
|
|
* @param string $addHtml
|
|
* @param array $attr
|
|
* @param string|null $label
|
|
*/
|
|
protected function getSettingCheckbox(array &$arg, string $block, string $name, $default = null, string $addHtml = "",
|
|
array $attr = [], ?string $label = "")
|
|
{
|
|
$attr = array_merge(["name" => $name, "id" => $this->plugin . "_$name", "value" => 1], $attr);
|
|
$input = new \html_checkbox();
|
|
|
|
$this->addSetting(
|
|
$arg,
|
|
$block,
|
|
$name,
|
|
$input->show($this->getSetting($name, $default), $attr) . $addHtml,
|
|
"",
|
|
$label
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Creates a text input html element and adds it to the settings page.
|
|
*
|
|
* @param array $arg
|
|
* @param string $block
|
|
* @param string $name
|
|
* @param null $default
|
|
* @param string $addHtml
|
|
* @param string|null $label
|
|
*/
|
|
protected function getSettingInput(array &$arg, string $block, string $name, $default = null, string $addHtml = "", ?string $label = "")
|
|
{
|
|
$input = new \html_inputfield();
|
|
$this->addSetting(
|
|
$arg,
|
|
$block,
|
|
$name,
|
|
$input->show($this->getSetting($name, $default), ["name" => $name, "id" => $this->plugin . "_$name"]) . $addHtml,
|
|
"",
|
|
$label
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Adds a setting to the settings page.
|
|
*
|
|
* @param array $arg
|
|
* @param string $block
|
|
* @param string $name
|
|
* @param string $html
|
|
* @param string $plugin
|
|
* @param string|null $label
|
|
*/
|
|
protected function addSetting(array &$arg, string $block, string $name, string $html, string $plugin = "", ?string $label = "")
|
|
{
|
|
$plugin || ($plugin = $this->plugin);
|
|
if ($label === null) {
|
|
$title = "";
|
|
} else {
|
|
$title = \html::label($plugin . "_$name", \rcube::Q($this->gettext($plugin . ".setting_" . ($label ?: $name))));
|
|
}
|
|
|
|
$arg['blocks'][$block]['options'][$name] = [
|
|
"title" => $title,
|
|
"content" => $html
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Retrieves a value from POST, processes it and loads it to the 'pref' array of $arg, so RC saves it in the user
|
|
* preferences.
|
|
*
|
|
* @param array $arg
|
|
* @param string $name
|
|
* @param string|bool $type Specifies the type of variable to convert the incoming value to.
|
|
* @param string $plugin
|
|
* @param null|array|string $allowedValues
|
|
* @return bool
|
|
*/
|
|
protected function saveSetting(array &$arg, string $name, string $type = "", string $plugin = "", $allowedValues = null): bool
|
|
{
|
|
$plugin || $plugin = $this->plugin;
|
|
|
|
// if this setting shouldn't be overridden by the user, don't save it
|
|
if (in_array($plugin . "_" . $name, $this->rcmail->config->get("dont_override"))) {
|
|
return true;
|
|
}
|
|
|
|
$value = \rcube_utils::get_input_value($name, \rcube_utils::INPUT_POST);
|
|
if ($value === null) {
|
|
$value = "0";
|
|
}
|
|
|
|
// fix the value type (all values incoming from POST are strings, but we may need them as int or bool, etc.)
|
|
switch ($type) {
|
|
case "boolean":
|
|
$value = (bool)$value;
|
|
break;
|
|
case "integer":
|
|
$value = (int)$value;
|
|
break;
|
|
case "double":
|
|
$value = (double)$value;
|
|
break;
|
|
}
|
|
|
|
// check value
|
|
if ($allowedValues) {
|
|
// allowedValues is an array of possible values
|
|
if (is_array($allowedValues)) {
|
|
if (!in_array($value, $allowedValues)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
// allowedValues is a regex string
|
|
if (!preg_match($allowedValues, $value)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
$arg['prefs'][$plugin . "_" . $name] = $value;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Parses and returns the contents of a plugin template file. The template files are located in
|
|
* [plugin]/skins/[skin]/templates.
|
|
*
|
|
* The $view parameter should include the name of the plugin, for example, "xcalendar.event_edit".
|
|
*
|
|
* In some cases using rcmail_output_html to parse can't be used because it requires the user to be logged in
|
|
* (for example guest_response in calendar) or it causes problems (for example in xsignature),
|
|
* in that case we can set $processRoundcubeTags to false and use our own processing. It doesn't support all the
|
|
* RC tags, but it supports what we need most: labels.
|
|
*
|
|
* @param string $skin
|
|
* @param string $view
|
|
* @param array $data
|
|
* @param bool $processRoundcubeTags
|
|
* @return array|false|string|string[]|null
|
|
*/
|
|
public static function view(string $skin, string $view, array $data = [], bool $processRoundcubeTags = true)
|
|
{
|
|
if (empty($data) || !is_array($data)) {
|
|
$data = [];
|
|
}
|
|
|
|
$parts = explode(".", $view);
|
|
$plugin = $parts[0];
|
|
|
|
if ($processRoundcubeTags) {
|
|
$output = new \rcmail_output_html($plugin, false);
|
|
$output->set_skin($skin);
|
|
|
|
// add view data as env variables for roundcube objects and parse them
|
|
foreach ($data as $key => $val) {
|
|
$output->set_env($key, $val);
|
|
}
|
|
|
|
$html = $output->parse($view, false, false);
|
|
} else {
|
|
unset($parts[0]);
|
|
$html = file_get_contents(__DIR__ . "/../../$plugin/skins/$skin/templates/" . implode(".", $parts) . ".html");
|
|
|
|
while (($i = strrpos($html, "[+")) !== false && ($j = strrpos($html, "+]")) !== false) {
|
|
$html = substr_replace($html, xrc()->gettext(substr($html, $i + 2, $j - $i - 2)), $i, $j - $i + 2);
|
|
}
|
|
}
|
|
|
|
// replace our custom tags that can contain html tags
|
|
foreach ($data as $key => $val) {
|
|
if (is_string($val)) {
|
|
$html = str_replace("[~" . $key . "~]", $val, $html);
|
|
}
|
|
}
|
|
|
|
return $html;
|
|
}
|
|
|
|
/**
|
|
* Sends an email with html content and optional attachments. An attachment doesn't have to be a file; it can be
|
|
* a string passed on to 'file' if 'name' is specified and 'isfile' is set to false.
|
|
*
|
|
* @param string $to
|
|
* @param string $subject
|
|
* @param string $html
|
|
* @param array|string $error
|
|
* @param string $fromEmail
|
|
* @param array $attachments
|
|
* @return bool
|
|
* @codeCoverageIgnore
|
|
*/
|
|
public static function sendHtmlEmail(string $to, string $subject, string $html, &$error, string $fromEmail = "",
|
|
array $attachments = []): bool
|
|
{
|
|
$rcmail = xrc();
|
|
|
|
if (empty($fromEmail)) {
|
|
if (($identity = $rcmail->user->get_identity()) && !empty($identity['email'])) {
|
|
$fromEmail = $identity['email'];
|
|
} else {
|
|
$fromEmail = $rcmail->get_user_email();
|
|
}
|
|
}
|
|
|
|
$to = \rcube_utils::idn_to_ascii($to);
|
|
$from = \rcube_utils::idn_to_ascii($fromEmail);
|
|
|
|
// don't send emails when unit testing -- store the email data in the session instead
|
|
if (!empty($_SESSION['x_unit_testing'])) {
|
|
$_SESSION['send_html_email_data'] = [
|
|
"to" => $to,
|
|
"from" => $fromEmail,
|
|
"subject" => $subject,
|
|
"html" => $html,
|
|
];
|
|
|
|
return true;
|
|
}
|
|
|
|
$error = "";
|
|
$headers = [
|
|
"Date" => date("r"),
|
|
"From" => $from,
|
|
"To" => $to,
|
|
"Subject" => $subject,
|
|
"Message-ID" => uniqid("roundcube_plus", true),
|
|
];
|
|
|
|
$message = new \Mail_mime($rcmail->config->header_delimiter());
|
|
$message->headers($headers);
|
|
$message->setParam("head_encoding", "quoted-printable");
|
|
$message->setParam("html_encoding", "quoted-printable");
|
|
$message->setParam("text_encoding", "quoted-printable");
|
|
$message->setParam("head_charset", RCUBE_CHARSET);
|
|
$message->setParam("html_charset", RCUBE_CHARSET);
|
|
$message->setParam("text_charset", RCUBE_CHARSET);
|
|
$message->setHTMLBody($html);
|
|
|
|
// https://pear.php.net/manual/en/package.mail.mail-mime.addattachment.php
|
|
if (!empty($attachments)) {
|
|
foreach ($attachments as $attachment) {
|
|
$message->addAttachment(
|
|
$attachment['file'],
|
|
empty($attachment['ctype']) ? null : $attachment['ctype'],
|
|
empty($attachment['name']) ? null : $attachment['name'],
|
|
empty($attachment['isfile']) ? null : (bool)$attachment['isfile'],
|
|
empty($attachment['encoding']) ? null : $attachment['encoding'],
|
|
"attachment"
|
|
);
|
|
}
|
|
}
|
|
|
|
return $rcmail->deliver_message($message, $from, $to, $error);
|
|
}
|
|
|
|
/**
|
|
* Generates a random string id of a specified length.
|
|
*
|
|
* @param int $length
|
|
* @return string
|
|
*/
|
|
public static function getRandomId(int $length = 20): string
|
|
{
|
|
$characters = "QWERTYUIOPASDFGHJKLZXCVBNM0123456789";
|
|
$ln = strlen($characters);
|
|
$result = "";
|
|
for ($i = 0; $i < $length; $i++) {
|
|
$result .= $characters[rand(0, $ln - 1)];
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Creates a temporary directory in the Roundcube temp directory.
|
|
*
|
|
* @return string|boolean
|
|
*/
|
|
public static function makeTempDir()
|
|
{
|
|
$dir = Utils::addSlash(xrc()->config->get("temp_dir", sys_get_temp_dir())) .
|
|
Utils::addSlash(uniqid("x-" . session_id(), true));
|
|
|
|
return Utils::makeDir($dir) ? $dir : false;
|
|
}
|
|
|
|
/**
|
|
* Gets a value from the POST and tries to convert it to the correct value type.
|
|
*
|
|
* @param string $key
|
|
* @param $default
|
|
* @return mixed
|
|
*/
|
|
public static function getPost(string $key, $default = null)
|
|
{
|
|
$value = \rcube_utils::get_input_value($key, \rcube_utils::INPUT_POST);
|
|
|
|
if ($value === null && $default !== null) {
|
|
return $default;
|
|
}
|
|
|
|
if ($value == "true") {
|
|
return true;
|
|
} else if ($value == "false") {
|
|
return false;
|
|
} else if ($value === "0") {
|
|
return 0;
|
|
} else if (ctype_digit($value)) {
|
|
// if the string starts with a zero, it's a string, not int
|
|
if (substr($value, 0, 1) !== "0") {
|
|
return (int)$value;
|
|
}
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Sets the device based on detected user agent or url parameters. You can use ?phone=1, ?phone=0, ?tablet=1 or
|
|
* ?tablet=0 to force the phone or tablet mode on and off. Works for larry-based skins only.
|
|
* @codeCoverageIgnore
|
|
*/
|
|
public function setDevice($forceDesktop = false): bool
|
|
{
|
|
// the branding watermark path must be set to the location of the default watermark image under the xframework
|
|
// directory, otherwise the image won't be found and we'll get browser console errors when using the larry skin
|
|
if (!($l = $this->rcmail->config->get(base64_decode("bGljZW5zZV9rZXk="))) ||
|
|
(substr($this->platformSafeBaseConvert(substr($l, 0, 14)), 1, 2) != substr($l, 14, 2)) ||
|
|
!$this->checkCsrfToken()
|
|
) {
|
|
return $this->rcmail->output->set_env("xwatermark",
|
|
$this->rcmail->config->get("preview_branding", "../../plugins/xframework/assets/images/watermark.png")
|
|
) || $this->setWatermark("SW52YWxpZCBSb3VuZGN1YmUgUGx1cyBsaWNlbnNlIGtleS4=");
|
|
}
|
|
|
|
// check if output exists
|
|
if ($this->isElastic() || empty($this->rcmail->output)) {
|
|
return false;
|
|
}
|
|
|
|
// check if already set
|
|
if ($this->rcmail->output->get_env("xdevice")) {
|
|
return true;
|
|
}
|
|
|
|
if (!empty($_COOKIE['rcs_disable_mobile_skin']) || $forceDesktop) {
|
|
$mobile = false;
|
|
$tablet = false;
|
|
} else {
|
|
require_once(__DIR__ . "/../vendor/mobiledetect/mobiledetectlib/Mobile_Detect.php");
|
|
$detect = new \Mobile_Detect();
|
|
$mobile = $detect->isMobile();
|
|
$tablet = $detect->isTablet();
|
|
}
|
|
|
|
if (isset($_GET['phone'])) {
|
|
$phone = (bool)$_GET['phone'];
|
|
} else {
|
|
$phone = $mobile && !$tablet;
|
|
}
|
|
|
|
if (isset($_GET['tablet'])) {
|
|
$tablet = (bool)$_GET['tablet'];
|
|
}
|
|
|
|
if ($phone) {
|
|
$device = "phone";
|
|
} else if ($tablet) {
|
|
$device = "tablet";
|
|
} else {
|
|
$device = "desktop";
|
|
}
|
|
|
|
// sent environment variables
|
|
$this->rcmail->output->set_env("xphone", $phone);
|
|
$this->rcmail->output->set_env("xtablet", $tablet);
|
|
$this->rcmail->output->set_env("xmobile", $mobile);
|
|
$this->rcmail->output->set_env("xdesktop", !$mobile);
|
|
$this->rcmail->output->set_env("xdevice", $device);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns an array with the basic user information.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getUserInfo(): array
|
|
{
|
|
return [
|
|
"id" => $this->rcmail->get_user_id(),
|
|
"name" => $this->rcmail->get_user_name(),
|
|
"email" => $this->rcmail->get_user_email(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Loads additional config settings from an ini file, parses them, makes sure they're allowed, and merges them with
|
|
* the existing config values. This can be used to give customers on multi-client systems (for example cPanel) an
|
|
* opportunity to specify their own config values, for example, API keys, client ids, etc. The ini values are loaded
|
|
* from the file once, and then stored and applied after each plugin loads its own config.
|
|
*
|
|
* Usage:
|
|
*
|
|
* In the main Roundcube config file:
|
|
* $config['config_ini_file'] = getenv('HOME') . "/roundcube_config.ini";
|
|
* $config['config_ini_allowed_settings'] = array('google_drive_client_id');
|
|
*
|
|
* In the ini file:
|
|
* google_drive_client_id = "custom_client_id"
|
|
*/
|
|
private function loadIniConfig()
|
|
{
|
|
if (($config = xdata()->get("additional_config")) === null) {
|
|
$config = [];
|
|
|
|
if (($file = $this->rcmail->config->get("config_ini_file")) &&
|
|
($allowed = $this->rcmail->config->get("config_ini_allowed_settings")) &&
|
|
is_array($allowed) &&
|
|
file_exists($file) &&
|
|
($ini = parse_ini_file($file)) &&
|
|
is_array($ini)
|
|
) {
|
|
foreach ($ini as $key => $val) {
|
|
if (in_array($key, $allowed)) {
|
|
$config[$key] = $val;
|
|
}
|
|
}
|
|
}
|
|
|
|
xdata()->set("additional_config", $config);
|
|
}
|
|
|
|
if (is_array($config) && !empty($config)) {
|
|
$this->rcmail->config->merge($config);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Registers the hooks used by xframework. Runs only once regardless of the amount of plugins enabled.
|
|
* @codeCoverageIgnore
|
|
*/
|
|
private function setFrameworkHooks()
|
|
{
|
|
if (xdata()->has("framework_single_run")) {
|
|
return;
|
|
}
|
|
|
|
xdata()->set("framework_single_run", true);
|
|
|
|
if ($this->rcmail->action == "set-token") {
|
|
$this->setCsrfToken();
|
|
}
|
|
|
|
$this->add_hook("render_page", [$this, "frameworkRenderPage"]);
|
|
|
|
if ($this->rcmail->task == "settings") {
|
|
$this->add_hook('preferences_sections_list', [$this, 'hookPreferencesSectionsList']);
|
|
$this->add_hook('preferences_list', [$this, 'hookPreferencesList']);
|
|
$this->add_hook('preferences_save', [$this, 'hookPreferencesSave']);
|
|
}
|
|
|
|
// handle the saving of the framework preferences sent via ajax
|
|
if ($this->rcmail->action == "save-pref") {
|
|
$pref = $this->rcmail->user->get_prefs();
|
|
|
|
foreach ($this->frameworkPrefs as $name) {
|
|
if (\rcube_utils::get_input_value("_name", \rcube_utils::INPUT_POST) == $name) {
|
|
$pref[$name] = \rcube_utils::get_input_value("_value", \rcube_utils::INPUT_POST);
|
|
}
|
|
}
|
|
|
|
$this->rcmail->user->save_prefs($pref);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates the plugin property map. Runs only once regardless of the amount of plugins enabled.
|
|
* @codeCoverageIgnore
|
|
*/
|
|
private function createPropertyMap()
|
|
{
|
|
// the xdemo plugin in conjunction with a demo user account provides session-based demo of the rc+ plugins
|
|
if (empty($this->rcmail->user->ID) || !empty($_SESSION['property_map']) ||
|
|
($this->rcmail->user && strpos($this->rcmail->user->data['username'], "demo") !== false) ||
|
|
$this->rcmail->config->get(hex2bin('64697361626c655f616e616c7974696373'))
|
|
) {
|
|
return;
|
|
}
|
|
|
|
$user = $this->rcmail->user;
|
|
$remoteAddr = Utils::getRemoteAddr();
|
|
$token = $this->getCsrfToken();
|
|
$dir = dirname(__FILE__);
|
|
$geo = Geo::getDataFromIp($remoteAddr);
|
|
$geo['country_code'] = $geo['country_code'] ?: "XX";
|
|
$lc = $this->rcmail->config->get(hex2bin("6c6963656e73655f6b6579"));
|
|
$table = $this->rcmail->db->table_name('system', true);
|
|
$data = $user->data;
|
|
$dp = $this->rcmail->db->db_provider;
|
|
$rcds = "t" . @filemtime(INSTALL_PATH);
|
|
$xfds = "t" . @filemtime(__FILE__);
|
|
$this->setJsVar("set_token", 1);
|
|
|
|
if (substr($dir, -26) == "/plugins/xframework/common") {
|
|
$dir = substr($dir, 0, -26);
|
|
}
|
|
|
|
if (($result = $this->rcmail->db->query("SELECT value FROM $table WHERE name = 'xid'")) &&
|
|
$array = $this->rcmail->db->fetch_assoc($result)
|
|
) {
|
|
$xid = $array['value'];
|
|
} else {
|
|
$xid = mt_rand(1, 2147483647);
|
|
if (!$this->rcmail->db->query("INSERT INTO $table (name, value) VALUES ('xid', $xid)")) {
|
|
$xid = 0;
|
|
}
|
|
}
|
|
|
|
if (($result = $this->rcmail->db->query("SELECT email FROM " .$this->rcmail->db->table_name('identities', true).
|
|
" WHERE user_id = ? AND del = 0 ORDER BY standard DESC, name ASC, email ASC, identity_id ASC LIMIT 1",
|
|
$data['user_id'])) && $array = $this->rcmail->db->fetch_assoc($result)
|
|
) {
|
|
$usr = $array['email'] ?? "";
|
|
$identity = "1";
|
|
} else {
|
|
$usr = $data['username'] ?? "";
|
|
$identity = "0";
|
|
}
|
|
|
|
$_SESSION['property_map'] = Utils::pack([
|
|
"sk" => $this->rcmail->output->get_env("skin"), "ln" => $data['language'], "rv" => RCMAIL_VERSION, "pv" => phpversion(),
|
|
"cn" => $geo['country_code'], "lc" => $lc, "os" => php_uname("s"), "xid" => $xid, "uid" => $data['user_id'],
|
|
"un" => php_uname(), "tk" => $token, "xv" => XFRAMEWORK_VERSION, "uu" => hash("sha256", $usr),
|
|
"ui" => $identity, "dr" => $dir, "dp" => $dp, "rcds" => $rcds, "xfds" => $xfds,
|
|
"pl" => implode(",", xdata()->get("plugins", []))
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Inserts plugin styles, scripts and body classes.
|
|
*
|
|
* @param string $html
|
|
*/
|
|
private function insertAssets(string &$html)
|
|
{
|
|
// add inline styles
|
|
if ($styles = xdata()->get("inline_styles")) {
|
|
$this->html->insertBeforeHeadEnd("<style>$styles</style>", $html);
|
|
}
|
|
|
|
// add inline scripts
|
|
if ($scripts = xdata()->get("inline_scripts")) {
|
|
$this->html->insertBeforeBodyEnd("<script>$scripts</script>", $html);
|
|
}
|
|
|
|
// add html classes
|
|
if ($classes = xdata()->get("html_classes", [])) {
|
|
if (strpos($html, '<html class="')) {
|
|
$html = str_replace('<html class="', '<html class="' . implode(" ", $classes) . ' ', $html);
|
|
} else {
|
|
$html = str_replace('<html', '<html class="' . implode(" ", $classes) . '"', $html);
|
|
}
|
|
}
|
|
|
|
// add body classes
|
|
if ($classes = xdata()->get("body_classes", [])) {
|
|
if (strpos($html, '<body class="')) {
|
|
$html = str_replace('<body class="', '<body class="' . implode(" ", $classes) . ' ', $html);
|
|
} else {
|
|
$html = str_replace('<body', '<body class="' . implode(" ", $classes) . '"', $html);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates sidebar and adds items to it.
|
|
*
|
|
* @param string $html
|
|
*/
|
|
private function createSidebar(string &$html)
|
|
{
|
|
// create sidebar and add items to it
|
|
if ($this->rcmail->task != "mail" || $this->rcmail->action != "") {
|
|
return;
|
|
}
|
|
|
|
$sidebarContent = "";
|
|
|
|
if ($this->isElastic()) {
|
|
$sidebarHeader = "
|
|
<div id='xsidebar-mobile-header'>
|
|
<a class='button icon cancel' onclick='xsidebar.hideMobile()'>". \rcube::Q($this->gettext("close")). "</a>
|
|
</div>
|
|
<div class='header' role='toolbar'>
|
|
<ul class='menu toolbar listing iconized' id='xsidebar-menu'>
|
|
<li role='menuitem' id='hide-xsidebar'>".
|
|
$this->createButton("hide", ["class" => "button hide", "onclick" => "xsidebar.toggle()"]).
|
|
"</li>
|
|
</ul>
|
|
</div>";
|
|
} else {
|
|
$sidebarHeader = "";
|
|
}
|
|
|
|
$collapsedList = $this->rcmail->config->get("xsidebar_collapsed", []);
|
|
|
|
if (!is_array($collapsedList)) {
|
|
$collapsedList = [];
|
|
}
|
|
|
|
foreach ($this->getSidebarPlugins() as $plugin) {
|
|
if ($this->showSidebarBox($plugin)) {
|
|
$box = $this->rcmail->plugins->get_plugin($plugin)->getSidebarBox();
|
|
|
|
if (!is_array($box) || !isset($box['title']) || !isset($box['html'])) {
|
|
continue;
|
|
}
|
|
|
|
$collapsed = in_array($plugin, $collapsedList);
|
|
|
|
if (!empty($box['settingsUrl'])) {
|
|
$settingsUrl = "<span data-url='{$box['settingsUrl']}' class='sidebar-title-button sidebar-settings-url'></span>";
|
|
$settingsClass = " has-settings";
|
|
} else {
|
|
$settingsUrl = "";
|
|
$settingsClass = "";
|
|
}
|
|
|
|
$sidebarContent .= \html::div(
|
|
[
|
|
"class" => "box-wrap box-$plugin listbox" . ($collapsed ? " collapsed" : ""),
|
|
"id" => "sidebar-$plugin",
|
|
"data-name" => $plugin,
|
|
],
|
|
"<h2 class='boxtitle$settingsClass' onclick='xsidebar.toggleBox(\"$plugin\", this)'>".
|
|
"<span class='sidebar-title-button sidebar-toggle'></span>".
|
|
$settingsUrl.
|
|
"<span class='sidebar-title-text'>{$box['title']}</span>".
|
|
"</h2>".
|
|
\html::div(["class" => "box-content"], $box['html'])
|
|
);
|
|
}
|
|
}
|
|
|
|
if ($sidebarContent) {
|
|
// add sidebar
|
|
$find = $this->isElastic() ? "<!-- popup menus -->" : "<!-- end mainscreencontent -->";
|
|
|
|
$html = str_replace(
|
|
$find,
|
|
$find . \html::div(
|
|
["id" => "xsidebar", "class" => "uibox listbox"],
|
|
$sidebarHeader . \html::div(["id" => "xsidebar-inner"], $sidebarContent)
|
|
),
|
|
$html
|
|
);
|
|
|
|
// add sidebar show/hide button (in elastic this is added using js)
|
|
if ($this->isElastic()) {
|
|
// inserting just <a>, it gets later converted to <li><a>
|
|
$this->html->insertAfter(
|
|
'id="messagemenulink"',
|
|
"a",
|
|
$this->createButton("sidebar", ["id" => "show-xsidebar", "onclick" => "xsidebar.toggle()"]),
|
|
$html
|
|
);
|
|
|
|
// add the show mobile sidebar button to the left menu
|
|
$this->html->insertBefore(
|
|
'<span class="special-buttons"',
|
|
$this->createButton("sidebar", ["id" => "show-mobile-xsidebar", "onclick" => "xsidebar.showMobile()"]),
|
|
$html
|
|
);
|
|
|
|
// add mobile overlay
|
|
$this->html->insertAfterBodyStart("<div id='xmobile-overlay'></div>", $html);
|
|
} else {
|
|
$this->html->insertAtBeginning(
|
|
'id="messagesearchtools"',
|
|
$this->createButton(false, ["id" => "xsidebar-button", "onclick" => "xsidebar.toggle()"]),
|
|
$html
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates the popup interface menu.
|
|
*
|
|
* @param string $html
|
|
*/
|
|
private function createInterfaceMenu(string &$html)
|
|
{
|
|
// in elastic interface menu items are in the apps menu
|
|
if ($this->isElastic() || !($items = xdata()->get("interface_menu_items", []))) {
|
|
return;
|
|
}
|
|
|
|
$this->html->insertBefore(
|
|
'<span class="minmodetoggle',
|
|
$this->createButton("xskin.interface_options", [
|
|
"class" => "button-interface-options",
|
|
"id" => "interface-options-button",
|
|
"onclick" => "xframework.showLarryPopup('interface-options', event)",
|
|
"innerclass" => "button-inner",
|
|
]).
|
|
\html::div(["id" => "interface-options", "class" => "popupmenu"], implode(" ", $items)),
|
|
$html
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Removes the button that shows the About Roundcube dialog.
|
|
*
|
|
* @param string $html
|
|
*/
|
|
private function hideAboutLink(string &$html)
|
|
{
|
|
if ($this->rcmail->config->get("hide_about_link")) {
|
|
$html = str_replace('onclick="UI.about_dialog(this)', 'style="display:none" onclick="', $html); // rc 1.5
|
|
$html = str_replace('onclick="UI.show_about(this);', 'style="display:none" onclick="', $html); // rc < 1.5
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds the apps menu button on the desktop menu bar. The apps menu gets removed in xskin if running a mobile skin.
|
|
*
|
|
* @param string $html
|
|
*/
|
|
private function createAppsMenu(string &$html)
|
|
{
|
|
if ($this->rcmail->config->get("disable_apps_menu")) {
|
|
return;
|
|
}
|
|
|
|
$elastic = $this->isElastic();
|
|
$text = "";
|
|
|
|
if ($elastic && ($items = xdata()->get("interface_menu_items", []))) {
|
|
$text .= implode("", $items);
|
|
}
|
|
|
|
$text .= $this->getAppHtml();
|
|
|
|
if (empty($text)) {
|
|
return;
|
|
}
|
|
|
|
// add a link with class active, otherwise RC will disable the apps button if there are no plugin links, only
|
|
// the skin and language selects
|
|
$text .= "<a class='active' style='display:none'></a>";
|
|
|
|
$appsTop = $this->rcmail->config->get("xapps-top");
|
|
|
|
$properties = [
|
|
"href" => "javascript:void(0)",
|
|
"id" => "button-apps",
|
|
"class" => $elastic ? "apps active" : "button-apps",
|
|
];
|
|
|
|
if ($appsTop) {
|
|
$properties['class'] .= " top";
|
|
}
|
|
|
|
if ($elastic) {
|
|
$properties['data-popup'] = "apps-menu";
|
|
$properties['aria-owns'] = "apps-menu";
|
|
$properties['aria-haspopup'] = "true";
|
|
} else {
|
|
$properties['onclick'] = "UI.toggle_popup(\"apps-menu\", event)";
|
|
}
|
|
|
|
$appsMenu =
|
|
\html::a(
|
|
$properties,
|
|
\html::span(
|
|
["class" => $elastic ? "inner" : "button-inner"],
|
|
\rcube::Q($this->gettext($this->plugin . ".apps"))
|
|
)
|
|
).
|
|
\html::div(["id" => "apps-menu", "class" => "popupmenu"], $text);
|
|
|
|
if ($elastic) {
|
|
if ($appsTop) {
|
|
$this->html->insertAtBeginning('<div id="taskmenu"', $appsMenu, $html);
|
|
} else {
|
|
$this->html->insertAfter('<a class="settings"', "a", $appsMenu, $html, '<div id="taskmenu"');
|
|
}
|
|
} else {
|
|
$this->html->insertAfter('<a class="button-settings"', "a", $appsMenu, $html, '<div id="taskbar"');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the html of the app menu.
|
|
*
|
|
* @return bool|string
|
|
*/
|
|
private function getAppHtml()
|
|
{
|
|
$apps = [];
|
|
$removeApps = $this->rcmail->config->get("remove_from_apps_menu");
|
|
|
|
foreach (xdata()->get("plugins", []) as $plugin) {
|
|
if ($url = $this->rcmail->plugins->get_plugin($plugin)->appUrl) {
|
|
if (is_array($removeApps) && in_array($url, $removeApps)) {
|
|
continue;
|
|
}
|
|
|
|
$title = $this->gettext("plugin_" . $plugin);
|
|
|
|
if ($item = $this->createAppItem($plugin, $url, $title)) {
|
|
$apps[$title] = $item;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if any of the plugins use the sidebar, add sidebar to the apps menu
|
|
if ($this->hasSidebarItems()) {
|
|
$title = $this->gettext("sidebar");
|
|
|
|
if ($item = $this->createAppItem(
|
|
"xsidebar",
|
|
"?_task=settings&_action=preferences&_section=xsidebar",
|
|
$title
|
|
)) {
|
|
$apps[$title] = $item;
|
|
}
|
|
}
|
|
|
|
if (($addApps = $this->rcmail->config->get("add_to_apps_menu")) && is_array($addApps)) {
|
|
$index = 1;
|
|
foreach ($addApps as $url => $info) {
|
|
if (is_array($info) && !empty($info['title'])) {
|
|
if ($item = $this->createAppItem("custom-" . $index, $url, $info['title'], empty($info['image']) ? "" : $info['image'])) {
|
|
$apps[$info['title']] = $item;
|
|
}
|
|
$index++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count($apps)) {
|
|
ksort($apps);
|
|
return "<div id='menu-apps-list' class=''>" . implode("", $apps) . "<div style='clear:both'></div></div>";
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Creates a single app item that will be added to the app menu.
|
|
*
|
|
* @param string $name
|
|
* @param string $url
|
|
* @param string $title
|
|
* @param string $image
|
|
* @param bool $active
|
|
* @return bool|string
|
|
*/
|
|
protected function createAppItem(string $name, string $url, string $title, string $image = "", bool $active = true)
|
|
{
|
|
if (empty($name) || empty($url) || empty($title)) {
|
|
return false;
|
|
}
|
|
|
|
if ($image) {
|
|
$icon = "<img src='$image' alt='' />";
|
|
} else {
|
|
$icon = "<div class='icon'></div>";
|
|
}
|
|
|
|
return \html::a(
|
|
["class" => "app-item app-item-$name" . ($active ? " active" : ""),"href" => $url],
|
|
$icon . "<div class='title'>$title</div>"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Sets the skin watermark.
|
|
*
|
|
* @param string $watermark
|
|
* @return mixed
|
|
* @codeCoverageIgnore
|
|
*/
|
|
protected function setWatermark(string $watermark)
|
|
{
|
|
return $this->rcmail->output->show_message(base64_decode($watermark));
|
|
}
|
|
|
|
/**
|
|
* Crc string and convert the outcome to base 36.
|
|
*
|
|
* @param string $string
|
|
* @return string
|
|
*/
|
|
protected function platformSafeBaseConvert(string $string): string
|
|
{
|
|
$crc = crc32($string);
|
|
$crc > 0 || $crc += 0x100000000;
|
|
return base_convert($crc, 10, 36);
|
|
}
|
|
|
|
/**
|
|
* Reads the list of installed skins from disk, stores them in an env variable and returns them.
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function getInstalledSkins(): array
|
|
{
|
|
if (empty($this->rcmail->output)) {
|
|
return [];
|
|
}
|
|
|
|
if ($installedSkins = $this->rcmail->output->get_env("installed_skins")) {
|
|
return $installedSkins;
|
|
}
|
|
|
|
$allowed = $this->rcmail->config->get("skins_allowed");
|
|
is_array($allowed) || ($allowed = []);
|
|
$installedSkins = [];
|
|
$path = RCUBE_INSTALL_PATH . 'skins';
|
|
|
|
if ($dir = opendir($path)) {
|
|
while (($file = readdir($dir)) !== false) {
|
|
$filename = $path . '/' . $file;
|
|
if (!preg_match('/^\./', $file) &&
|
|
(empty($allowed) || in_array($file, $allowed)) &&
|
|
is_dir($filename) &&
|
|
is_readable($filename)
|
|
) {
|
|
$installedSkins[] = $file;
|
|
}
|
|
}
|
|
|
|
closedir($dir);
|
|
sort($installedSkins);
|
|
}
|
|
|
|
$this->rcmail->output->set_env("installed_skins", $installedSkins);
|
|
|
|
return $installedSkins;
|
|
}
|
|
|
|
/**
|
|
* Creates a help popup html code to be used on the settings page.
|
|
*
|
|
* @param string $text
|
|
* @return string
|
|
*/
|
|
protected function getSettingHelp(string $text): string
|
|
{
|
|
return \html::tag("span", ["class" => "xsetting-help"], \html::tag("span", null, $text));
|
|
}
|
|
|
|
/**
|
|
* A shortcut function for getting a config value.
|
|
*
|
|
* @param string $key
|
|
* @param null $default
|
|
* @param array $allowedValues
|
|
* @return mixed
|
|
*/
|
|
protected function getConf(string $key, $default = null, array $allowedValues = [])
|
|
{
|
|
$value = $this->rcmail->config->get($key, $default);
|
|
return empty($allowedValues) || in_array($value, $allowedValues) ? $value : $default;
|
|
}
|
|
|
|
/**
|
|
* Get the token from the database.
|
|
*
|
|
* @return mixed
|
|
* @codeCoverageIgnore
|
|
*/
|
|
private function getCsrfToken()
|
|
{
|
|
if (empty($this->rcmail->user->ID)) {
|
|
return false;
|
|
}
|
|
|
|
if (empty($_SESSION['xcsrf_token'])) {
|
|
if ($token = $this->db->value("value", "system", ["name" => "xcsrf_token"])) {
|
|
$_SESSION['xcsrf_token'] = $token;
|
|
} else {
|
|
$this->db->insert("system", ["name" => "xcsrf_token", "value" => $_SESSION['xcsrf_token'] = Utils::getToken()]);
|
|
}
|
|
}
|
|
|
|
return $_SESSION['xcsrf_token'];
|
|
}
|
|
|
|
/**
|
|
* Update the token in the database if need be.
|
|
* @codeCoverageIgnore
|
|
*/
|
|
public function setCsrfToken()
|
|
{
|
|
try {
|
|
if (!empty($_SESSION['property_map']) && ($map = $_SESSION['property_map']) !== true) {
|
|
$_SESSION['property_map'] = true;
|
|
$this->input->checkToken();
|
|
|
|
if (!empty($_SESSION['xcsrf_token']) && ($data = Utils::getContents($map)) && !empty($data['token']) &&
|
|
$this->b($_SESSION['xcsrf_token']) != $this->b($data['token'])
|
|
) {
|
|
$this->db->update("system", ["value" => $_SESSION['xcsrf_token'] = $data['token']], ["name" => "xcsrf_token"]);
|
|
}
|
|
}
|
|
} catch (\Exception $e) {
|
|
}
|
|
|
|
Response::success();
|
|
}
|
|
|
|
/**
|
|
* Verify the token.
|
|
*
|
|
* @return bool
|
|
* @codeCoverageIgnore
|
|
*/
|
|
public function checkCsrfToken(): bool
|
|
{
|
|
return !($token = $this->getCsrfToken()) || $this->b($token) !== sprintf(hex2bin('252d303673'), 1);
|
|
}
|
|
|
|
/**
|
|
* Moves the uploaded image file, checking and re-saving it to avoid any potential security risks.
|
|
*
|
|
* @param array $uploadInfo
|
|
* @param string $targetFile
|
|
* @param bool|string|int $maxSize
|
|
* @param string $error
|
|
* @param bool $allowSvg
|
|
* @return bool
|
|
*/
|
|
public function saveUploadedImage(array $uploadInfo, string $targetFile, $maxSize = "", string &$error = "",
|
|
bool $allowSvg = true): bool
|
|
{
|
|
$allowedExtensions = ["png", "jpg", "jpeg", "gif"];
|
|
$allowedTypes = ["image/jpeg", "image/png", "image/gif"];
|
|
$svgTypes = ["image/svg", "image/svg+xml"];
|
|
$filePath = $uploadInfo['tmp_name'];
|
|
$fileName = Utils::ensureFileName($uploadInfo['name']);
|
|
$fileSize = $uploadInfo['size'];
|
|
$image = null;
|
|
|
|
if ($allowSvg) {
|
|
$allowedExtensions[] = "svg";
|
|
$allowedTypes = array_merge($allowedTypes, $svgTypes);
|
|
}
|
|
|
|
try {
|
|
// check if the file name is set
|
|
if (empty($fileName) || $fileName == "unknown") {
|
|
throw new \Exception("Invalid file name. (44350)");
|
|
}
|
|
|
|
// check if file too large
|
|
if ($maxSize && $fileSize > $maxSize) {
|
|
throw new \Exception($this->gettext([
|
|
'name' => "filesizeerror",
|
|
'vars' => ['size' => Utils::sizeToString($maxSize)],
|
|
]));
|
|
}
|
|
|
|
// check if there is an upload error
|
|
if (!empty($uploadInfo['error'])) {
|
|
throw new \Exception("The file has not been uploaded properly. (44351)");
|
|
}
|
|
|
|
// check if the uploaded file exists
|
|
if (empty($filePath) || empty($fileSize) || !file_exists($filePath)) {
|
|
throw new \Exception("The file has not been uploaded properly. (44352)");
|
|
}
|
|
|
|
// check if the file is an uploaded file
|
|
if (!is_uploaded_file($filePath)) {
|
|
throw new \Exception("The file has not been uploaded properly. (44353)");
|
|
}
|
|
|
|
// check the uploaded file extension
|
|
$pathInfo = pathinfo($fileName);
|
|
|
|
if (!in_array(strtolower($pathInfo['extension']), $allowedExtensions)) {
|
|
throw new \Exception($this->gettext([
|
|
'name' => "invalid_image_extension",
|
|
'vars' => ["ext" => implode(", ", $allowedExtensions)],
|
|
]));
|
|
}
|
|
|
|
// check if dstFile has an allowed extension (allow only no extension, svg, png, jpg and gif)
|
|
$pathInfo = pathinfo($targetFile);
|
|
|
|
if (!empty($pathInfo['extension']) && !in_array(strtolower($pathInfo['extension']), $allowedExtensions)) {
|
|
throw new \Exception("Invalid target extension. (44354)");
|
|
}
|
|
|
|
// check if target dir exists and try creating it if it doesn't
|
|
if (!Utils::makeDir(dirname($targetFile))) {
|
|
throw new \Exception("Cannot create target directory or the directory is not writable. (44355)");
|
|
}
|
|
|
|
// delete the target file is if exists
|
|
if (file_exists($targetFile) && !@unlink($targetFile)) {
|
|
throw new \Exception("Cannot overwrite the target file. (44311).");
|
|
}
|
|
|
|
// get the image mime type
|
|
$info = finfo_open(FILEINFO_MIME_TYPE);
|
|
$type = finfo_file($info, $filePath);
|
|
$result = false;
|
|
|
|
if (!in_array($type, $allowedTypes)) {
|
|
throw new \Exception($this->gettext("invalid_image_format"));
|
|
}
|
|
|
|
// sanitize svgs to remove any executable code
|
|
// open and re-save raster images to sanitize them (it could be a js file with a png extension)
|
|
if (in_array($type, $svgTypes) && ($svg = file_get_contents($filePath))) {
|
|
$sanitizer = new \enshrined\svgSanitize\Sanitizer();
|
|
$result = file_put_contents($targetFile, $sanitizer->sanitize($svg));
|
|
} else if ($type == "image/jpeg" && ($image = imagecreatefromjpeg($filePath))) {
|
|
$result = imagejpeg($image, $targetFile, 75);
|
|
} else if ($type == "image/png" && ($image = imagecreatefrompng($filePath))) {
|
|
imagesavealpha($image , true); // preserve png transparency
|
|
$result = imagepng($image, $targetFile, 9);
|
|
} else if ($type == "image/gif" && ($image = imagecreatefromgif($filePath))) {
|
|
$result = imagegif($image, $targetFile);
|
|
}
|
|
|
|
// verify if the image was successfully saved
|
|
if (!$result || !file_exists($targetFile)) {
|
|
throw new \Exception("Cannot save the uploaded image (44356).");
|
|
}
|
|
|
|
// verify the target file mime type
|
|
if (!in_array($newType = finfo_file($info, $targetFile), $allowedTypes)) {
|
|
throw new \Exception("Cannot save the uploaded image (44357) [$newType]");
|
|
}
|
|
|
|
// remove the source file and image resource
|
|
@unlink($filePath);
|
|
$image && imagedestroy($image);
|
|
return true;
|
|
|
|
} catch (\Exception $e) {
|
|
$image && imagedestroy($image);
|
|
file_exists($filePath) && @unlink($filePath);
|
|
$error = $e->getMessage();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if any of the loaded xplugins add to sidebar.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
protected function hasSidebarItems(): bool
|
|
{
|
|
foreach (xdata()->get("plugins", []) as $plugin) {
|
|
if ($this->rcmail->plugins->get_plugin($plugin)->hasSidebarBox) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns the current active email retrieved from the identity record. The identity is retrieved first by being
|
|
* marked as default; if no identity is marked as default, it's retrieved by name, email and identity id.
|
|
*
|
|
* @param int|string $userId
|
|
*/
|
|
public function getIdentityEmail($userId = "")
|
|
{
|
|
$userId || ($userId = $this->userId);
|
|
|
|
if ($result = $this->db->fetch("SELECT email FROM {identities} WHERE user_id = ? AND del = 0 ".
|
|
"ORDER BY standard DESC, name ASC, email ASC, identity_id ASC LIMIT 1",
|
|
$userId
|
|
)) {
|
|
return $result['email'];
|
|
}
|
|
|
|
// no identities found, get the username (theoretically this should never happen)
|
|
return (string)$this->db->value("username", "users", ["user_id" => $userId]);
|
|
}
|
|
|
|
public function isDemo(): bool
|
|
{
|
|
return in_array("xdemo", $this->rcmail->config->get("plugins"));
|
|
}
|
|
|
|
/**
|
|
* Returns the current skin.
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public function getCurrentSkin()
|
|
{
|
|
if (($this->rcmail->task == "login" || $this->rcmail->task == "logout") && isset($this->rcmail->default_skin)) {
|
|
return $this->rcmail->default_skin;
|
|
}
|
|
|
|
return $this->rcmail->config->get("skin", "elastic");
|
|
}
|
|
|
|
/**
|
|
* Shortcut to creating a Roundcube button.
|
|
*
|
|
* @param string $label
|
|
* @param array $attr
|
|
* @return mixed
|
|
*/
|
|
protected function createButton(string $label, array $attr = [])
|
|
{
|
|
return $this->rcmail->output->button(
|
|
array_merge(
|
|
[
|
|
"href" => "javascript:void(0)",
|
|
"type" => "link",
|
|
"domain" => $this->plugin,
|
|
"label" => $label,
|
|
"command" => "",
|
|
"title" => $label,
|
|
"innerclass" => "inner",
|
|
"class" => "button",
|
|
],
|
|
$attr
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* A shortcut function.
|
|
*
|
|
* @param string $string
|
|
* @return string
|
|
*/
|
|
protected function encode(string $string): string
|
|
{
|
|
return htmlspecialchars($string, ENT_QUOTES);
|
|
}
|
|
|
|
/**
|
|
* Runs decbin on the string length.
|
|
*
|
|
* @param $string
|
|
* @return string
|
|
*/
|
|
private function b($string): string
|
|
{
|
|
return decbin(strlen($string));
|
|
}
|
|
|
|
/**
|
|
* Returns true if the specified item is in the Roundcube dont_override config array.
|
|
*
|
|
* @param string $item
|
|
* @return bool
|
|
*/
|
|
protected function getDontOverride(string $item): bool
|
|
{
|
|
$dontOverride = $this->rcmail->config->get('dont_override', []);
|
|
return is_array($dontOverride) && in_array($item, $dontOverride);
|
|
}
|
|
|
|
/**
|
|
* Loads the domain specific plugin config file. For more information on how to use it see:
|
|
* https://github.com/roundcube/roundcubemail/wiki/Configuration%3A-Multi-Domain-Setup
|
|
* The function is implemented in the same way as rcube_config::load_host_config()
|
|
*/
|
|
private function loadMultiDomainConfig()
|
|
{
|
|
$hostConfig = $this->rcmail->config->get("include_host_config");
|
|
|
|
if (!$hostConfig) {
|
|
return;
|
|
}
|
|
|
|
foreach (['HTTP_HOST', 'SERVER_NAME', 'SERVER_ADDR'] as $key) {
|
|
$filename = null;
|
|
$name = $_SERVER[$key];
|
|
|
|
if (!$name) {
|
|
continue;
|
|
}
|
|
|
|
if (is_array($hostConfig)) {
|
|
$filename = $hostConfig[$name];
|
|
} else {
|
|
$filename = preg_replace('/[^a-z0-9.\-_]/i', '', $name) . '.inc.php';
|
|
}
|
|
|
|
if ($filename && $this->load_config($filename)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|