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 @@
}">
+
diff --git a/rainloop/v/0.0.0/langs/admin/de.ini b/rainloop/v/0.0.0/langs/admin/de.ini
index 501891c83..eed9ce10a 100644
--- a/rainloop/v/0.0.0/langs/admin/de.ini
+++ b/rainloop/v/0.0.0/langs/admin/de.ini
@@ -53,6 +53,7 @@ LABEL_DETERMINE_USER_LANGUAGE = "Versuche Sprache des Benutzers zu ermitteln"
LEGEND_BRANDING = "Branding"
LABEL_PAGE_TITLE = "Seitentitel"
LABEL_LOADING_DESCRIPTION = "Ladevorgang-Beschreibung"
+LABEL_FAVICON_URL = "Favicon"
LEGEND_LOGIN = "Anmeldung"
LABEL_LOGIN_LOGO = "Logo"
LABEL_LOGIN_DESCRIPTION = "Beschreibung"
diff --git a/rainloop/v/0.0.0/langs/admin/en.ini b/rainloop/v/0.0.0/langs/admin/en.ini
index 0485dbea1..c9771fe2d 100644
--- a/rainloop/v/0.0.0/langs/admin/en.ini
+++ b/rainloop/v/0.0.0/langs/admin/en.ini
@@ -54,6 +54,7 @@ LABEL_DETERMINE_USER_LANGUAGE = "Try to determine user language"
LEGEND_BRANDING = "Branding"
LABEL_PAGE_TITLE = "Page Title"
LABEL_LOADING_DESCRIPTION = "Loading Description"
+LABEL_FAVICON_URL = "Favicon"
LEGEND_LOGIN = "Login"
LABEL_LOGIN_LOGO = "Logo"
LABEL_LOGIN_DESCRIPTION = "Description"
diff --git a/rainloop/v/0.0.0/langs/admin/it.ini b/rainloop/v/0.0.0/langs/admin/it.ini
index ea1bdfe63..bd8661141 100644
--- a/rainloop/v/0.0.0/langs/admin/it.ini
+++ b/rainloop/v/0.0.0/langs/admin/it.ini
@@ -54,6 +54,7 @@ LABEL_DETERMINE_USER_LANGUAGE = "Prova a determinare la lingua dell'utente"
LEGEND_BRANDING = "Personalizzazione"
LABEL_PAGE_TITLE = "Titolo della pagina"
LABEL_LOADING_DESCRIPTION = "Messaggio di caricamento"
+LABEL_FAVICON_URL = "Favicon"
LEGEND_LOGIN = "Pagina di accesso"
LABEL_LOGIN_LOGO = "Logo"
LABEL_LOGIN_DESCRIPTION = "Descrizione"
diff --git a/rainloop/v/0.0.0/langs/admin/nl.ini b/rainloop/v/0.0.0/langs/admin/nl.ini
index 11d86e5ec..39671361d 100644
--- a/rainloop/v/0.0.0/langs/admin/nl.ini
+++ b/rainloop/v/0.0.0/langs/admin/nl.ini
@@ -52,6 +52,7 @@ LABEL_DETERMINE_USER_LANGUAGE = "Probeer gebruiker taal te bepalen"
LEGEND_BRANDING = "Huisstijl"
LABEL_PAGE_TITLE = "Pagina titel"
LABEL_LOADING_DESCRIPTION = "Beschrijving tijdens laden"
+LABEL_FAVICON_URL = "Favicon"
LEGEND_LOGIN = "Login scherm"
LABEL_LOGIN_LOGO = "Logo"
LABEL_LOGIN_DESCRIPTION = "Beschrijving"
diff --git a/rainloop/v/0.0.0/langs/admin/pl.ini b/rainloop/v/0.0.0/langs/admin/pl.ini
index b19479857..04c33dd1d 100644
--- a/rainloop/v/0.0.0/langs/admin/pl.ini
+++ b/rainloop/v/0.0.0/langs/admin/pl.ini
@@ -54,6 +54,7 @@ LABEL_DETERMINE_USER_LANGUAGE = "Spróbuj określić język użytkownika"
LEGEND_BRANDING = "Personalizacja"
LABEL_PAGE_TITLE = "Tytuł strony:"
LABEL_LOADING_DESCRIPTION = "Komunikat ład. strony:"
+LABEL_FAVICON_URL = "Favicon:"
LEGEND_LOGIN = "Login"
LABEL_LOGIN_LOGO = "Logo:"
LABEL_LOGIN_DESCRIPTION = "Opis:"
diff --git a/rainloop/v/0.0.0/langs/admin/pt-br.ini b/rainloop/v/0.0.0/langs/admin/pt-br.ini
index c5f089b54..45bb14753 100644
--- a/rainloop/v/0.0.0/langs/admin/pt-br.ini
+++ b/rainloop/v/0.0.0/langs/admin/pt-br.ini
@@ -54,6 +54,7 @@ LABEL_DETERMINE_USER_LANGUAGE = "Tentar determinar o idioma do usuário"
LEGEND_BRANDING = "Personalização"
LABEL_PAGE_TITLE = "Título da página"
LABEL_LOADING_DESCRIPTION = "Descrição do 'carregando'"
+LABEL_FAVICON_URL = "Favicon"
LEGEND_LOGIN = "Tela de Entrada"
LABEL_LOGIN_LOGO = "Logo"
LABEL_LOGIN_DESCRIPTION = "Descrição"
diff --git a/rainloop/v/0.0.0/langs/admin/ru.ini b/rainloop/v/0.0.0/langs/admin/ru.ini
index 56a597af4..a82015ee9 100644
--- a/rainloop/v/0.0.0/langs/admin/ru.ini
+++ b/rainloop/v/0.0.0/langs/admin/ru.ini
@@ -53,6 +53,7 @@ LABEL_DETERMINE_USER_LANGUAGE = "Пытаться определить язык
LEGEND_BRANDING = "Брендинг"
LABEL_PAGE_TITLE = "Название страницы"
LABEL_LOADING_DESCRIPTION = "Описание при загрузке"
+LABEL_FAVICON_URL = "Иконка (favicon)"
LEGEND_LOGIN = "Экран входа"
LABEL_LOGIN_LOGO = "Логотип"
LABEL_LOGIN_DESCRIPTION = "Описание"