From 7312b2854b47c04d2d2c1c5c80f66b4b2580146b Mon Sep 17 00:00:00 2001 From: RainLoop Team Date: Tue, 8 Sep 2015 00:15:39 +0300 Subject: [PATCH] Added: Plugin: Contacts suggestion from ldap Added: Custom favicon setting + Small fixes --- build/plugin.xml | 10 + dev/Settings/Admin/Branding.js | 12 +- package.json | 6 +- .../ChangePasswordLdapDriver.php | 26 +- plugins/ldap-change-password/VERSION | 2 +- plugins/ldap-change-password/index.php | 10 +- plugins/ldap-contacts-suggestions/LICENSE | 20 ++ .../LdapContactsSuggestions.php | 317 ++++++++++++++++++ plugins/ldap-contacts-suggestions/VERSION | 1 + plugins/ldap-contacts-suggestions/index.php | 92 +++++ .../0.0.0/app/libraries/MailSo/Base/Utils.php | 7 + .../0.0.0/app/libraries/RainLoop/Actions.php | 7 +- .../libraries/RainLoop/Config/Application.php | 1 + .../RainLoop/Providers/Suggestions.php | 3 +- .../0.0.0/app/libraries/RainLoop/Service.php | 11 +- .../v/0.0.0/app/libraries/RainLoop/Utils.php | 2 +- rainloop/v/0.0.0/app/templates/Index.html | 3 +- .../Views/Admin/AdminSettingsBranding.html | 14 + rainloop/v/0.0.0/langs/admin/de.ini | 1 + rainloop/v/0.0.0/langs/admin/en.ini | 1 + rainloop/v/0.0.0/langs/admin/it.ini | 1 + rainloop/v/0.0.0/langs/admin/nl.ini | 1 + rainloop/v/0.0.0/langs/admin/pl.ini | 1 + rainloop/v/0.0.0/langs/admin/pt-br.ini | 1 + rainloop/v/0.0.0/langs/admin/ru.ini | 1 + 25 files changed, 526 insertions(+), 25 deletions(-) create mode 100644 plugins/ldap-contacts-suggestions/LICENSE create mode 100644 plugins/ldap-contacts-suggestions/LdapContactsSuggestions.php create mode 100644 plugins/ldap-contacts-suggestions/VERSION create mode 100644 plugins/ldap-contacts-suggestions/index.php diff --git a/build/plugin.xml b/build/plugin.xml index 8afe74ea8..d3d1721d0 100644 --- a/build/plugin.xml +++ b/build/plugin.xml @@ -78,6 +78,16 @@ + + + + + + + + + + diff --git a/dev/Settings/Admin/Branding.js b/dev/Settings/Admin/Branding.js index 0ce884e59..496c68253 100644 --- a/dev/Settings/Admin/Branding.js +++ b/dev/Settings/Admin/Branding.js @@ -30,6 +30,9 @@ this.loadingDesc = ko.observable(Settings.settingsGet('LoadingDescription')); this.loadingDesc.trigger = ko.observable(Enums.SaveSettingsStep.Idle); + this.faviconUrl = ko.observable(Settings.settingsGet('FaviconUrl')); + this.faviconUrl.trigger = ko.observable(Enums.SaveSettingsStep.Idle); + this.loginLogo = ko.observable(Settings.settingsGet('LoginLogo') || ''); this.loginLogo.trigger = ko.observable(Enums.SaveSettingsStep.Idle); @@ -88,7 +91,8 @@ var f1 = Utils.settingsSaveHelperSimpleFunction(self.title.trigger, self), - f2 = Utils.settingsSaveHelperSimpleFunction(self.loadingDesc.trigger, self) + f2 = Utils.settingsSaveHelperSimpleFunction(self.loadingDesc.trigger, self), + f3 = Utils.settingsSaveHelperSimpleFunction(self.faviconUrl.trigger, self) ; self.title.subscribe(function (sValue) { @@ -103,6 +107,12 @@ }); }); + self.faviconUrl.subscribe(function (sValue) { + Remote.saveAdminConfig(f3, { + 'FaviconUrl': Utils.trim(sValue) + }); + }); + }, 50); }; diff --git a/package.json b/package.json index 9ccaa1ce2..f2f158180 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "RainLoop", "title": "RainLoop Webmail", "version": "1.9.2", - "release": "357", + "release": "361", "description": "Simple, modern & fast web-based email client", "homepage": "http://rainloop.net", "main": "gulpfile.js", @@ -40,7 +40,7 @@ "plugins" ], "readmeFilename": "README.md", - "ownCloudPackageVersion": "4.6", + "ownCloudPackageVersion": "4.7", "engines": { "node": ">= 0.10.0" }, @@ -49,7 +49,7 @@ "rimraf": "*", "jshint-summary": "*", "webpack": "*", - "gulp": "*", + "gulp": "~3.9.0", "gulp-util": "*", "gulp-uglify": "*", "gulp-rimraf": "*", diff --git a/plugins/ldap-change-password/ChangePasswordLdapDriver.php b/plugins/ldap-change-password/ChangePasswordLdapDriver.php index 5b266f289..d686036df 100644 --- a/plugins/ldap-change-password/ChangePasswordLdapDriver.php +++ b/plugins/ldap-change-password/ChangePasswordLdapDriver.php @@ -7,6 +7,11 @@ class ChangePasswordLdapDriver implements \RainLoop\Providers\ChangePassword\Cha */ private $sHostName = '127.0.0.1'; + /** + * @var int + */ + private $iHostPort = 389; + /** * @var string */ @@ -34,15 +39,17 @@ class ChangePasswordLdapDriver implements \RainLoop\Providers\ChangePassword\Cha /** * @param string $sHostName + * @param int $iHostPort * @param string $sUserDnFormat * @param string $sPasswordField * @param string $sPasswordEncType * * @return \ChangePasswordLdapDriver */ - public function SetConfig($sHostName, $sUserDnFormat, $sPasswordField, $sPasswordEncType) + public function SetConfig($sHostName, $iHostPort, $sUserDnFormat, $sPasswordField, $sPasswordEncType) { $this->sHostName = $sHostName; + $this->iHostPort = $iHostPort; $this->sUserDnFormat = $sUserDnFormat; $this->sPasswordField = $sPasswordField; $this->sPasswordEncType = $sPasswordEncType; @@ -114,7 +121,7 @@ class ChangePasswordLdapDriver implements \RainLoop\Providers\ChangePassword\Cha '{imap:port}' => $oAccount->DomainIncPort() )); - $oCon = @\ldap_connect($this->sHostName); + $oCon = @\ldap_connect($this->sHostName, $this->iHostPort); if ($oCon) { @\ldap_set_option($oCon, LDAP_OPT_PROTOCOL_VERSION, 3); @@ -133,22 +140,31 @@ class ChangePasswordLdapDriver implements \RainLoop\Providers\ChangePassword\Cha return false; } } + else + { + return false; + } + $sSshaSalt = ''; + $sShaPrefix = '{SHA}'; $sEncodedNewPassword = $sNewPassword; switch (\strtolower($this->sPasswordEncType)) { + case 'ssha': + $sSshaSalt = $this->getSalt(4); + $sShaPrefix = '{SSHA}'; case 'sha': switch (true) { default: case \function_exists('sha1'): - $sEncodedNewPassword = '{SHA}'.\base64_encode(\pack('H*', \sha1($sNewPassword))); + $sEncodedNewPassword = $sShaPrefix.\base64_encode(\sha1($sNewPassword.$sSshaSalt, true).$sSshaSalt); break; case \function_exists('hash'): - $sEncodedNewPassword = '{SHA}'.\base64_encode(\hash('sha1', $sNewPassword, true)); + $sEncodedNewPassword = $sShaPrefix.\base64_encode(\hash('sha1', $sNewPassword, true).$sSshaSalt); break; case \function_exists('mhash') && defined('MHASH_SHA1'): - $sEncodedNewPassword = '{SHA}'.\base64_encode(\mhash(MHASH_SHA1, $sNewPassword)); + $sEncodedNewPassword = $sShaPrefix.\base64_encode(\mhash(MHASH_SHA1, $sNewPassword).$sSshaSalt); break; } break; diff --git a/plugins/ldap-change-password/VERSION b/plugins/ldap-change-password/VERSION index 9f8e9b69a..b123147e2 100644 --- a/plugins/ldap-change-password/VERSION +++ b/plugins/ldap-change-password/VERSION @@ -1 +1 @@ -1.0 \ No newline at end of file +1.1 \ No newline at end of file diff --git a/plugins/ldap-change-password/index.php b/plugins/ldap-change-password/index.php index 639a6ed03..abfe7d37c 100644 --- a/plugins/ldap-change-password/index.php +++ b/plugins/ldap-change-password/index.php @@ -31,18 +31,19 @@ class LdapChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin case 'change-password': $sHostName = \trim($this->Config()->Get('plugin', 'hostname', '')); + $iHostPort = (int) $this->Config()->Get('plugin', 'port', 389); $sUserDnFormat = \trim($this->Config()->Get('plugin', 'user_dn_format', '')); $sPasswordField = \trim($this->Config()->Get('plugin', 'password_field', '')); $sPasswordEncType = \trim($this->Config()->Get('plugin', 'password_enc_type', '')); - if (!empty($sHostName) && !empty($sUserDnFormat) && !empty($sPasswordField) && !empty($sPasswordEncType)) + if (!empty($sHostName) && 0 < $iHostPort && !empty($sUserDnFormat) && !empty($sPasswordField) && !empty($sPasswordEncType)) { include_once __DIR__.'/ChangePasswordLdapDriver.php'; $oProvider = new \ChangePasswordLdapDriver(); $oProvider - ->SetConfig($sHostName, $sUserDnFormat, $sPasswordField, $sPasswordEncType) + ->SetConfig($sHostName, $iHostPort, $sUserDnFormat, $sPasswordField, $sPasswordEncType) ->SetAllowedEmails(\strtolower(\trim($this->Config()->Get('plugin', 'allowed_emails', '')))) ->SetLogger($this->Manager()->Actions()->Logger()) ; @@ -59,6 +60,9 @@ class LdapChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin return array( \RainLoop\Plugins\Property::NewInstance('hostname')->SetLabel('LDAP hostname') ->SetDefaultValue('127.0.0.1'), + \RainLoop\Plugins\Property::NewInstance('port')->SetLabel('LDAP port') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::INT) + ->SetDefaultValue(389), \RainLoop\Plugins\Property::NewInstance('user_dn_format')->SetLabel('User DN format') ->SetDescription('LDAP user dn format. Supported tokens: {email}, {login}, {domain}, {domain:dc}, {imap:login}, {imap:host}, {imap:port}') ->SetDefaultValue('uid={imap:login},ou=Users,{domain:dc}'), @@ -66,7 +70,7 @@ class LdapChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin ->SetDefaultValue('userPassword'), \RainLoop\Plugins\Property::NewInstance('password_enc_type')->SetLabel('Encryption type') ->SetType(\RainLoop\Enumerations\PluginPropertyType::SELECTION) - ->SetDefaultValue(array('SHA', 'MD5', 'Crypt', 'Clear')), + ->SetDefaultValue(array('SHA', 'SSHA', 'MD5', 'Crypt', 'Clear')), \RainLoop\Plugins\Property::NewInstance('allowed_emails')->SetLabel('Allowed emails') ->SetDescription('Allowed emails, space as delimiter, wildcard supported. Example: user1@domain1.net user2@domain1.net *@domain2.net') ->SetDefaultValue('*') diff --git a/plugins/ldap-contacts-suggestions/LICENSE b/plugins/ldap-contacts-suggestions/LICENSE new file mode 100644 index 000000000..4aed64b3a --- /dev/null +++ b/plugins/ldap-contacts-suggestions/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2015 RainLoop Team + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/ldap-contacts-suggestions/LdapContactsSuggestions.php b/plugins/ldap-contacts-suggestions/LdapContactsSuggestions.php new file mode 100644 index 000000000..9eb538678 --- /dev/null +++ b/plugins/ldap-contacts-suggestions/LdapContactsSuggestions.php @@ -0,0 +1,317 @@ +sHostName = $sHostName; + $this->iHostPort = $iHostPort; + $this->sAccessDn = $sAccessDn; + $this->sAccessPassword = $sAccessPassword; + $this->sUsersDn = $sUsersDn; + $this->sObjectClass = $sObjectClass; + $this->sNameField = $sNameField; + $this->sEmailField = $sEmailField; + + return $this; + } + + /** + * @param string $sAllowedEmails + * + * @return \LdapContactsSuggestions + */ + public function SetAllowedEmails($sAllowedEmails) + { + $this->sAllowedEmails = $sAllowedEmails; + + return $this; + } + + /** + * @param \RainLoop\Model\Account $oAccount + * @param string $sQuery + * @param int $iLimit = 20 + * + * @return array + */ + public function Process($oAccount, $sQuery, $iLimit = 20) + { + $sQuery = \trim($sQuery); + + if (2 > \strlen($sQuery)) + { + return array(); + } + else if (!$oAccount || !\RainLoop\Plugins\Helper::ValidateWildcardValues($oAccount->Email(), $this->sAllowedEmails)) + { + return array(); + } + + $aResult = $this->ldapSearch($oAccount, $sQuery); + + $aResult = \RainLoop\Utils::RemoveSuggestionDuplicates($aResult); + if ($iLimit < \count($aResult)) + { + $aResult = \array_slice($aResult, 0, $iLimit); + } + + return $aResult; + } + + /** + * @param array $aLdapItem + * @param array $aEmailFields + * @param array $aNameFields + * + * @return array + */ + private function findNameAndEmail($aLdapItem, $aEmailFields, $aNameFields) + { + $sEmail = $sName = ''; + if ($aLdapItem) + { + foreach ($aEmailFields as $sField) + { + if (!empty($aLdapItem[$sField][0])) + { + $sEmail = \trim($aLdapItem[$sField][0]); + if (!empty($sEmail)) + { + break; + } + } + } + + foreach ($aNameFields as $sField) + { + if (!empty($aLdapItem[$sField][0])) + { + $sName = \trim($aLdapItem[$sField][0]); + if (!empty($sName)) + { + break; + } + } + } + } + + return array($sEmail, $sName); + } + + /** + * @param \RainLoop\Model\Account $oAccount + * @param string $sQuery + * + * @return array + */ + private function ldapSearch($oAccount, $sQuery) + { + $sSearchEscaped = $this->escape($sQuery); + + $aResult = array(); + $oCon = @\ldap_connect($this->sHostName, $this->iHostPort); + if ($oCon) + { + $this->oLogger->Write('ldap_connect: connected', \MailSo\Log\Enumerations\Type::INFO, 'LDAP'); + + @\ldap_set_option($oCon, LDAP_OPT_PROTOCOL_VERSION, 3); + + if (!@\ldap_bind($oCon, $this->sAccessDn, $this->sAccessPassword)) + { + $this->logLdapError($oCon, 'ldap_bind'); + return $aResult; + } + + $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email()); + $sSearchDn = \strtr($this->sUsersDn, array( + '{domain}' => $sDomain, + '{domain:dc}' => 'dc='.\strtr($sDomain, array('.' => ',dc=')), + '{email}' => $oAccount->Email(), + '{email:user}' => \MailSo\Base\Utils::GetAccountNameFromEmail($oAccount->Email()), + '{email:domain}' => $sDomain, + '{login}' => $oAccount->Login(), + '{imap:login}' => $oAccount->Login(), + '{imap:host}' => $oAccount->DomainIncHost(), + '{imap:port}' => $oAccount->DomainIncPort() + )); + + $aEmails = empty($this->sEmailField) ? array() : \explode(',', $this->sEmailField); + $aNames = empty($this->sNameField) ? array() : \explode(',', $this->sNameField); + + $aEmails = \array_map('trim', $aEmails); + $aNames = \array_map('trim', $aNames); + + $aFields = \array_merge($aEmails, $aNames); + + $aItems = array(); + $sSubFilter = ''; + foreach ($aFields as $sItem) + { + if (!empty($sItem)) + { + $aItems[] = $sItem; + $sSubFilter .= '('.$sItem.'=*'.$sSearchEscaped.'*)'; + } + } + + $sFilter = '(&(objectclass='.$this->sObjectClass.')'; + $sFilter .= (1 < count($aItems) ? '(|' : '').$sSubFilter.(1 < count($aItems) ? ')' : ''); + $sFilter .= ')'; + + $this->oLogger->Write('ldap_search: start: '.$sSearchDn.' / '.$sFilter, \MailSo\Log\Enumerations\Type::INFO, 'LDAP'); + $oS = @\ldap_search($oCon, $sSearchDn, $sFilter, $aItems, 0, 30, 30); + if ($oS) + { + $aEntries = @\ldap_get_entries($oCon, $oS); + if (is_array($aEntries)) + { + if (isset($aEntries['count'])) + { + unset($aEntries['count']); + } + + foreach ($aEntries as $aItem) + { + if ($aItem) + { + $sName = $sEmail = ''; + list ($sEmail, $sName) = $this->findNameAndEmail($aItem, $aEmails, $aNames); + if (!empty($sEmail)) + { + $aResult[] = array($sEmail, $sName); + } + } + } + } + else + { + $this->logLdapError($oCon, 'ldap_get_entries'); + } + } + else + { + $this->logLdapError($oCon, 'ldap_search'); + } + } + else + { + return $aResult; + } + + return $aResult; + } + + /** + * @param string $sStr + * + * @return string + */ + public function escape($sStr) + { + $aNewChars = array(); + $aChars = array('\\', '*', '(', ')', \chr(0)); + + foreach ($aChars as $iIndex => $sValue) + { + $aNewChars[$iIndex] = '\\'.\str_pad(\dechex(\ord($sValue)), 2, '0'); + } + + return \str_replace($aChars, $aNewChars, $sStr); + } + + /** + * @param mixed $oCon + * @param string $sCmd + * + * @return string + */ + public function logLdapError($oCon, $sCmd) + { + if ($this->oLogger) + { + $sError = $oCon ? @\ldap_error($oCon) : ''; + $iErrno = $oCon ? @\ldap_errno($oCon) : 0; + + $this->oLogger->Write($sCmd.' error: '.$sError.' ('.$iErrno.')', + \MailSo\Log\Enumerations\Type::WARNING, 'LDAP'); + } + } + + /** + * @param \MailSo\Log\Logger $oLogger + * + * @return \LdapContactsSuggestions + */ + public function SetLogger($oLogger) + { + if ($oLogger instanceof \MailSo\Log\Logger) + { + $this->oLogger = $oLogger; + } + + return $this; + } +} diff --git a/plugins/ldap-contacts-suggestions/VERSION b/plugins/ldap-contacts-suggestions/VERSION new file mode 100644 index 000000000..9f8e9b69a --- /dev/null +++ b/plugins/ldap-contacts-suggestions/VERSION @@ -0,0 +1 @@ +1.0 \ No newline at end of file diff --git a/plugins/ldap-contacts-suggestions/index.php b/plugins/ldap-contacts-suggestions/index.php new file mode 100644 index 000000000..7e77f2790 --- /dev/null +++ b/plugins/ldap-contacts-suggestions/index.php @@ -0,0 +1,92 @@ +addHook('main.fabrica', 'MainFabrica'); + } + + /** + * @return string + */ + public function Supported() + { + if (!\function_exists('ldap_connect')) + { + return 'The LDAP PHP exention must be installed to use this plugin'; + } + + return ''; + } + + /** + * @param string $sName + * @param mixed $mResult + */ + public function MainFabrica($sName, &$mResult) + { + switch ($sName) + { + case 'suggestions': + + if (!\is_array($mResult)) + { + $mResult = array(); + } + + $sHostName = \trim($this->Config()->Get('plugin', 'hostname', '')); + $iHostPort = (int) $this->Config()->Get('plugin', 'port', 389); + $sAccessDn = \trim($this->Config()->Get('plugin', 'access_dn', '')); + $sAccessPassword = \trim($this->Config()->Get('plugin', 'access_password', '')); + $sUsersDn = \trim($this->Config()->Get('plugin', 'users_dn_format', '')); + $sObjectClass = \trim($this->Config()->Get('plugin', 'object_class', '')); + $sNameField = \trim($this->Config()->Get('plugin', 'name_field', '')); + $sEmailField = \trim($this->Config()->Get('plugin', 'mail_field', '')); + + if (0 < \strlen($sAccessDn) && 0 < \strlen($sAccessPassword) && 0 < \strlen($sUsersDn) && + 0 < \strlen($sObjectClass) && 0 < \strlen($sEmailField)) + { + include_once __DIR__.'/LdapContactsSuggestions.php'; + + $oProvider = new LdapContactsSuggestions(); + $oProvider->SetConfig($sHostName, $iHostPort, $sAccessDn, $sAccessPassword, $sUsersDn, $sObjectClass, $sNameField, $sEmailField); + + $mResult[] = $oProvider; + } + + break; + } + } + + /** + * @return array + */ + public function configMapping() + { + return array( + \RainLoop\Plugins\Property::NewInstance('hostname')->SetLabel('LDAP hostname') + ->SetDefaultValue('127.0.0.1'), + \RainLoop\Plugins\Property::NewInstance('port')->SetLabel('LDAP port') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::INT) + ->SetDefaultValue(389), + \RainLoop\Plugins\Property::NewInstance('access_dn')->SetLabel('Access dn (login)') + ->SetDefaultValue(''), + \RainLoop\Plugins\Property::NewInstance('access_password')->SetLabel('Access password') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::PASSWORD) + ->SetDefaultValue(''), + \RainLoop\Plugins\Property::NewInstance('users_dn_format')->SetLabel('Users DN format') + ->SetDescription('LDAP users dn format. Supported tokens: {email}, {login}, {domain}, {domain:dc}, {imap:login}, {imap:host}, {imap:port}') + ->SetDefaultValue('ou=People,dc=domain,dc=com'), + \RainLoop\Plugins\Property::NewInstance('object_class')->SetLabel('objectClass value') + ->SetDefaultValue('inetOrgPerson'), + \RainLoop\Plugins\Property::NewInstance('name_field')->SetLabel('Name field') + ->SetDefaultValue('givenname'), + \RainLoop\Plugins\Property::NewInstance('mail_field')->SetLabel('Mail field') + ->SetDefaultValue('mail'), + \RainLoop\Plugins\Property::NewInstance('allowed_emails')->SetLabel('Allowed emails') + ->SetDescription('Allowed emails, space as delimiter, wildcard supported. Example: user1@domain1.net user2@domain1.net *@domain2.net') + ->SetDefaultValue('*') + ); + } +} \ No newline at end of file diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Base/Utils.php b/rainloop/v/0.0.0/app/libraries/MailSo/Base/Utils.php index 993b4c061..066d6529a 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Base/Utils.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Base/Utils.php @@ -175,6 +175,11 @@ END; public static function NormalizeCharset($sEncoding, $bAsciAsUtf8 = false) { $sEncoding = \strtolower($sEncoding); + + $sEncoding = \preg_replace('/^iso8/', 'iso-8', $sEncoding); + $sEncoding = \preg_replace('/^cp-([\d])/', 'cp$1', $sEncoding); + $sEncoding = \preg_replace('/^windows?12/', 'windows-12', $sEncoding); + switch ($sEncoding) { case 'asci': @@ -185,6 +190,8 @@ END; \MailSo\Base\Enumerations\Charset::ISO_8859_1; break; case 'unicode-1-1-utf-7': + case 'unicode-1-utf-7': + case 'unicode-utf-7': $sEncoding = \MailSo\Base\Enumerations\Charset::UTF_7; break; case 'utf8': diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php index 6c9f548d0..f75ebd478 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -1378,6 +1378,7 @@ class Actions 'Title' => 'RainLoop Webmail', 'LoadingDescription' => 'RainLoop', 'LoadingDescriptionEsc' => 'RainLoop', + 'FaviconUrl' => '', 'LoginDescription' => '', 'LoginPowered' => true, 'LoginLogo' => '', @@ -1472,6 +1473,7 @@ class Actions $aResult['Title'] = $oConfig->Get('webmail', 'title', ''); $aResult['LoadingDescription'] = $oConfig->Get('webmail', 'loading_description', ''); + $aResult['FaviconUrl'] = $oConfig->Get('webmail', 'favicon_url', ''); if ($oPremProvider) { @@ -3686,6 +3688,7 @@ class Actions $this->setConfigFromParams($oConfig, 'Title', 'webmail', 'title', 'string'); $this->setConfigFromParams($oConfig, 'LoadingDescription', 'webmail', 'loading_description', 'string'); + $this->setConfigFromParams($oConfig, 'FaviconUrl', 'webmail', 'favicon_url', 'string'); $this->setConfigFromParams($oConfig, 'TokenProtection', 'security', 'csrf_protection', 'bool'); $this->setConfigFromParams($oConfig, 'EnabledPlugins', 'plugins', 'enable', 'bool'); @@ -7030,7 +7033,7 @@ class Actions } - $aResult = \RainLoop\Utils::RemoveSuggestionsdDuplicates($aResult); + $aResult = \RainLoop\Utils::RemoveSuggestionDuplicates($aResult); if ($iLimit < \count($aResult)) { $aResult = \array_slice($aResult, 0, $iLimit); @@ -7038,7 +7041,7 @@ class Actions $this->Plugins()->RunHook('ajax.suggestions-post', array(&$aResult, $sQuery, $oAccount, $iLimit)); - $aResult = \RainLoop\Utils::RemoveSuggestionsdDuplicates($aResult); + $aResult = \RainLoop\Utils::RemoveSuggestionDuplicates($aResult); if ($iLimit < \count($aResult)) { $aResult = \array_slice($aResult, 0, $iLimit); diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php index 19839bb5d..7e77a95a2 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php @@ -61,6 +61,7 @@ class Application extends \RainLoop\Config\AbstractConfig 'title' => array('RainLoop Webmail', 'Text displayed as page title'), 'loading_description' => array('RainLoop', 'Text displayed on startup'), + 'favicon_url' => array('', ''), 'theme' => array('Default', 'Theme used by default'), 'allow_themes' => array(true, 'Allow theme selection on settings screen'), diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Suggestions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Suggestions.php index 6268a0893..6a5d020f2 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Suggestions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Suggestions.php @@ -35,7 +35,6 @@ class Suggestions extends \RainLoop\Providers\AbstractProvider */ public function Process($oAccount, $sQuery, $iLimit = 20) { - $aSuggestions = array(); if ($oAccount instanceof \RainLoop\Model\Account && $this->IsActive() && \is_array($this->aDrivers) && 0 < \strlen($sQuery)) @@ -54,7 +53,7 @@ class Suggestions extends \RainLoop\Providers\AbstractProvider } } - $aResult = \RainLoop\Utils::RemoveSuggestionsdDuplicates($aSuggestions); + $aResult = \RainLoop\Utils::RemoveSuggestionDuplicates($aSuggestions); if ($iLimit < \count($aResult)) { diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php index 903e615c1..0ec0bc0a0 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php @@ -235,14 +235,15 @@ class Service $bAppJsDebug = !!$this->oActions->Config()->Get('labs', 'use_app_debug_js', false); $bAppCssDebug = !!$this->oActions->Config()->Get('labs', 'use_app_debug_css', false); + $sFaviconUrl = (string) $this->oActions->Config()->Get('webmail', 'favicon_url', ''); + $sStaticPrefix = \RainLoop\Utils::WebStaticPath(); $aData = array( 'Language' => $sLanguage, 'Theme' => $sTheme, - 'FaviconIcoLink' => $sStaticPrefix.'favicon.ico', - 'FaviconPngLink' => $sStaticPrefix.'favicon.png', - 'AppleTouchLink' => $sStaticPrefix.'apple-touch-icon.png', + 'FaviconPngLink' => $sFaviconUrl ? $sFaviconUrl : $sStaticPrefix.'favicon.png', + 'AppleTouchLink' => $sFaviconUrl ? '' : $sStaticPrefix.'apple-touch-icon.png', 'AppCssLink' => $sStaticPrefix.'css/app'.($bAppCssDebug ? '' : '.min').'.css', 'BootJsLink' => $sStaticPrefix.'js/min/boot.js', 'ComponentsJsLink' => $sStaticPrefix.'js/'.($bAppJsDebug ? '' : 'min/').'components.js', @@ -255,8 +256,8 @@ class Service $aTemplateParameters = array( '{{BaseAppDataScriptLink}}' => ($bAdmin ? './?/AdminAppData/' : './?/AppData/'), - '{{BaseAppFaviconIcoFile}}' => $aData['FaviconIcoLink'], - '{{BaseAppFaviconPngFile}}' => $aData['FaviconPngLink'], + '{{BaseAppFaviconPngLinkTag}}' => $aData['FaviconPngLink'] ? '' : '', + '{{BaseAppFaviconTouchLinkTag}}' => $aData['AppleTouchLink'] ? '' : '', '{{BaseAppAppleTouchFile}}' => $aData['AppleTouchLink'], '{{BaseAppMainCssLink}}' => $aData['AppCssLink'], '{{BaseAppBootScriptLink}}' => $aData['BootJsLink'], diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Utils.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Utils.php index fbab59fe2..a67d88ca9 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Utils.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Utils.php @@ -613,7 +613,7 @@ class Utils * * @return array */ - public static function RemoveSuggestionsdDuplicates($aSuggestions) + public static function RemoveSuggestionDuplicates($aSuggestions) { $aResult = array(); diff --git a/rainloop/v/0.0.0/app/templates/Index.html b/rainloop/v/0.0.0/app/templates/Index.html index 09aae0104..db16ceb74 100644 --- a/rainloop/v/0.0.0/app/templates/Index.html +++ b/rainloop/v/0.0.0/app/templates/Index.html @@ -22,8 +22,7 @@ - - + {{BaseAppFaviconPngLinkTag}}{{BaseAppFaviconTouchLinkTag}} diff --git a/rainloop/v/0.0.0/app/templates/Views/Admin/AdminSettingsBranding.html b/rainloop/v/0.0.0/app/templates/Views/Admin/AdminSettingsBranding.html index 23d724312..8f6c90368 100644 --- a/rainloop/v/0.0.0/app/templates/Views/Admin/AdminSettingsBranding.html +++ b/rainloop/v/0.0.0/app/templates/Views/Admin/AdminSettingsBranding.html @@ -27,6 +27,20 @@ }"> +
+ +
+
+
+