Merge branch 'master' into plugin-2fa

This commit is contained in:
djmaze 2021-07-14 12:51:54 +02:00
commit 7985eaa23a
303 changed files with 51374 additions and 11952 deletions

View file

@ -34,8 +34,6 @@ module.exports = {
'Crossroads': "readonly", 'Crossroads': "readonly",
// vendors/jua // vendors/jua
'Jua': "readonly", 'Jua': "readonly",
// vendors/qr.js
'qr': "readonly",
// vendors/bootstrap/bootstrap.native.js // vendors/bootstrap/bootstrap.native.js
'BSN': "readonly" 'BSN': "readonly"
}, },

View file

@ -28,6 +28,9 @@ RewriteRule cpsess.* https://%{HTTP_HOST}/ [L,R=301]
Header set X-XSS-Protection "1; mode=block" Header set X-XSS-Protection "1; mode=block"
Header set Service-Worker-Allowed "/" Header set Service-Worker-Allowed "/"
# Google FLoC
Header set Permissions-Policy "interest-cohort=()"
RewriteCond %{HTTP:Accept-encoding} br RewriteCond %{HTTP:Accept-encoding} br
RewriteCond "%{REQUEST_FILENAME}\.br" -s RewriteCond "%{REQUEST_FILENAME}\.br" -s
RewriteRule "^(.+)" "$1\.br" [L,T=text/javascript,QSA] RewriteRule "^(.+)" "$1\.br" [L,T=text/javascript,QSA]

View file

@ -1,75 +1,75 @@
#!make #!make
rebuild: _down rebuild: _down
docker-compose build --no-cache docker compose build --no-cache
up: _up status up: _up status
_up: _up:
docker-compose up -d docker compose up -d
stop: _stop status stop: _stop status
_stop: _stop:
docker-compose stop docker compose stop
down: _down status down: _down status
_down: _down:
docker-compose down docker compose down
restart: _stop _up status restart: _stop _up status
status: status:
@docker-compose ps @docker compose ps
tx: tx:
@docker-compose run --no-deps --rm tx tx pull -a -s -f -d @docker compose run --no-deps --rm tx tx pull -a -s -f -d
console-node: console-node:
@docker-compose run --no-deps --rm node sh @docker compose run --no-deps --rm node sh
console-tx: console-tx:
@docker-compose run --no-deps --rm tx sh @docker compose run --no-deps --rm tx sh
console-php: console-php:
@docker-compose exec php sh @docker compose exec php sh
console: console-node console: console-node
logs: logs:
@docker-compose logs --tail=100 -f @docker compose logs --tail=100 -f
logs-db: logs-db:
@docker-compose logs --tail=100 -f db @docker compose logs --tail=100 -f db
logs-php: logs-php:
@docker-compose logs --tail=100 -f php @docker compose logs --tail=100 -f php
logs-node: logs-node:
@docker-compose logs --tail=100 -f node @docker compose logs --tail=100 -f node
logs-nginx: logs-nginx:
@docker-compose logs --tail=100 -f nginx @docker compose logs --tail=100 -f nginx
logs-mail: logs-mail:
@docker-compose logs --tail=100 -f mail @docker compose logs --tail=100 -f mail
logs-tx: logs-tx:
@docker-compose logs --tail=100 -f tx @docker compose logs --tail=100 -f tx
rl-lint: rl-lint:
@docker-compose run --no-deps --rm node gulp lint @docker compose run --no-deps --rm node gulp lint
rl-dev: rl-dev:
@docker-compose run --no-deps --rm node npm run watch-js @docker compose run --no-deps --rm node npm run watch-js
rl-compile: rl-compile:
@docker-compose run --no-deps --rm node gulp build @docker compose run --no-deps --rm node gulp build
rl-compile-with-source: rl-compile-with-source:
@docker-compose run --no-deps --rm node gulp build --source @docker compose run --no-deps --rm node gulp build --source
rl-watch-css: rl-watch-css:
@docker-compose run --no-deps --rm node npm run watch-css @docker compose run --no-deps --rm node npm run watch-css
rl-watch-js: rl-watch-js:
@docker-compose run --no-deps --rm node npm run watch-js @docker compose run --no-deps --rm node npm run watch-js
rl-build: rl-build:
@docker-compose run --no-deps --rm node gulp all @docker compose run --no-deps --rm node gulp all
rl-build-pro: rl-build-pro:
@docker-compose run --no-deps --rm node gulp all --pro @docker compose run --no-deps --rm node gulp all --pro
yarn-install: yarn-install:
@docker-compose run --no-deps --rm node yarn install @docker compose run --no-deps --rm node yarn install
yarn-outdated: yarn-outdated:
@docker-compose run --no-deps --rm node yarn outdated @docker compose run --no-deps --rm node yarn outdated
yarn-upgrade: yarn-upgrade:
@docker-compose run --no-deps --rm node yarn upgrade-interactive --exact --latest @docker compose run --no-deps --rm node yarn upgrade-interactive --exact --latest
gpg: gpg:
docker run -it --rm -w=/var/www \ docker run -it --rm -w=/var/www \

View file

@ -1,6 +1,6 @@
<div align="center"> <div align="center">
<a href="https://github.com/the-djmaze/snappymail"> <a href="https://github.com/the-djmaze/snappymail">
<img width="200" heigth="200" src="https://snappymail.eu/static/img/logo-256x256.png"> <img src="https://snappymail.eu/static/img/logo-256x256.png">
</a> </a>
<br> <br>
<h1>SnappyMail</h1> <h1>SnappyMail</h1>
@ -71,6 +71,9 @@ This fork of RainLoop has the following changes:
* Replaced webpack with rollup * Replaced webpack with rollup
* No user-agent detection (use device width) * No user-agent detection (use device width)
* Added support to load plugins as .phar * Added support to load plugins as .phar
* Replaced old Sabre library
* AddressBook Contacts support MySQL/MariaDB utf8mb4
* Prevent Google FLoC
### Removal of old JavaScript ### Removal of old JavaScript
@ -116,25 +119,30 @@ RainLoop 1.15 vs SnappyMail
|js/* |RainLoop |Snappy | |js/* |RainLoop |Snappy |
|--------------- |--------: |--------: | |--------------- |--------: |--------: |
|admin.js |2.158.025 | 94.597 | |admin.js |2.158.025 | 91.664 |
|app.js |4.215.733 | 460.014 | |app.js |4.215.733 | 453.928 |
|boot.js | 672.433 | 4.554 | |boot.js | 672.433 | 3.147 |
|libs.js | 647.679 | 218.511 | |libs.js | 647.679 | 218.466 |
|polyfills.js | 325.908 | 0 | |polyfills.js | 325.908 | 0 |
|serviceworker.js | 0 | 285 | |serviceworker.js | 0 | 285 |
|TOTAL |8.019.778 | 777.961 | |TOTAL |8.019.778 | 767.490 |
|js/min/* |RainLoop |Snappy |RL gzip |SM gzip |RL brotli |SM brotli | |js/min/* |RainLoop |Snappy |RL gzip |SM gzip |RL brotli |SM brotli |
|--------------- |--------: |--------: |------: |------: |--------: |--------: | |--------------- |--------: |--------: |------: |------: |--------: |--------: |
|admin.min.js | 255.514 | 49.908 | 73.899 | 14.781 | 60.674 | 13.233 | |admin.min.js | 255.514 | 47.908 | 73.899 | 14.313 | 60.674 | 12.787 |
|app.min.js | 516.000 | 236.129 |140.430 | 69.396 |110.657 | 58.601 | |app.min.js | 516.000 | 233.532 |140.430 | 68.577 |110.657 | 57.878 |
|boot.min.js | 66.456 | 2.442 | 22.553 | 1.371 | 20.043 | 1.178 | |boot.min.js | 66.456 | 1.751 | 22.553 | 1.025 | 20.043 | 858 |
|libs.min.js | 574.626 | 106.574 |177.280 | 38.600 |151.855 | 34.532 | |libs.min.js | 574.626 | 106.529 |177.280 | 38.607 |151.855 | 34.567 |
|polyfills.min.js | 32.608 | 0 | 11.315 | 0 | 10.072 | 0 | |polyfills.min.js | 32.608 | 0 | 11.315 | 0 | 10.072 | 0 |
|TOTAL |1.445.204 | 395.053 |425.477 |124.148 |353.301 |107.544 | |TOTAL |1.445.204 | 389.720 |425.477 |122.522 |353.301 |106.090 |
|TOTAL (no admin) |1.189.690 | 345.145 |351.061 |109.367 |292.627 | 94.311 | |TOTAL (no admin) |1.189.690 | 341.812 |351.061 |108.209 |292.627 | 93.303 |
For a user its around 66% smaller and faster than traditional RainLoop. For a user its around 68% smaller and faster than traditional RainLoop.
|OpenPGP |RainLoop |Snappy |RL gzip |SM gzip |RL brotli |SM brotli |
|--------------- |--------: |--------: |------: |------: |--------: |--------: |
|openpgp.min.js | 330.742 | 293.972 |102.388 | 93.030 | 84.241 | 77.142 |
|openpgp.worker | 1.499 | 1.125 | 824 | 567 | 695 | 467 |
### CSS changes ### CSS changes
@ -166,12 +174,12 @@ For a user its around 66% smaller and faster than traditional RainLoop.
|css/* |RainLoop |Snappy |RL gzip |SM gzip |SM brotli | |css/* |RainLoop |Snappy |RL gzip |SM gzip |SM brotli |
|------------ |-------: |-------: |------: |------: |--------: | |------------ |-------: |-------: |------: |------: |--------: |
|app.css | 340.334 | 107.026 | 46,959 | 18.779 | 16.133 | |app.css | 340.334 | 100.825 | 46,959 | 18.002 | 15.540 |
|app.min.css | 274.791 | 88.207 | 39.618 | 16.817 | 14.816 | |app.min.css | 274.791 | 82.684 | 39.618 | 16.138 | 14.234 |
|boot.css | | 2.066 | | 913 | 742 | |boot.css | | 1.326 | | 664 | 545 |
|boot.min.css | | 1.696 | | 818 | 664 | |boot.min.css | | 1.071 | | 590 | 474 |
|admin.css | | 46.326 | | 9.219 | 8.067 | |admin.css | | 42.217 | | 8.714 | 7.602 |
|admin.min.css | | 37.240 | | 8.178 | 7.268 | |admin.min.css | | 33.576 | | 7.680 | 6.810 |
### Squire vs CKEditor ### Squire vs CKEditor

View file

@ -10,6 +10,9 @@
// Uncomment to enable multiple domain installation. // Uncomment to enable multiple domain installation.
//define('MULTIDOMAIN', 1); //define('MULTIDOMAIN', 1);
// Uncomment to disable APCU.
//define('APP_USE_APCU_CACHE', false);
/** /**
* Custom 'data' folder path * Custom 'data' folder path
* @return string * @return string

View file

@ -28,7 +28,6 @@ export class AbstractApp {
logoutReload(close = false) { logoutReload(close = false) {
const url = logoutLink(); const url = logoutLink();
rl.hash.clear();
close && window.close && window.close(); close && window.close && window.close();
if (location.href !== url) { if (location.href !== url) {

View file

@ -18,9 +18,6 @@ class AdminApp extends AbstractApp {
bootstart() { bootstart() {
super.bootstart(); super.bootstart();
this.hideLoading();
if (!Settings.app('allowAdminPanel')) { if (!Settings.app('allowAdminPanel')) {
rl.route.root(); rl.route.root();
setTimeout(() => location.href = '/', 1); setTimeout(() => location.href = '/', 1);
@ -30,8 +27,7 @@ class AdminApp extends AbstractApp {
} else { } else {
startScreens([LoginAdminScreen]); startScreens([LoginAdminScreen]);
} }
this.hideLoading();
progressJs.end();
} }
} }

View file

@ -5,6 +5,7 @@ import { isPosNumeric, delegateRunOnDestroy, mailToHelper } from 'Common/UtilsUs
import { import {
Capa, Capa,
Notification,
Scope Scope
} from 'Common/Enums'; } from 'Common/Enums';
@ -100,9 +101,6 @@ class AppUser extends AbstractApp {
setInterval(() => { setInterval(() => {
const currentTime = Date.now(); const currentTime = Date.now();
if (currentTime > (lastTime + interval + 1000)) { if (currentTime > (lastTime + interval + 1000)) {
if (rl.hash.check()) {
this.reload();
}
Remote.jsVersion(iError => { Remote.jsVersion(iError => {
if (100 < iError) { if (100 < iError) {
this.reload(); this.reload();
@ -112,10 +110,6 @@ class AppUser extends AbstractApp {
lastTime = currentTime; lastTime = currentTime;
}, interval); }, interval);
if (rl.hash.check()) {
this.reload();
}
if (SettingsGet('UserBackgroundHash')) { if (SettingsGet('UserBackgroundHash')) {
setTimeout(() => { setTimeout(() => {
const img = userBackground(SettingsGet('UserBackgroundHash')); const img = userBackground(SettingsGet('UserBackgroundHash'));
@ -886,12 +880,7 @@ class AppUser extends AbstractApp {
} }
logout() { logout() {
Remote.logout(() => this.logoutReload((SettingsGet('ParentEmail')||{length:0}).length)); Remote.logout(() => rl.logoutReload((SettingsGet('ParentEmail')||{length:0}).length));
}
bootend() {
progressJs.end();
this.hideLoading();
} }
bootstart() { bootstart() {
@ -907,16 +896,12 @@ class AppUser extends AbstractApp {
const startupUrl = pString(SettingsGet('StartupUrl')); const startupUrl = pString(SettingsGet('StartupUrl'));
progressJs.set(90);
rl.setWindowTitle(); rl.setWindowTitle();
if (SettingsGet('Auth')) { if (SettingsGet('Auth')) {
rl.setWindowTitle(i18n('GLOBAL/LOADING')); rl.setWindowTitle(i18n('GLOBAL/LOADING'));
this.foldersReload(value => { this.foldersReload(value => {
try { try {
this.bootend();
if (value) { if (value) {
if (startupUrl) { if (startupUrl) {
rl.route.setHash(root(startupUrl), true); rl.route.setHash(root(startupUrl), true);
@ -960,6 +945,7 @@ class AppUser extends AbstractApp {
Settings.capa(Capa.Settings) ? SettingsUserScreen : null Settings.capa(Capa.Settings) ? SettingsUserScreen : null
// false ? AboutUserScreen : null // false ? AboutUserScreen : null
]); ]);
this.hideLoading();
setInterval(() => { setInterval(() => {
const cF = FolderUserStore.currentFolderFullNameRaw(), const cF = FolderUserStore.currentFolderFullNameRaw(),
@ -1030,8 +1016,8 @@ class AppUser extends AbstractApp {
}); });
} else { } else {
this.bootend();
startScreens([LoginUserScreen]); startScreens([LoginUserScreen]);
this.hideLoading();
} }
setInterval(() => dispatchEvent(new CustomEvent('reload-time')), 60000); setInterval(() => dispatchEvent(new CustomEvent('reload-time')), 60000);

View file

@ -1,14 +1,12 @@
import { pString, pInt } from 'Common/Utils'; import { pString, pInt } from 'Common/Utils';
import { Settings, SettingsGet } from 'Common/Globals'; import { Settings } from 'Common/Globals';
const const
ROOT = './', ROOT = './',
HASH_PREFIX = '#/', HASH_PREFIX = '#/',
SERVER_PREFIX = './?', SERVER_PREFIX = './?',
VERSION = Settings.app('version'), VERSION = Settings.app('version'),
VERSION_PREFIX = Settings.app('webVersionPath') || 'snappymail/v/' + VERSION + '/', VERSION_PREFIX = Settings.app('webVersionPath') || 'snappymail/v/' + VERSION + '/';
getHash = () => SettingsGet('AuthAccountHash') || '0';
/** /**
* @returns {string} * @returns {string}
@ -40,7 +38,7 @@ export function logoutLink() {
*/ */
export function serverRequestRaw(type, hash, customSpecSuffix) { export function serverRequestRaw(type, hash, customSpecSuffix) {
return SERVER_PREFIX + '/Raw/' + SUB_QUERY_PREFIX + '/' return SERVER_PREFIX + '/Raw/' + SUB_QUERY_PREFIX + '/'
+ (null == customSpecSuffix ? getHash() : customSpecSuffix) + '/' + (null == customSpecSuffix ? '0' : customSpecSuffix) + '/'
+ (type + (type
? type + '/' + (hash ? SUB_QUERY_PREFIX + '/' + hash : '') ? type + '/' + (hash ? SUB_QUERY_PREFIX + '/' + hash : '')
: '') : '')
@ -61,7 +59,7 @@ export function attachmentDownload(download, customSpecSuffix) {
* @returns {string} * @returns {string}
*/ */
export function serverRequest(type) { export function serverRequest(type) {
return SERVER_PREFIX + '/' + type + '/' + SUB_QUERY_PREFIX + '/' + getHash() + '/'; return SERVER_PREFIX + '/' + type + '/' + SUB_QUERY_PREFIX + '/0/';
} }
/** /**

View file

@ -3,7 +3,7 @@ import { Notification, UploadErrorCode } from 'Common/Enums';
import { langLink } from 'Common/Links'; import { langLink } from 'Common/Links';
import { doc, createElement } from 'Common/Globals'; import { doc, createElement } from 'Common/Globals';
let I18N_DATA = window.snappymailI18N || {}; let I18N_DATA = {};
export const trigger = ko.observable(false); export const trigger = ko.observable(false);
@ -49,10 +49,21 @@ const i18nToNode = element => {
} }
}, },
init = () => {
if (rl.I18N) {
I18N_DATA = rl.I18N;
Date.defineRelativeTimeFormat(rl.relativeTime || {});
rl.I18N = null;
return 1;
}
},
i18nKey = key => key.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase(), i18nKey = key => key.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase(),
getKeyByValue = (o, v) => Object.keys(o).find(key => o[key] === v); getKeyByValue = (o, v) => Object.keys(o).find(key => o[key] === v);
init();
/** /**
* @param {Object} elements * @param {Object} elements
* @param {boolean=} animate = false * @param {boolean=} animate = false
@ -127,13 +138,11 @@ export function reload(admin, language) {
const script = createElement('script'); const script = createElement('script');
script.onload = () => { script.onload = () => {
// reload the data // reload the data
if (window.snappymailI18N) { if (init()) {
I18N_DATA = window.snappymailI18N;
i18nToNodes(doc); i18nToNodes(doc);
dispatchEvent(new CustomEvent('reload-time')); dispatchEvent(new CustomEvent('reload-time'));
trigger(!trigger()); trigger(!trigger());
} }
window.snappymailI18N = null;
script.remove(); script.remove();
resolve(); resolve();
}; };

View file

@ -1,5 +1,5 @@
import { SaveSettingsStep } from 'Common/Enums'; import { SaveSettingsStep } from 'Common/Enums';
import { doc, createElement, elementById } from 'Common/Globals'; import { doc, elementById } from 'Common/Globals';
export const export const
isArray = Array.isArray, isArray = Array.isArray,
@ -84,14 +84,13 @@ let __themeTimer = 0,
* @returns {void} * @returns {void}
*/ */
export function changeTheme(value, themeTrigger = ()=>{}) { export function changeTheme(value, themeTrigger = ()=>{}) {
const themeLink = elementById('app-theme-link'), const themeStyle = elementById('app-theme-style'),
clearTimer = () => { clearTimer = () => {
__themeTimer = setTimeout(() => themeTrigger(SaveSettingsStep.Idle), 1000); __themeTimer = setTimeout(() => themeTrigger(SaveSettingsStep.Idle), 1000);
__themeJson = null; __themeJson = null;
}; };
let themeStyle = elementById('app-theme-style'), let url = themeStyle.dataset.href;
url = (themeLink && themeLink.href) || (themeStyle && themeStyle.dataset.href);
if (url) { if (url) {
url = url.toString() url = url.toString()
@ -118,19 +117,9 @@ export function changeTheme(value, themeTrigger = ()=>{}) {
rl.fetchJSON(url, init) rl.fetchJSON(url, init)
.then(data => { .then(data => {
if (data && isArray(data) && 2 === data.length) { if (data && isArray(data) && 2 === data.length) {
if (themeLink && !themeStyle) {
themeStyle = createElement('style');
themeStyle.id = 'app-theme-style';
themeLink.after(themeStyle);
themeLink.remove();
}
if (themeStyle) {
themeStyle.textContent = data[1]; themeStyle.textContent = data[1];
themeStyle.dataset.href = url; themeStyle.dataset.href = url;
themeStyle.dataset.theme = data[0]; themeStyle.dataset.theme = data[0];
}
themeTrigger(SaveSettingsStep.TrueResult); themeTrigger(SaveSettingsStep.TrueResult);
} }
}) })

View file

@ -11,31 +11,18 @@ export class CheckboxMaterialDesignComponent extends AbstractCheckbox {
this.animationBox = ko.observable(false).extend({ falseTimeout: 200 }); this.animationBox = ko.observable(false).extend({ falseTimeout: 200 });
this.animationCheckmark = ko.observable(false).extend({ falseTimeout: 200 }); this.animationCheckmark = ko.observable(false).extend({ falseTimeout: 200 });
this.animationBoxSetTrue = this.animationBoxSetTrue.bind(this);
this.animationCheckmarkSetTrue = this.animationCheckmarkSetTrue.bind(this);
this.disposable.push( this.disposable.push(
this.value.subscribe((value) => { this.value.subscribe(value => this.triggerAnimation(value), this)
this.triggerAnimation(value);
}, this)
); );
} }
animationBoxSetTrue() {
this.animationBox(true);
}
animationCheckmarkSetTrue() {
this.animationCheckmark(true);
}
triggerAnimation(box) { triggerAnimation(box) {
if (box) { if (box) {
this.animationBoxSetTrue(); this.animationBox(true);
setTimeout(this.animationCheckmarkSetTrue, 200); setTimeout(()=>this.animationCheckmark(true), 200);
} else { } else {
this.animationCheckmarkSetTrue(); this.animationCheckmark(true);
setTimeout(this.animationBoxSetTrue, 200); setTimeout(()=>this.animationBox(true), 200);
} }
} }
} }

View file

@ -412,6 +412,7 @@ class SquireUI
input.title = ctrlKey + cfg.key; input.title = ctrlKey + cfg.key;
} }
input.dataset.action = action; input.dataset.action = action;
input.tabIndex = -1;
cfg.input = input; cfg.input = input;
toolgroup.append(input); toolgroup.append(input);
} }

View file

@ -92,7 +92,7 @@ export class AbstractViewCenter extends AbstractView
{ {
constructor(name, templateID) constructor(name, templateID)
{ {
super(name, templateID, ViewType.Center); super(name, templateID, ViewType.Content);
} }
} }

View file

@ -18,7 +18,7 @@ export const ViewType = {
Popup: 'Popups', Popup: 'Popups',
Left: 'Left', Left: 'Left',
Right: 'Right', Right: 'Right',
Center: 'Center' Content: 'Content'
}; };
/** /**
@ -76,7 +76,7 @@ function buildViewModel(ViewModelClass, vmScreen) {
let vmDom = null; let vmDom = null;
const vm = new ViewModelClass(vmScreen), const vm = new ViewModelClass(vmScreen),
position = vm.viewModelPosition || '', position = vm.viewModelPosition || '',
vmPlace = position ? doc.querySelector('#rl-content #rl-' + position.toLowerCase()) : null; vmPlace = position ? doc.getElementById('rl-' + position.toLowerCase()) : null;
ViewModelClass.__builded = true; ViewModelClass.__builded = true;
ViewModelClass.__vm = vm; ViewModelClass.__vm = vm;
@ -145,6 +145,8 @@ function buildViewModel(ViewModelClass, vmScreen) {
if (vm && ViewType.Popup === position) { if (vm && ViewType.Popup === position) {
vm.registerPopupKeyDown(); vm.registerPopupKeyDown();
} }
dispatchEvent(new CustomEvent('rl-view-model', {detail:vm}));
} else { } else {
console.log('Cannot find view model position: ' + position); console.log('Cannot find view model position: ' + position);
} }
@ -153,7 +155,7 @@ function buildViewModel(ViewModelClass, vmScreen) {
return ViewModelClass && ViewModelClass.__vm; return ViewModelClass && ViewModelClass.__vm;
} }
function getScreenPopupViewModel(ViewModelClassToShow) { export function getScreenPopupViewModel(ViewModelClassToShow) {
return (buildViewModel(ViewModelClassToShow) && ViewModelClassToShow.__dom) && ViewModelClassToShow.__vm; return (buildViewModel(ViewModelClassToShow) && ViewModelClassToShow.__dom) && ViewModelClassToShow.__vm;
} }
@ -175,15 +177,6 @@ export function showScreenPopup(ViewModelClassToShow, params = []) {
} }
} }
/**
* @param {Function} ViewModelClassToShow
* @returns {void}
*/
export function warmUpScreenPopup(ViewModelClassToShow) {
const vm = getScreenPopupViewModel(ViewModelClassToShow);
vm && vm.onWarmUp && vm.onWarmUp();
}
/** /**
* @param {Function} ViewModelClassToShow * @param {Function} ViewModelClassToShow
* @returns {boolean} * @returns {boolean}

View file

@ -100,10 +100,8 @@ export class FolderCollectionModel extends AbstractCollectionModel
storeIt() { storeIt() {
const cnt = pInt(this.CountRec); const cnt = pInt(this.CountRec);
let limit = pInt(Settings.app('folderSpecLimit')); FolderUserStore.displaySpecSetting(0 >= cnt
limit = 100 < limit ? 100 : 10 > limit ? 10 : limit; || Math.max(10, Math.min(100, pInt(Settings.app('folderSpecLimit')))) < cnt);
FolderUserStore.displaySpecSetting(0 >= cnt || limit < cnt);
FolderUserStore.folderList(this); FolderUserStore.folderList(this);

View file

@ -1,5 +1,4 @@
import { Notification } from 'Common/Enums'; import { Notification } from 'Common/Enums';
import { Settings } from 'Common/Globals';
import { isArray, pInt, pString } from 'Common/Utils'; import { isArray, pInt, pString } from 'Common/Utils';
import { serverRequest } from 'Common/Links'; import { serverRequest } from 'Common/Links';
@ -8,13 +7,6 @@ let iJsonErrorCount = 0,
const getURL = (add = '') => serverRequest('Json') + add, const getURL = (add = '') => serverRequest('Json') + add,
updateToken = data => {
if (data.UpdateToken) {
rl.hash.set();
Settings.set('AuthAccountHash', data.UpdateToken);
}
},
checkResponseError = data => { checkResponseError = data => {
const err = data ? data.ErrorCode : null; const err = data ? data.ErrorCode : null;
if (Notification.InvalidToken === err && 10 < ++iTokenErrorCount) { if (Notification.InvalidToken === err && 10 < ++iTokenErrorCount) {
@ -32,9 +24,8 @@ checkResponseError = data => {
) { ) {
++iJsonErrorCount; ++iJsonErrorCount;
} }
if (data.ClearAuth || data.Logout || 7 < iJsonErrorCount) { if (data.Logout || 7 < iJsonErrorCount) {
rl.hash.clear(); rl.logoutReload();
data.ClearAuth || rl.logoutReload();
} }
} }
}, },
@ -97,14 +88,10 @@ export class AbstractFetchRemote
undefined === iTimeout ? 30000 : pInt(iTimeout), undefined === iTimeout ? 30000 : pInt(iTimeout),
data => { data => {
let cached = false; let cached = false;
if (data) { if (data && data.Time) {
if (data.Time) {
cached = pInt(data.Time) > Date.now() - start; cached = pInt(data.Time) > Date.now() - start;
} }
updateToken(data);
}
let iError = 0; let iError = 0;
if (sAction && oRequests[sAction]) { if (sAction && oRequests[sAction]) {
if (oRequests[sAction].__aborted) { if (oRequests[sAction].__aborted) {
@ -188,8 +175,6 @@ export class AbstractFetchRemote
if (!data) { if (!data) {
return Promise.reject(Notification.JsonParse); return Promise.reject(Notification.JsonParse);
} }
updateToken(data);
/* /*
let isCached = false, type = ''; let isCached = false, type = '';
if (data && data.Time) { if (data && data.Time) {

View file

@ -126,8 +126,7 @@ export class AbstractSettingsScreen extends AbstractScreen {
this.menu.push({ this.menu.push({
route: SettingsViewModel.__rlSettingsData.Route, route: SettingsViewModel.__rlSettingsData.Route,
label: SettingsViewModel.__rlSettingsData.Label, label: SettingsViewModel.__rlSettingsData.Label,
selected: ko.observable(false), selected: ko.observable(false)
disabled: false
}); });
} }
}); });

View file

@ -15,7 +15,7 @@ import { FolderListMailBoxUserView } from 'View/User/MailBox/FolderList';
import { MessageListMailBoxUserView } from 'View/User/MailBox/MessageList'; import { MessageListMailBoxUserView } from 'View/User/MailBox/MessageList';
import { MessageViewMailBoxUserView } from 'View/User/MailBox/MessageView'; import { MessageViewMailBoxUserView } from 'View/User/MailBox/MessageView';
import { warmUpScreenPopup } from 'Knoin/Knoin'; import { getScreenPopupViewModel } from 'Knoin/Knoin';
import { AbstractScreen } from 'Knoin/AbstractScreen'; import { AbstractScreen } from 'Knoin/AbstractScreen';
@ -88,7 +88,7 @@ export class MailBoxUserScreen extends AbstractScreen {
onStart() { onStart() {
if (!this.__started) { if (!this.__started) {
super.onStart(); super.onStart();
setTimeout(() => warmUpScreenPopup(ComposePopupView), 500); setTimeout(() => getScreenPopupViewModel(ComposePopupView), 500);
addEventListener('mailbox.inbox-unread-count', e => { addEventListener('mailbox.inbox-unread-count', e => {
FolderUserStore.foldersInboxUnreadCount(e.detail); FolderUserStore.foldersInboxUnreadCount(e.detail);

View file

@ -105,8 +105,6 @@ export class ContactsAdminSettings {
}) })
}) })
this.onTestContactsResponse = this.onTestContactsResponse.bind(this);
decorateKoCommands(this, { decorateKoCommands(this, {
testContactsCommand: self => self.pdoDsn() && self.pdoUser() testContactsCommand: self => self.pdoDsn() && self.pdoUser()
}); });
@ -118,15 +116,7 @@ export class ContactsAdminSettings {
this.testContactsErrorMessage(''); this.testContactsErrorMessage('');
this.testing(true); this.testing(true);
Remote.testContacts(this.onTestContactsResponse, { Remote.testContacts((iError, data) => {
ContactsPdoType: this.contactsType(),
ContactsPdoDsn: this.pdoDsn(),
ContactsPdoUser: this.pdoUser(),
ContactsPdoPassword: this.pdoPassword()
});
}
onTestContactsResponse(iError, data) {
this.testContactsSuccess(false); this.testContactsSuccess(false);
this.testContactsError(false); this.testContactsError(false);
this.testContactsErrorMessage(''); this.testContactsErrorMessage('');
@ -143,6 +133,12 @@ export class ContactsAdminSettings {
} }
this.testing(false); this.testing(false);
}, {
ContactsPdoType: this.contactsType(),
ContactsPdoDsn: this.pdoDsn(),
ContactsPdoUser: this.pdoUser(),
ContactsPdoPassword: this.pdoPassword()
});
} }
onShow() { onShow() {

View file

@ -13,9 +13,6 @@ export class DomainsAdminSettings {
this.domains = DomainAdminStore; this.domains = DomainAdminStore;
this.domainForDeletion = ko.observable(null).deleteAccessHelper(); this.domainForDeletion = ko.observable(null).deleteAccessHelper();
this.onDomainListChangeRequest = this.onDomainListChangeRequest.bind(this);
this.onDomainLoadRequest = this.onDomainLoadRequest.bind(this);
} }
createDomain() { createDomain() {
@ -28,30 +25,22 @@ export class DomainsAdminSettings {
deleteDomain(domain) { deleteDomain(domain) {
DomainAdminStore.remove(domain); DomainAdminStore.remove(domain);
Remote.domainDelete(this.onDomainListChangeRequest, domain.name); Remote.domainDelete(DomainAdminStore.fetch, domain.name);
} }
disableDomain(domain) { disableDomain(domain) {
domain.disabled(!domain.disabled()); domain.disabled(!domain.disabled());
Remote.domainDisable(this.onDomainListChangeRequest, domain.name, domain.disabled()); Remote.domainDisable(DomainAdminStore.fetch, domain.name, domain.disabled());
} }
onBuild(oDom) { onBuild(oDom) {
oDom.addEventListener('click', event => { oDom.addEventListener('click', event => {
let el = event.target.closestWithin('.b-admin-domains-list-table .e-item .e-action', oDom); let el = event.target.closestWithin('.b-admin-domains-list-table .e-action', oDom);
el && ko.dataFor(el) && Remote.domain(this.onDomainLoadRequest, ko.dataFor(el).name); el && ko.dataFor(el) && Remote.domain(
(iError, oData) => iError || showScreenPopup(DomainPopupView, [oData.Result]), ko.dataFor(el).name
);
}); });
DomainAdminStore.fetch(); DomainAdminStore.fetch();
} }
onDomainLoadRequest(iError, oData) {
if (!iError) {
showScreenPopup(DomainPopupView, [oData.Result]);
}
}
onDomainListChangeRequest() {
DomainAdminStore.fetch();
}
} }

View file

@ -12,16 +12,14 @@ import Remote from 'Remote/Admin/Fetch';
import { PluginPopupView } from 'View/Popup/Plugin'; import { PluginPopupView } from 'View/Popup/Plugin';
export class PluginsAdminSettings { export class PluginsAdminSettings
{
constructor() { constructor() {
this.enabledPlugins = ko.observable(!!SettingsGet('EnabledPlugins')); this.enabledPlugins = ko.observable(!!SettingsGet('EnabledPlugins'));
this.plugins = PluginAdminStore; this.plugins = PluginAdminStore;
this.pluginsError = PluginAdminStore.error; this.pluginsError = PluginAdminStore.error;
this.onPluginLoadRequest = this.onPluginLoadRequest.bind(this);
this.onPluginDisableRequest = this.onPluginDisableRequest.bind(this);
this.enabledPlugins.subscribe(value => this.enabledPlugins.subscribe(value =>
Remote.saveAdminConfig(null, { Remote.saveAdminConfig(null, {
EnabledPlugins: value ? 1 : 0 EnabledPlugins: value ? 1 : 0
@ -30,20 +28,30 @@ export class PluginsAdminSettings {
} }
disablePlugin(plugin) { disablePlugin(plugin) {
plugin.disabled(!plugin.disabled()); let b = !plugin.disabled();
Remote.pluginDisable(this.onPluginDisableRequest, plugin.name, plugin.disabled()); plugin.disabled(b);
Remote.pluginDisable((iError, data) => {
if (iError) {
PluginAdminStore.error(
(Notification.UnsupportedPluginPackage === iError && data && data.ErrorMessage)
? data.ErrorMessage
: getNotification(iError)
);
}
PluginAdminStore.fetch();
}, plugin.name, b);
} }
configurePlugin(plugin) { configurePlugin(plugin) {
Remote.plugin(this.onPluginLoadRequest, plugin.name); Remote.plugin((iError, data) => iError || showScreenPopup(PluginPopupView, [data.Result]), plugin.name);
} }
onBuild(oDom) { onBuild(oDom) {
oDom.addEventListener('click', event => { oDom.addEventListener('click', event => {
let el = event.target.closestWithin('.e-item .configure-plugin-action', oDom); let el = event.target.closestWithin('.configure-plugin-action', oDom);
el && ko.dataFor(el) && this.configurePlugin(ko.dataFor(el)); el && ko.dataFor(el) && this.configurePlugin(ko.dataFor(el));
el = event.target.closestWithin('.e-item .disabled-plugin', oDom); el = event.target.closestWithin('.disabled-plugin', oDom);
el && ko.dataFor(el) && this.disablePlugin(ko.dataFor(el)); el && ko.dataFor(el) && this.disablePlugin(ko.dataFor(el));
}); });
} }
@ -52,22 +60,4 @@ export class PluginsAdminSettings {
PluginAdminStore.error(''); PluginAdminStore.error('');
PluginAdminStore.fetch(); PluginAdminStore.fetch();
} }
onPluginLoadRequest(iError, data) {
if (!iError) {
showScreenPopup(PluginPopupView, [data.Result]);
}
}
onPluginDisableRequest(iError, data) {
if (iError) {
if (Notification.UnsupportedPluginPackage === iError && data && data.ErrorMessage) {
PluginAdminStore.error(data.ErrorMessage);
} else {
PluginAdminStore.error(getNotification(iError));
}
}
PluginAdminStore.fetch();
}
} }

View file

@ -72,8 +72,6 @@ export class SecurityAdminSettings {
}) })
}); });
this.onNewAdminPasswordResponse = this.onNewAdminPasswordResponse.bind(this);
decorateKoCommands(this, { decorateKoCommands(this, {
saveNewAdminPasswordCommand: self => self.adminLogin().trim() && self.adminPassword() saveNewAdminPasswordCommand: self => self.adminLogin().trim() && self.adminPassword()
}); });
@ -93,16 +91,7 @@ export class SecurityAdminSettings {
this.adminPasswordUpdateError(false); this.adminPasswordUpdateError(false);
this.adminPasswordUpdateSuccess(false); this.adminPasswordUpdateSuccess(false);
Remote.saveNewAdminPassword(this.onNewAdminPasswordResponse, { Remote.saveNewAdminPassword((iError, data) => {
'Login': this.adminLogin(),
'Password': this.adminPassword(),
'NewPassword': this.adminPasswordNew()
});
return true;
}
onNewAdminPasswordResponse(iError, data) {
if (iError) { if (iError) {
this.adminPasswordUpdateError(true); this.adminPasswordUpdateError(true);
} else { } else {
@ -114,6 +103,13 @@ export class SecurityAdminSettings {
this.weakPassword(!!data.Result.Weak); this.weakPassword(!!data.Result.Weak);
} }
}, {
'Login': this.adminLogin(),
'Password': this.adminPassword(),
'NewPassword': this.adminPasswordNew()
});
return true;
} }
onHide() { onHide() {

View file

@ -1,9 +1,9 @@
const storage = localStorage, const
CLIENT_SIDE_STORAGE_INDEX_NAME = 'rlcsc', CLIENT_SIDE_STORAGE_INDEX_NAME = 'rlcsc',
getStorage = () => { getStorage = () => {
try { try {
const value = storage.getItem(CLIENT_SIDE_STORAGE_INDEX_NAME) || null; const value = localStorage.getItem(CLIENT_SIDE_STORAGE_INDEX_NAME);
return null == value ? null : JSON.parse(value); return value ? JSON.parse(value) : null;
} catch (e) { } catch (e) {
return null; return null;
} }
@ -19,7 +19,7 @@ export function set(key, data) {
storageResult['p' + key] = data; storageResult['p' + key] = data;
try { try {
storage.setItem(CLIENT_SIDE_STORAGE_INDEX_NAME, JSON.stringify(storageResult)); localStorage.setItem(CLIENT_SIDE_STORAGE_INDEX_NAME, JSON.stringify(storageResult));
return true; return true;
} catch (e) { } catch (e) {
return false; return false;
@ -32,10 +32,7 @@ export function set(key, data) {
*/ */
export function get(key) { export function get(key) {
try { try {
key = 'p' + key; return (getStorage() || {})['p' + key];
const storageResult = getStorage();
return storageResult && null != storageResult[key] ? storageResult[key] : null;
} catch (e) { } catch (e) {
return null; return null;
} }

View file

@ -220,8 +220,6 @@ export const MessageUserStore = new class {
} }
}); });
this.onMessageResponse = this.onMessageResponse.bind(this);
this.purgeMessageBodyCacheThrottle = this.purgeMessageBodyCache.throttle(30000); this.purgeMessageBodyCacheThrottle = this.purgeMessageBodyCache.throttle(30000);
} }
@ -437,7 +435,6 @@ export const MessageUserStore = new class {
id = '', id = '',
plain = '', plain = '',
resultHtml = '', resultHtml = '',
pgpSigned = false,
messagesDom = this.messagesBodiesDom(), messagesDom = this.messagesBodiesDom(),
selectedMessage = this.selectorMessageSelected(), selectedMessage = this.selectorMessageSelected(),
message = this.message(); message = this.message();
@ -497,27 +494,17 @@ export const MessageUserStore = new class {
if ((message.isPgpSigned() || message.isPgpEncrypted()) && PgpUserStore.capaOpenPGP()) { if ((message.isPgpSigned() || message.isPgpEncrypted()) && PgpUserStore.capaOpenPGP()) {
plain = pString(json.Plain); plain = pString(json.Plain);
const isPgpEncrypted = /---BEGIN PGP MESSAGE---/.test(plain);
if (!isPgpEncrypted) {
pgpSigned =
/-----BEGIN PGP SIGNED MESSAGE-----/.test(plain) && /-----BEGIN PGP SIGNATURE-----/.test(plain);
}
const pre = createElement('pre'); const pre = createElement('pre');
if (pgpSigned && message.isPgpSigned()) { if (message.isPgpSigned()) {
pre.className = 'b-plain-openpgp signed'; pre.className = 'b-plain-openpgp signed';
pre.textContent = plain; pre.textContent = plain;
} else if (isPgpEncrypted && message.isPgpEncrypted()) { } else if (message.isPgpEncrypted()) {
pre.className = 'b-plain-openpgp encrypted'; pre.className = 'b-plain-openpgp encrypted';
pre.textContent = plain; pre.textContent = plain;
} else { } else {
pre.innerHTML = resultHtml; pre.innerHTML = resultHtml;
} }
resultHtml = pre.outerHTML; resultHtml = pre.outerHTML;
message.isPgpSigned(pgpSigned);
message.isPgpEncrypted(isPgpEncrypted);
} else { } else {
resultHtml = '<pre>' + resultHtml + '</pre>'; resultHtml = '<pre>' + resultHtml + '</pre>';
} }
@ -630,16 +617,7 @@ export const MessageUserStore = new class {
if (oMessage) { if (oMessage) {
this.hideMessageBodies(); this.hideMessageBodies();
this.messageLoading(true); this.messageLoading(true);
Remote.message(this.onMessageResponse, oMessage.folder, oMessage.uid); Remote.message((iError, oData, bCached) => {
}
}
/**
* @param {string} sResult
* @param {FetchJsonDefaultResponse} oData
* @param {boolean} bCached
*/
onMessageResponse(iError, oData, bCached) {
if (iError) { if (iError) {
if (Notification.RequestAborted !== iError) { if (Notification.RequestAborted !== iError) {
this.message(null); this.message(null);
@ -649,6 +627,8 @@ export const MessageUserStore = new class {
this.setMessage(oData, bCached); this.setMessage(oData, bCached);
} }
this.messageLoading(false); this.messageLoading(false);
}, oMessage.folder, oMessage.uid);
}
} }
/** /**

View file

@ -7,7 +7,7 @@ import * as Links from 'Common/Links';
/** /**
* Might not work due to the new ServiceWorkerRegistration.showNotification * Might not work due to the new ServiceWorkerRegistration.showNotification
*/ */
const HTML5Notification = window.Notification ? Notification : null, const HTML5Notification = window.Notification,
HTML5NotificationStatus = () => (HTML5Notification && HTML5Notification.permission) || 'denied', HTML5NotificationStatus = () => (HTML5Notification && HTML5Notification.permission) || 'denied',
NotificationsDenied = () => 'denied' === HTML5NotificationStatus(), NotificationsDenied = () => 'denied' === HTML5NotificationStatus(),
NotificationsGranted = () => 'granted' === HTML5NotificationStatus(), NotificationsGranted = () => 'granted' === HTML5NotificationStatus(),

View file

@ -340,11 +340,11 @@ export const PgpUserStore = new class {
*/ */
initMessageBodyControls(dom, rainLoopMessage) { initMessageBodyControls(dom, rainLoopMessage) {
const cl = dom && dom.classList; const cl = dom && dom.classList;
if (!cl.has('inited')) { if (!cl.contains('inited')) {
cl.add('inited'); cl.add('inited');
const encrypted = cl.has('encrypted'), const encrypted = cl.contains('encrypted'),
signed = cl.has('signed'), signed = cl.contains('signed'),
recipients = rainLoopMessage ? rainLoopMessage.getEmails(['from', 'to', 'cc']) : []; recipients = rainLoopMessage ? rainLoopMessage.getEmails(['from', 'to', 'cc']) : [];
let verControl = null; let verControl = null;
@ -355,10 +355,10 @@ export const PgpUserStore = new class {
verControl = Element.fromHTML('<div class="b-openpgp-control"><i class="fontastic">🔒</i></div>'); verControl = Element.fromHTML('<div class="b-openpgp-control"><i class="fontastic">🔒</i></div>');
if (encrypted) { if (encrypted) {
verControl.title = i18n('MESSAGE/PGP_ENCRYPTED_MESSAGE_DESC'); verControl.title = i18n('MESSAGE/PGP_ENCRYPTED_MESSAGE_DESC');
verControl.addEventHandler('click', domControlEncryptedClickHelper(this, dom, domText, recipients)); verControl.addEventListener('click', domControlEncryptedClickHelper(this, dom, domText, recipients));
} else { } else {
verControl.title = i18n('MESSAGE/PGP_SIGNED_MESSAGE_DESC'); verControl.title = i18n('MESSAGE/PGP_SIGNED_MESSAGE_DESC');
verControl.addEventHandler('click', domControlSignedClickHelper(this, dom, domText)); verControl.addEventListener('click', domControlSignedClickHelper(this, dom, domText));
} }
dom.before(verControl, createElement('div')); dom.before(verControl, createElement('div'));

View file

@ -33,7 +33,7 @@ body {
text-align: center; text-align: center;
} }
.e-spinner .e-bounce { .e-spinner div {
animation: bouncedelay 1.4s infinite ease-in-out; animation: bouncedelay 1.4s infinite ease-in-out;
background-color: var(--loading-color, #000); background-color: var(--loading-color, #000);
border-radius: 100%; border-radius: 100%;
@ -46,12 +46,12 @@ body {
animation-fill-mode: both; animation-fill-mode: both;
} }
.e-spinner .bounce1 { .e-spinner div + div {
animation-delay: -0.32s; animation-delay: 0.16s;
} }
.e-spinner .bounce2 { .e-spinner div + div + div {
animation-delay: -0.16s; animation-delay: 0.32s;
} }
@keyframes bouncedelay { @keyframes bouncedelay {

View file

@ -1,67 +1,25 @@
.b-admin-left { #rl-left {
overflow: auto;
padding-top: 50px + @rlLowMargin + 10px;
.b-toolbar { nav {
position: absolute; a {
top: 0;
right: 0;
left: 0;
height: 34px;
padding: 8px 0 0 @rlLowMargin;
}
.b-content {
position: absolute;
top: 50px + @rlLowMargin + 10px;
bottom: @rlLowMargin;
left: 0;
right: 0;
overflow: hidden;
.content {
-webkit-overflow-scrolling: touch;
}
}
}
.b-admin-menu {
.e-item {
overflow: hidden;
text-decoration: none;
outline: 0;
}
.e-link {
position: relative;
display: block;
height: 30px;
line-height: 29px;
cursor: pointer;
font-size: 18px;
z-index: 1;
cursor: default;
background-color: transparent;
color: var(--settings-menu-disabled-color, #666);
padding: 4px 10px;
outline: 0;
text-decoration: none;
}
.e-item.selectable .e-link {
cursor: pointer;
color: var(--settings-menu-color, #333); color: var(--settings-menu-color, #333);
cursor: pointer;
display: block;
font-size: 18px;
line-height: 30px;
padding: 4px 10px;
text-decoration: none;
} }
.e-item.selectable { a:focus, a:hover {
&:hover .e-link{
background-color: var(--settings-menu-hover-bg-color, #333); background-color: var(--settings-menu-hover-bg-color, #333);
color: var(--settings-menu-hover-color, #eee); color: var(--settings-menu-hover-color, #eee);
} }
&.selected .e-link {
a.selected {
background-color: var(--settings-menu-selected-bg-color, #333); background-color: var(--settings-menu-selected-bg-color, #333);
color: var(--settings-menu-selected-color, #eee); color: var(--settings-menu-selected-color, #eee);
} }
@ -97,14 +55,10 @@
z-index: 2; z-index: 2;
background-color: #fff; background-color: #fff;
border: @rlMainBorderSize solid @rlMainDarkColor; border: 1px solid @rlMainDarkColor;
box-shadow: @rlMainShadow; box-shadow: @rlMainShadow;
border-radius: @rlMainBorderRadius; border-radius: @rlMainBorderRadius;
.content {
-webkit-overflow-scrolling: touch;
}
} }
} }

View file

@ -3,8 +3,6 @@
width: 600px; width: 600px;
.e-item {
.domain-name { .domain-name {
display: inline-block; display: inline-block;
word-break: break-all; word-break: break-all;
@ -18,22 +16,16 @@
padding-left: 5px; padding-left: 5px;
} }
&.disabled {
.domain-name, .domain-alias {
color: #bbb;
}
}
.button-delete { .button-delete {
margin-right: 15px; position: relative;
right: 0;
visibility: hidden; visibility: hidden;
opacity: 0; opacity: 0;
} white-space: nowrap;
.delete-access { &.delete-access {
&.button-delete {
visibility: visible; visibility: visible;
margin-right: 0; right: -15px;
opacity: 1; opacity: 1;
} }
} }
@ -41,16 +33,19 @@
.delete-domain, .disabled-domain { .delete-domain, .disabled-domain {
cursor: pointer; cursor: pointer;
opacity: 0.5; opacity: 0.5;
}
&.disabled .disabled-domain {
opacity: 0.5;
}
.delete-domain, .disabled-domain {
&:hover { &:hover {
opacity: 1; opacity: 1;
} }
} }
&.disabled {
.domain-name, .domain-alias {
color: #bbb;
}
.disabled-domain {
opacity: 0.5;
}
} }
} }

View file

@ -11,8 +11,6 @@
width: 700px; width: 700px;
.e-item {
.package-img { .package-img {
font-size: 12px; font-size: 12px;
margin-right: 2px; margin-right: 2px;
@ -32,5 +30,4 @@
.package-actions { .package-actions {
vertical-align: middle; vertical-align: middle;
} }
}
} }

View file

@ -1,26 +1,14 @@
.b-admin-plugins-list-table { .b-admin-plugins-list-table {
max-width: 620px; /* span8 */
&.disabled { &.disabled {
opacity: 0.5; opacity: 0.5;
background-color: #eee;
} }
.e-item { .disabled {
.plugin-img {
font-size: 12px;
margin-right: 2px;
}
&.disabled {
.plugin-img, .plugin-name {
color: #bbb;
}
.disabled-plugin {
opacity: 0.5; opacity: 0.5;
} }
}
}
} }
.b-admin-plugin-property { .b-admin-plugin-property {

View file

@ -25,7 +25,7 @@ html.rl-started-delay {
@media screen and (min-width: 1000px) { @media screen and (min-width: 1000px) {
html.rl-started-trigger .b-login-content .loginFormWrapper { html.rl-started-trigger .b-login-content {
/*transform: scale(1.1);*/ /*transform: scale(1.1);*/
transform: translateY(-20px); transform: translateY(-20px);
opacity: 0.5; opacity: 0.5;
@ -40,11 +40,7 @@ html.rl-started-delay {
font-weight: bold; font-weight: bold;
} }
.b-login-content .afterLoginHide { .b-login-content {
opacity: 0;
}
.b-login-content .loginFormWrapper {
transition: all 0.3s ease-out; transition: all 0.3s ease-out;
} }

View file

@ -39,7 +39,7 @@
transition: margin 0.3s linear; transition: margin 0.3s linear;
} }
.b-folders .b-content.opacity-on-panel-disabled { #rl-left .b-content {
transition: opacity 0.3s linear; transition: opacity 0.3s linear;
} }

View file

@ -41,7 +41,7 @@
} }
&.error { &.error {
.attachmentIcon, .attachmentSize, .attachmentName { .iconMain, .iconPreview, .attachmentSize, .attachmentName {
color: red; color: red;
} }
} }
@ -91,11 +91,10 @@
border-left: 1px solid #ddd; border-left: 1px solid #ddd;
} }
.attachmentIcon { .iconMain, .iconPreview {
margin: 6px 0 0; box-sizing: border-box;
padding: 6px 0 0;
font-size: 36px; font-size: 36px;
width: 36px;
height: 36px;
color: #aaa; color: #aaa;
&.icon-none { &.icon-none {
@ -125,7 +124,7 @@
} }
.attachmentIconParent.hasPreview .showPreview, .attachmentIconParent.hasPreview .showPreview,
.attachmentIconParent.hasPreplay .showPreview { .attachmentIconParent.hasPreplay .showPreplay {
display: inline; display: inline;
cursor: pointer; cursor: pointer;
} }

View file

@ -57,7 +57,6 @@
&.disabled { &.disabled {
cursor: not-allowed; cursor: not-allowed;
opacity: 0.5; opacity: 0.5;
outline: 0;
} }
} }

View file

@ -20,10 +20,10 @@
.attachmentAreaParent { .attachmentAreaParent {
border-top: 1px solid #ccc; border-top: 1px solid #ccc;
overflow-y: auto; overflow-y: auto;
padding: 10px 10px 6px 10px; padding: 10px;
.no-attachments-desc { .no-attachments-desc {
padding: 50px 10px; padding-top: 50px;
text-align: center; text-align: center;
font-size: 24px; font-size: 24px;
color: #666; color: #666;
@ -38,7 +38,6 @@
.b-header-toolbar { .b-header-toolbar {
color: #fff;
background-color: rgba(0,0,0,0.8); background-color: rgba(0,0,0,0.8);
.close, .minimize-custom { .close, .minimize-custom {
@ -72,23 +71,17 @@
.b-header { .b-header {
padding: 10px; padding: 10px;
background-color: #eee; background-color: rgba(128,128,128,0.2);;
color: #333;
.e-identity { .e-identity {
color: #333; color: var(--main-color);
text-decoration: none; text-decoration: none;
font-weight: bold; font-weight: bold;
&:hover {
color: #333;
text-decoration: none;
}
&.multiply { &.multiply {
cursor: pointer; cursor: pointer;
border-bottom: 1px dashed #555; border-bottom: 1px dashed var(--main-color);
} }
} }

View file

@ -8,16 +8,11 @@
padding-top: 0; padding-top: 0;
width: 50px; width: 50px;
} }
.controls {
margin-left: 70px;
}
} }
&.modal { &.modal {
position: absolute; position: absolute;
right: 0; right: 0;
top: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
width: auto; width: auto;
@ -28,9 +23,9 @@
} }
.modal-body { .modal-body {
overflow: auto;
height: calc(100% - 49px); height: calc(100% - 49px);
padding: 0; padding: 0;
position: relative;
} }
.b-list-toolbar { .b-list-toolbar {
@ -71,10 +66,6 @@
overflow-y: auto; overflow-y: auto;
scroll-behavior: smooth; scroll-behavior: smooth;
.content {
-webkit-overflow-scrolling: touch;
}
.listClear { .listClear {
text-align: center; text-align: center;
padding: 10px; padding: 10px;
@ -184,29 +175,15 @@
overflow: auto; overflow: auto;
border-left: 1px solid rgba(128,128,128,0.25); border-left: 1px solid rgba(128,128,128,0.25);
.content { span {
-webkit-overflow-scrolling: touch;
}
.contactValueStatic, .contactValueLargeStatic, .contactValueTextAreaStatic {
height: 20px; height: 20px;
line-height: 20px; line-height: 20px;
font-size: 18px; display: none;
display: inline-block;
padding: 5px 7px; padding: 5px 7px;
font-size: 18px;
display: none; display: none;
} }
&.read-only {
.contactValueStatic, .contactValueLargeStatic, .contactValueTextAreaStatic {
display: inline-block;
}
.contactValueInput, .contactValueInputLarge, .contactValueTextArea {
display: none;
}
}
.b-contact-view-desc { .b-contact-view-desc {
text-align: center; text-align: center;
font-size: 24px; font-size: 24px;
@ -235,16 +212,12 @@
color: #aaa; color: #aaa;
} }
.contactValueStatic, .contactValueLargeStatic, .contactValueTextAreaStatic { input, textarea {
font-size: 18px;
display: none;
}
.contactValueInput, .contactValueInputLarge, .contactValueTextArea {
box-shadow: none; box-shadow: none;
border-color: #fff; border-color: #fff;
font-size: 18px; font-size: 18px;
width: 300px; width: 70vw;
max-width: 400px;
&:hover { &:hover {
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
@ -257,16 +230,8 @@
} }
} }
.contactValueTextArea {
width: 300px;
}
.contactValueInputLarge {
width: 400px;
}
.hasError { .hasError {
.contactValueInput { input {
color: #ee5f5b; color: #ee5f5b;
border-color: #ee5f5b; border-color: #ee5f5b;
} }
@ -293,11 +258,11 @@
} }
&.read-only { &.read-only {
.e-read-only-sign { span, .e-read-only-sign {
display: inline-block; display: inline-block;
} }
.e-share-sign { input, textarea, .e-share-sign {
display: none; display: none;
} }
} }

View file

@ -14,7 +14,7 @@
.emailaddresses.emailaddresses-focused { .emailaddresses.emailaddresses-focused {
background-color: #fff; background-color: #fff;
border: @rlInputBorderSize solid darken(@inputBorder, 20%); border: 1px solid darken(@inputBorder, 20%);
box-shadow: none; box-shadow: none;
} }
@ -49,7 +49,6 @@
.emailaddresses li a { .emailaddresses li a {
font-size: 12px; font-size: 12px;
color: #999; color: #999;
outline: 0;
padding: 1px; padding: 1px;
position: absolute; position: absolute;
right: 2px; right: 2px;
@ -73,7 +72,6 @@
height: auto; height: auto;
line-height: inherit; line-height: inherit;
margin: 0; margin: 0;
outline: 0;
padding: 0; padding: 0;
vertical-align: baseline; vertical-align: baseline;
} }

View file

@ -20,7 +20,7 @@
} }
.b-folders-user { .b-folders-user {
.e-link.print-count.system .badge { a.print-count.system .badge {
display: none !important; display: none !important;
} }
} }
@ -58,10 +58,6 @@
overflow: hidden; overflow: hidden;
overflow-y: auto; overflow-y: auto;
min-width: 100px; min-width: 100px;
.content {
-webkit-overflow-scrolling: touch;
}
} }
hr { hr {
@ -75,7 +71,7 @@
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
.e-link { a {
display: block; display: block;
position: relative; position: relative;
z-index: 1; z-index: 1;
@ -108,11 +104,8 @@
display: inline; display: inline;
} }
padding: 0; padding: 0 @folderItemPadding;
padding-left: @folderItemPadding;
padding-right: @folderItemPadding;
outline: 0;
text-decoration: none; text-decoration: none;
&.selectable { &.selectable {
@ -204,21 +197,21 @@
font-weight: bold; font-weight: bold;
} }
.b-sub-folders .e-item .e-link { .b-sub-folders .e-item a {
padding-left: @subPadding * 1 + @folderItemPadding; padding-left: @subPadding * 1 + @folderItemPadding;
} }
.b-sub-folders .b-sub-folders .e-item .e-link { .b-sub-folders .b-sub-folders .e-item a {
padding-left: @subPadding * 2 + @folderItemPadding; padding-left: @subPadding * 2 + @folderItemPadding;
} }
.b-sub-folders .b-sub-folders .b-sub-folders .e-item .e-link { .b-sub-folders .b-sub-folders .b-sub-folders .e-item a {
padding-left: @subPadding * 3 + @folderItemPadding; padding-left: @subPadding * 3 + @folderItemPadding;
} }
.b-sub-folders .b-sub-folders .b-sub-folders .b-sub-folders .e-item .e-link { .b-sub-folders .b-sub-folders .b-sub-folders .b-sub-folders .e-item a {
padding-left: @subPadding * 4 + @folderItemPadding; padding-left: @subPadding * 4 + @folderItemPadding;
} }
/**/ /**/
&.single-root-inbox .i-am-inbox.e-link { &.single-root-inbox a.i-am-inbox {
display: none !important; display: none !important;
} }
@ -229,16 +222,16 @@
} }
&.single-root-inbox .i-am-inbox-wrapper { &.single-root-inbox .i-am-inbox-wrapper {
.b-sub-folders .e-item .e-link { .b-sub-folders .e-item a {
padding-left: @folderItemPadding; padding-left: @folderItemPadding;
} }
.b-sub-folders .b-sub-folders .e-item .e-link { .b-sub-folders .b-sub-folders .e-item a {
padding-left: @subPadding * 1 + @folderItemPadding; padding-left: @subPadding * 1 + @folderItemPadding;
} }
.b-sub-folders .b-sub-folders .b-sub-folders .e-item .e-link { .b-sub-folders .b-sub-folders .b-sub-folders .e-item a {
padding-left: @subPadding * 2 + @folderItemPadding; padding-left: @subPadding * 2 + @folderItemPadding;
} }
.b-sub-folders .b-sub-folders .b-sub-folders .b-sub-folders .e-item .e-link { .b-sub-folders .b-sub-folders .b-sub-folders .b-sub-folders .e-item a {
padding-left: @subPadding * 3 + @folderItemPadding; padding-left: @subPadding * 3 + @folderItemPadding;
} }
} }

View file

@ -5,7 +5,7 @@
background-position: center; background-position: center;
} }
#rl-center, #rl-top, #rl-left, #rl-right { #rl-left, #rl-right {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
@ -17,23 +17,8 @@
z-index: 0; z-index: 0;
} }
#rl-content {
height: 100%;
width: 100%;
}
#rl-center {
min-width: 600px;
min-height: 400px;
}
html.rl-mobile { html.rl-mobile {
#rl-center {
min-width: 250px;
min-height: 250px;
}
&:not(.rl-left-panel-disabled) #rl-right { &:not(.rl-left-panel-disabled) #rl-right {
right: -150px; right: -150px;
} }
@ -43,11 +28,6 @@ html.rl-mobile {
} }
} }
#rl-top {
bottom: auto;
z-index: 2;
}
#rl-left { #rl-left {
width: @rlLeftWidth; width: @rlLeftWidth;
} }
@ -223,11 +203,10 @@ html:not(.rl-left-panel-disabled) #rl-left.resizable > .resizer,
z-index: 1100; z-index: 1100;
overflow: auto; overflow: auto;
background-color: rgba(0,0,0,0.3); background-color: rgba(0,0,0,0.3);
// -webkit-overflow-scrolling: touch;
} }
.b-settings-content { #rl-settings-subscreen {
padding:20px 20px 20px 30px; padding:20px;
} }
.dropdown-menu * + .dividerbar { .dropdown-menu * + .dividerbar {
@ -264,7 +243,7 @@ html:not(.rl-left-panel-disabled) #rl-left.resizable > .resizer,
} }
.btn-group > .btn.single { .btn-group > .btn.single {
border-radius: @btnBorderRadius !important; border-radius: 3px !important;
} }
/* desktop */ /* desktop */
@ -300,8 +279,8 @@ html:not(.rl-left-panel-disabled) #rl-left.resizable > .resizer,
left: 155px; left: 155px;
} }
.b-settings-content { #rl-settings-subscreen {
padding: 10px 10px 10px 20px; padding: 10px;
} }
.dropdown-menu a { .dropdown-menu a {
@ -324,7 +303,7 @@ html.rl-left-panel-disabled {
display: block; display: block;
} }
.opacity-on-panel-disabled { .b-content {
opacity: 0.3; opacity: 0.3;
} }

View file

@ -5,7 +5,7 @@
background-position: center; background-position: center;
} }
#rl-center, #rl-top, #rl-left, #rl-right { #rl-left, #rl-right {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
@ -17,21 +17,6 @@
z-index: 0; z-index: 0;
} }
#rl-content {
height: 100%;
width: 100%;
}
#rl-center {
min-width: 600px;
min-height: 400px;
}
#rl-top {
bottom: auto;
z-index: 2;
}
#rl-left { #rl-left {
width: @rlLeftWidth; width: @rlLeftWidth;
} }
@ -50,11 +35,10 @@
z-index: 1100; z-index: 1100;
overflow: auto; overflow: auto;
background-color: rgba(0,0,0,0.3); background-color: rgba(0,0,0,0.3);
// -webkit-overflow-scrolling: touch;
} }
.b-settings-content { #rl-settings-subscreen {
padding:20px 20px 20px 30px; padding: 20px;
} }
/* desktop */ /* desktop */
@ -77,13 +61,8 @@
/* mobile and tablet */ /* mobile and tablet */
@media screen and (max-width: 999px) { @media screen and (max-width: 999px) {
.b-settings-content { #rl-settings-subscreen {
padding: 10px 10px 10px 20px; padding: 10px;
}
#rl-center {
min-width: 250px;
min-height: 250px;
} }
html:not(.rl-left-panel-disabled) #rl-right { html:not(.rl-left-panel-disabled) #rl-right {
@ -92,11 +71,7 @@
html.rl-left-panel-disabled { html.rl-left-panel-disabled {
#rl-left { #rl-left {
width: 0px !important; width: 0 !important;
.opacity-on-panel-disabled {
opacity: 0.3;
}
} }
#rl-right { #rl-right {
left: 5px !important; left: 5px !important;

View file

@ -1,76 +1,100 @@
.RL-Login, .RL-AdminLogin {
.rl-view-model {
&.RL-Login, &.RL-AdminLogin {
position: fixed; position: fixed;
left: 50%; left: 50%;
top: 50%; top: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
width: 100%; width: 100%;
max-width: 450px; min-width: 250px;
max-width: 600px;
z-index: 5; z-index: 5;
}
} }
@glass-color: #fff !important; @glass-color: #fff;
@glass-error-color: #f76260 !important; @glass-error-color: #f76260;
@glass-m-color: rgba(255, 255, 255, .8) !important; @glass-m-color: rgba(255, 255, 255, .8);
.b-login-content { .b-login-content {
.loginFormWrapper {
position: relative; position: relative;
.descWrapper { .descWrapper {
color: var(--loading-color, #000);
font-size: 30px;
margin-bottom: 10px; margin-bottom: 10px;
text-align: center; text-align: center;
.desc {
font-size: 30px;
color: var(--loading-color, #000);
text-shadow: var(--loading-text-shadow); text-shadow: var(--loading-text-shadow);
} }
}
.loginForm {
background-color: var(--login-bg-color, rgba(0, 0, 0, .5));
border: var(--login-border, 1px solid rgba(255, 255, 255, .2));
border-radius: var(--login-border-radius, 7px);
box-shadow: var(--login-box-shadow);
margin: 0;
}
.input-append .add-on *,
.loginForm, .legend, .e-checkbox-icon, .g-ui-link, .language-button {
color: var(--login-color, @glass-m-color);
text-shadow: none !important;
}
.controls, .control-group { .controls, .control-group {
margin-bottom: 25px; margin-bottom: 25px;
} }
.wrapper { form {
background-color: var(--login-bg-color, rgba(0, 0, 0, .5));
border: var(--login-border, 1px solid rgba(255, 255, 255, .2));
border-radius: var(--login-border-radius, 7px);
box-shadow: var(--login-box-shadow);
color: var(--login-color, @glass-m-color);
margin: 0;
padding: 40px 40px 20px 40px; padding: 40px 40px 20px 40px;
position: relative;
* {
color: var(--login-color, @glass-m-color);
}
.controls.error {
.input-append .add-on,
.input-append .add-on * {
color: @glass-error-color;
}
input {
color: @glass-error-color;
border-color: @glass-error-color;
}
}
&.submitting > * {
opacity: 0.3;
}
&.submitting::after{
content: '';
position: absolute;
width: 60px;
height: 60px;
top: 50%;
left: 50%;
margin-top: -30px;
margin-left: -30px;
border: 8px solid transparent;
border-top-color: var(--spinner-color, #fff);
animation: loginRotation 1s infinite ease-in-out;
border-radius: 50%;
z-index: 1052;
}
} }
.controls { .controls {
.inputLoginForm, .inputEmail, .inputLogin, .inputPassword { .inputLoginForm, .inputLogin, .inputPassword {
font-size: 18px; font-size: 18px;
height: 40px; height: 40px;
line-height: 20px; line-height: 20px;
padding-left: 12px; padding-left: 12px;
padding-right: 12px; padding-right: 12px;
} }
.inputEmail, .inputLogin, .inputPassword { .inputLogin, .inputPassword {
padding-right: 35px; padding-right: 35px;
} }
input, .btn { input, .btn {
border: 1px solid none !important; border: 1px solid none;
background: none !important; background: none !important;
outline: none !important; text-shadow: none;
text-shadow: none !important; box-shadow: none;
box-shadow: none !important;
color: @glass-color; color: @glass-color;
border-color: @glass-m-color; border-color: @glass-m-color;
@ -80,7 +104,7 @@
&:placeholder { &:placeholder {
color: @glass-color; color: @glass-color;
text-shadow: none !important; text-shadow: none;
} }
&:focus, &:hover { &:focus, &:hover {
@ -97,17 +121,6 @@
border-color: @glass-color; border-color: @glass-color;
} }
} }
&.error {
.input-append .add-on * {
color: @glass-error-color;
}
input {
color: @glass-error-color;
border-color: @glass-error-color;
}
}
} }
.signMeLabel .e-checkbox { .signMeLabel .e-checkbox {
@ -115,25 +128,18 @@
} }
.input-append .add-on { .input-append .add-on {
position: relative; margin-left: -1.5em;
height: 30px; position: absolute;
background: none; font-size: 17px;
margin-left: -35px; line-height: 29px;
z-index: 1000; right: 6px;
border: 0; top: 6px;
z-index: 6;
} }
.input-append .add-on * { .input-append .add-on * {
font-size: 17px; font-size: 17px;
line-height: 29px; line-height: 29px;
outline: none !important;
box-shadow: none !important;
}
.controls.error .add-on *,
.control-group.error .add-on * {
color: #b94a48;
}
} }
.buttonLogin { .buttonLogin {
@ -163,14 +169,13 @@
.language-button { .language-button {
padding: 5px; padding: 5px;
outline: none;
text-decoration: none; text-decoration: none;
} }
} }
@media screen and (max-width: 480px) { @media screen and (max-width: 480px) {
.b-login-content .loginFormWrapper { .b-login-content {
.wrapper { form {
padding: 30px 4vw 10px; padding: 30px 4vw 10px;
} }
} }
@ -180,36 +185,11 @@
to {transform: rotate(1turn);} to {transform: rotate(1turn);}
} }
.submitting-pane {
position: relative;
&.submitting > * {
opacity: 0.3;
}
html &.submitting::after{
content: '';
position: absolute;
width: 60px;
height: 60px;
top: 50%;
left: 50%;
margin-top: -30px;
margin-left: -30px;
border: 8px solid transparent;
border-top-color: var(--spinner-color, #fff);
animation: loginRotation 1s infinite ease-in-out;
border-radius: 50%;
z-index: 1052;
}
}
.btn-submit-icon-wrp { .btn-submit-icon-wrp {
border: none; border: none;
background: none; background: none;
display: inline-block; display: inline-block;
margin: 0; margin: 0;
padding: 0; padding: 0;
outline: none;
cursor: pointer; cursor: pointer;
} }

View file

@ -19,7 +19,7 @@ html.rl-no-preview-pane {
right: 0; right: 0;
left: 0; left: 0;
height: 30px; height: 30px;
padding: 10px @rlMainBorderSize; padding: 10px 1px;
z-index: 102; z-index: 102;
white-space: nowrap; white-space: nowrap;
} }
@ -64,7 +64,7 @@ html.rl-no-preview-pane {
left: 0; left: 0;
bottom: @rlBottomMargin; bottom: @rlBottomMargin;
border: @rlMainBorderSize solid @rlMainDarkColor; border: 1px solid @rlMainDarkColor;
box-shadow: @rlMainShadow; box-shadow: @rlMainShadow;
border-radius: @rlMainBorderRadius; border-radius: @rlMainBorderRadius;
@ -90,7 +90,7 @@ html.rl-no-preview-pane {
.checkboxCheckAll { .checkboxCheckAll {
cursor: pointer; cursor: pointer;
line-height: 30px; vertical-align: middle
} }
.input-append { .input-append {
@ -134,10 +134,6 @@ html.rl-no-preview-pane {
background-color: #fff; background-color: #fff;
.content {
-webkit-overflow-scrolling: touch;
}
.listClear { .listClear {
text-align: center; text-align: center;
padding: 10px; padding: 10px;

View file

@ -1,4 +1,3 @@
.g-ui-min-height-300 { .g-ui-min-height-300 {
min-height: 300px; min-height: 300px;
} }
@ -24,9 +23,9 @@
right: @rlLowMargin; right: @rlLowMargin;
left: -2px; left: -2px;
/*overflow: hidden;*/ /*overflow: hidden;*/
border: @rlLowBorderSize solid @rlMainDarkColor; border: 1px solid @rlMainDarkColor;
border-radius: @rlLowBorderRadius; border-radius: @rlMainBorderRadius;
background-color: #fff; background-color: #fff;
@ -57,10 +56,6 @@
color: #DA4F49; color: #DA4F49;
} }
.content {
-webkit-overflow-scrolling: touch;
}
.message-fixed-button-toolbar { .message-fixed-button-toolbar {
z-index: 100; z-index: 100;
position: absolute; position: absolute;
@ -96,11 +91,8 @@
padding: 10px; padding: 10px;
background-color: #f8f8f8; background-color: #f8f8f8;
border-top: 0;
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
border-top-right-radius: 5px; border-radius: @rlMainBorderRadius @rlMainBorderRadius 0 0;
border-top-left-radius: 5px;
z-index: 1;
.subjectParent { .subjectParent {
font-size: 18px; font-size: 18px;
@ -109,9 +101,6 @@
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
margin-bottom: 8px; margin-bottom: 8px;
line-height: 100%;
height: 22px;
vertical-align: middle;
} }
.messageButtons { .messageButtons {
@ -190,8 +179,6 @@
left: 0; left: 0;
right: 0; right: 0;
overflow: auto; overflow: auto;
-webkit-overflow-scrolling: touch;
border-radius: @rlLowBorderRadius;
scroll-behavior: smooth; scroll-behavior: smooth;
.buttonFull { .buttonFull {
@ -294,13 +281,10 @@
} }
.rlBlockquoteSwitcher { .rlBlockquoteSwitcher {
background-color: #eee;
border: 1px solid #999; border: 1px solid #999;
display: block; display: block;
width: 30px; width: 3em;
height: 14px; line-height: 1em;
line-height: 14px;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
margin: 2em 0 10px; margin: 2em 0 10px;
@ -311,10 +295,6 @@
} }
} }
.bodySubHeader {
z-index: 2;
}
.b-text-part { .b-text-part {
height: 100%; height: 100%;
@ -458,19 +438,21 @@
display: inline-block; display: inline-block;
cursor: pointer; cursor: pointer;
color: #777; opacity: 0.5;
&:hover { &:hover {
color: #111; opacity: 1;
} }
&.success { &.success {
color: green; color: green;
cursor: help; cursor: help;
opacity: 1;
} }
&.error { &.error {
color: red; color: red;
opacity: 1;
} }
} }
} }
@ -479,7 +461,6 @@
&.message-focused .b-content { &.message-focused .b-content {
z-index: 101; z-index: 101;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
border-radius: @rlLowBorderRadius;
border-color: darken(@rlMainDarkColor, 5%); border-color: darken(@rlMainDarkColor, 5%);
} }
@ -528,7 +509,7 @@ html.rl-no-preview-pane .messageView {
} }
.toolbar { .toolbar {
padding-left: @rlMainBorderSize; padding-left: 1px;
} }
.b-content { .b-content {
@ -536,9 +517,8 @@ html.rl-no-preview-pane .messageView {
left: 0; left: 0;
bottom: @rlBottomMargin; bottom: @rlBottomMargin;
right: @rlBottomMargin; right: @rlBottomMargin;
border: @rlMainBorderSize solid @rlMainDarkColor; border: 1px solid @rlMainDarkColor;
box-shadow: @rlMainShadow; box-shadow: @rlMainShadow;
border-radius: @rlMainBorderRadius;
} }
} }
@ -577,8 +557,7 @@ html.rl-message-fullscreen {
right: 0 !important; right: 0 !important;
bottom: 0 !important; bottom: 0 !important;
z-index: 10000 !important; z-index: 10000 !important;
border: @rlLowBorderSize solid @rlMainDarkColor !important; border: 1px solid @rlMainDarkColor !important;
border-radius: @rlLowBorderRadius !important;
} }
} }

View file

@ -1,21 +1,11 @@
.b-settins-left { .b-settins-left {
.b-toolbar {
position: absolute;
top: 0;
right: 0;
left: 0;
height: 34px;
padding: 8px 0 0 @rlLowMargin;
}
.b-footer { .b-footer {
position: absolute; position: absolute;
bottom: 20px; bottom: 10px;
right: 0; right: 0;
left: 0; left: 0;
height: 20px;
padding: 0 10px 0 5px; padding: 0 10px 0 5px;
z-index: 101; z-index: 101;
} }
@ -27,50 +17,29 @@
left: 0; left: 0;
right: 0; right: 0;
overflow: hidden; overflow: hidden;
.content {
-webkit-overflow-scrolling: touch;
}
}
}
.b-settings-menu {
.e-item {
overflow: hidden;
text-decoration: none;
outline: 0;
} }
.e-link { nav {
position: relative;
a {
background-color: transparent;
color: var(--settings-menu-color, #333);
cursor: pointer;
display: block; display: block;
font-size: 18px;
height: 30px; height: 30px;
line-height: 29px; line-height: 29px;
font-size: 18px; overflow: hidden;
z-index: 1;
cursor: default;
background-color: transparent;
color: var(--settings-menu-disabled-color, #666);
padding: 4px 10px; padding: 4px 10px;
outline: 0;
text-decoration: none; text-decoration: none;
} }
.e-item.selectable .e-link { a:focus, a:hover {
cursor: pointer;
color: var(--settings-menu-color, #333);
}
.e-item.selectable {
&:hover .e-link{
background-color: var(--settings-menu-hover-bg-color, #333); background-color: var(--settings-menu-hover-bg-color, #333);
color: var(--settings-menu-hover-color, #eee); color: var(--settings-menu-hover-color, #eee);
} }
&.selected .e-link {
a.selected {
background-color: var(--settings-menu-selected-bg-color, #333); background-color: var(--settings-menu-selected-bg-color, #333);
color: var(--settings-menu-selected-color, #eee); color: var(--settings-menu-selected-color, #eee);
} }
@ -85,7 +54,7 @@
right: 0; right: 0;
left: 0; left: 0;
height: 34px; height: 34px;
padding: 8px 5px; padding: 8px 0;
color: #fff; color: #fff;
} }
@ -100,13 +69,9 @@
scroll-behavior: smooth; scroll-behavior: smooth;
background-color: #fff; background-color: #fff;
border: @rlMainBorderSize solid @rlMainDarkColor; border: 1px solid @rlMainDarkColor;
box-shadow: @rlMainShadow; box-shadow: @rlMainShadow;
border-radius: @rlMainBorderRadius; border-radius: @rlMainBorderRadius;
.content {
-webkit-overflow-scrolling: touch;
}
} }
td { td {

View file

@ -2,7 +2,6 @@
.squire-toolbar { .squire-toolbar {
padding: 2px; padding: 2px;
border-bottom: 1px solid #b6b6b6; border-bottom: 1px solid #b6b6b6;
background: #EEE;
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
white-space: nowrap; white-space: nowrap;
@ -143,7 +142,6 @@ Secondly, we can't rely on MUA's what to do with :empty
border-radius: 0; border-radius: 0;
display: none; display: none;
font-family: var(--fontMono); font-family: var(--fontMono);
outline: none;
margin: 0; margin: 0;
resize: none; resize: none;
white-space: pre-wrap; white-space: pre-wrap;
@ -162,6 +160,7 @@ Secondly, we can't rely on MUA's what to do with :empty
display: none; display: none;
} }
.rl-mobile .squire-toolbar { .rl-mobile .squire-toolbar {
background: #EEE;
padding: 1px; padding: 1px;
position: fixed; position: fixed;
bottom: 0; bottom: 0;

View file

@ -23,7 +23,7 @@
.email-title { .email-title {
display: inline-block; display: inline-block;
max-width: 200px; max-width: 50vw;
text-align: left; text-align: left;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
@ -61,41 +61,27 @@
} }
.accountPlace { .accountPlace {
background-color: #000;
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.5);
border-radius: 4px;
color: #fff; color: #fff;
text-shadow: 0 1px 0 #000;
display: inline-block; display: inline-block;
height: 29px;
max-width: 250px;
border-radius: 4px;
font-size: 16px;
line-height: 30px;
padding: 1px 8px;
overflow: hidden;
text-overflow: ellipsis;
border-radius: 4px;
font-weight: bold; font-weight: bold;
font-size: 16px;
height: 29px;
line-height: 30px;
max-width: 25vw;
overflow: hidden;
padding: 1px 8px;
text-overflow: ellipsis;
text-shadow: 0 1px 0 #000;
white-space: nowrap; white-space: nowrap;
} }
.account-item { .account-item {
&:not(.current) i:first-child,
.icon-ok { &.current i + i {
display: none; display: none;
} }
&.current {
.icon-ok {
display: inline-block;
}
.icon-user {
display: none;
}
}
} }
.counter { .counter {
@ -107,13 +93,6 @@
} }
} }
/* mobile */
@media screen and (max-width: 767px) {
.accountPlace {
max-width: 150px !important;
}
}
@keyframes firstBar { @keyframes firstBar {
0% { height: 30%; } 0% { height: 30%; }
50% { height: 100%; } 50% { height: 100%; }

View file

@ -17,7 +17,7 @@
top: 100%; top: 100%;
transition: opacity 0.2s ease-in-out; transition: opacity 0.2s ease-in-out;
white-space: nowrap; white-space: nowrap;
z-index: 2001 !important; z-index: 2001;
} }
@media only screen and (max-width: 600px) { @media only screen and (max-width: 600px) {
[data-rainloopErrorTip]::before { [data-rainloopErrorTip]::before {

View file

@ -1,11 +1,7 @@
@rlLeftWidth: 200px; @rlLeftWidth: 200px;
@rlInputBorderSize: 1px;
@rlMainBorderSize: 1px;
@rlLowBorderSize: 1px;
@rlMainBorderRadius: 5px; @rlMainBorderRadius: 5px;
@rlLowBorderRadius: 3px;
@rlMainShadow: 0 2px 8px rgba(0, 0, 0, 0.2); @rlMainShadow: 0 2px 8px rgba(0, 0, 0, 0.2);
@ -13,6 +9,3 @@
@rlBottomMargin: 5px; @rlBottomMargin: 5px;
@rlMainDarkColor: #aaa; @rlMainDarkColor: #aaa;
// bootstart
@btnBorderRadius: 3px;

View file

@ -1,33 +0,0 @@
#progressjs {
left: 0;
position: fixed;
top: 0;
width: 100%;
z-index: 2000;
}
.progressjs-inner {
background-color: #939595;
height: 3px;
overflow: hidden;
position: relative;
transition: width .5s;
width: 0;
z-index: 2000;
}
.progressjs-percent {
position: absolute;
top: 0;
left: 0;
right: -32px;
bottom: 0;
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.3) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.3) 50%, rgba(255, 255, 255, 0.3) 75%, transparent 75%, transparent);
background-size: 32px 32px;
animation: simple-pace-stripe-animation 500ms linear infinite;
}
@keyframes simple-pace-stripe-animation {
0% { transform: none; }
100% { transform: translate(-32px, 0); }
}

View file

@ -21,8 +21,6 @@ class LoginAdminView extends AbstractViewCenter {
loginError: false, loginError: false,
passwordError: false, passwordError: false,
formHidden: false,
submitRequest: false, submitRequest: false,
submitError: '' submitError: ''
}); });

View file

@ -1,6 +1,3 @@
import { Scope } from 'Common/Enums';
import { settingsMenuKeysHandler } from 'Knoin/Knoin';
import { AbstractViewLeft } from 'Knoin/AbstractViews'; import { AbstractViewLeft } from 'Knoin/AbstractViews';
class MenuSettingsAdminView extends AbstractViewLeft { class MenuSettingsAdminView extends AbstractViewLeft {
@ -16,11 +13,6 @@ class MenuSettingsAdminView extends AbstractViewLeft {
link(route) { link(route) {
return '#/' + route; return '#/' + route;
} }
onBuild(dom) {
shortcuts.add('arrowup,arrowdown', '', Scope.Settings,
settingsMenuKeysHandler(dom.querySelectorAll('.b-admin-menu .e-item')));
}
} }
export { MenuSettingsAdminView }; export { MenuSettingsAdminView };

View file

@ -419,7 +419,24 @@ class ComposePopupView extends AbstractViewPopup {
setFolderHash(sSentFolder, ''); setFolderHash(sSentFolder, '');
Remote.sendMessage( Remote.sendMessage(
this.sendMessageResponse.bind(this), (iError, data) => {
this.sending(false);
if (this.modalVisibility()) {
if (iError) {
if (Notification.CantSaveMessage === iError) {
this.sendSuccessButSaveError(true);
this.savedErrorDesc(i18n('COMPOSE/SAVED_ERROR_ON_SEND').trim());
} else {
this.sendError(true);
this.sendErrorDesc(getNotification(iError, data && data.ErrorMessage)
|| getNotification(Notification.CantSendMessage));
}
} else {
this.closeCommand && this.closeCommand();
}
}
this.reloadDraftFolder();
},
this.getMessageRequestParams(sSentFolder) this.getMessageRequestParams(sSentFolder)
); );
} }
@ -438,7 +455,40 @@ class ComposePopupView extends AbstractViewPopup {
setFolderHash(FolderUserStore.draftFolder(), ''); setFolderHash(FolderUserStore.draftFolder(), '');
Remote.saveMessage( Remote.saveMessage(
this.saveMessageResponse.bind(this), (iError, oData) => {
let result = false;
this.saving(false);
if (!iError) {
if (oData.Result.NewFolder && oData.Result.NewUid) {
result = true;
if (this.bFromDraft) {
const message = MessageUserStore.message();
if (message && this.draftFolder() === message.folder && this.draftUid() === message.uid) {
MessageUserStore.message(null);
}
}
this.draftFolder(oData.Result.NewFolder);
this.draftUid(oData.Result.NewUid);
this.savedTime(new Date);
if (this.bFromDraft) {
setFolderHash(this.draftFolder(), '');
}
}
}
if (!result) {
this.savedError(true);
this.savedErrorDesc(getNotification(Notification.CantSaveMessage));
}
this.reloadDraftFolder();
},
this.getMessageRequestParams(FolderUserStore.draftFolder()) this.getMessageRequestParams(FolderUserStore.draftFolder())
); );
} }
@ -578,63 +628,6 @@ class ComposePopupView extends AbstractViewPopup {
} }
} }
sendMessageResponse(iError, data) {
this.sending(false);
if (this.modalVisibility()) {
if (iError) {
if (Notification.CantSaveMessage === iError) {
this.sendSuccessButSaveError(true);
this.savedErrorDesc(i18n('COMPOSE/SAVED_ERROR_ON_SEND').trim());
} else {
this.sendError(true);
this.sendErrorDesc(getNotification(iError, data && data.ErrorMessage)
|| getNotification(Notification.CantSendMessage));
}
} else {
this.closeCommand && this.closeCommand();
}
}
this.reloadDraftFolder();
}
saveMessageResponse(iError, oData) {
let result = false;
this.saving(false);
if (!iError) {
if (oData.Result.NewFolder && oData.Result.NewUid) {
result = true;
if (this.bFromDraft) {
const message = MessageUserStore.message();
if (message && this.draftFolder() === message.folder && this.draftUid() === message.uid) {
MessageUserStore.message(null);
}
}
this.draftFolder(oData.Result.NewFolder);
this.draftUid(oData.Result.NewUid);
this.savedTime(new Date);
if (this.bFromDraft) {
setFolderHash(this.draftFolder(), '');
}
}
}
if (!result) {
this.savedError(true);
this.savedErrorDesc(getNotification(Notification.CantSaveMessage));
}
this.reloadDraftFolder();
}
onHide() { onHide() {
// Stop autosave // Stop autosave
clearTimeout(this.iTimer); clearTimeout(this.iTimer);
@ -748,12 +741,6 @@ class ComposePopupView extends AbstractViewPopup {
} }
} }
onWarmUp() {
if (this.modalVisibility && !this.modalVisibility()) {
this.editor(editor => editor.modeWysiwyg());
}
}
/** /**
* @param {Function} fKoValue * @param {Function} fKoValue
* @param {Array} emails * @param {Array} emails
@ -1146,6 +1133,8 @@ class ComposePopupView extends AbstractViewPopup {
ro.header = dom.querySelector('.b-header'); ro.header = dom.querySelector('.b-header');
ro.toolbar = dom.querySelector('.b-header-toolbar'); ro.toolbar = dom.querySelector('.b-header-toolbar');
ro.els = [dom.querySelector('.textAreaParent'), dom.querySelector('.attachmentAreaParent')]; ro.els = [dom.querySelector('.textAreaParent'), dom.querySelector('.attachmentAreaParent')];
this.editor(editor => editor.modeWysiwyg());
} }
/** /**

View file

@ -90,7 +90,7 @@ class ContactsPopupView extends AbstractViewPopup {
this.bDropPageAfterDelete = false; this.bDropPageAfterDelete = false;
// this.saveCommandDebounce = _.debounce(this.saveCommand.bind(this), 1000); // this.saveCommandDebounce = this.saveCommand.bind(this).debounce(1000);
const const
// propertyFocused = property => !property.isValid() && !property.focused(), // propertyFocused = property => !property.isValid() && !property.focused(),
@ -382,23 +382,15 @@ class ContactsPopupView extends AbstractViewPopup {
deleteSelectedContacts() { deleteSelectedContacts() {
if (this.contactsCheckedOrSelected().length) { if (this.contactsCheckedOrSelected().length) {
Remote.contactsDelete(this.deleteResponse.bind(this), this.contactsCheckedOrSelectedUids()); Remote.contactsDelete((iError, oData) => {
this.removeCheckedOrSelectedContactsFromList();
}
}
/**
* @param {int} iError
* @param {FetchJsonDefaultResponse} oData
*/
deleteResponse(iError, oData) {
if (500 < (!iError && oData && oData.Time ? pInt(oData.Time) : 0)) { if (500 < (!iError && oData && oData.Time ? pInt(oData.Time) : 0)) {
this.reloadContactList(this.bDropPageAfterDelete); this.reloadContactList(this.bDropPageAfterDelete);
} else { } else {
setTimeout(() => { setTimeout(() => this.reloadContactList(this.bDropPageAfterDelete), 500);
this.reloadContactList(this.bDropPageAfterDelete); }
}, 500); }, this.contactsCheckedOrSelectedUids());
this.removeCheckedOrSelectedContactsFromList();
} }
} }

View file

@ -187,25 +187,7 @@ class DomainPopupView extends AbstractViewPopup {
this.testing(true); this.testing(true);
Remote.testConnectionForDomain( Remote.testConnectionForDomain(
this.onTestConnectionResponse.bind(this), (iError, oData) => {
this
);
}
whiteListCommand() {
this.page('white-list');
}
backCommand() {
this.page('main');
}
sieveCommand() {
this.sieveSettings(!this.sieveSettings());
this.clearTesting();
}
onTestConnectionResponse(iError, oData) {
this.testing(false); this.testing(false);
if (iError) { if (iError) {
this.testingImapError(true); this.testingImapError(true);
@ -246,6 +228,22 @@ class DomainPopupView extends AbstractViewPopup {
this.sieveSettings(true); this.sieveSettings(true);
} }
} }
},
this
);
}
whiteListCommand() {
this.page('white-list');
}
backCommand() {
this.page('main');
}
sieveCommand() {
this.sieveSettings(!this.sieveSettings());
this.clearTesting();
} }
onDomainCreateOrSaveResponse(iError) { onDomainCreateOrSaveResponse(iError) {

View file

@ -27,8 +27,7 @@ class LanguagesPopupView extends AbstractViewPopup {
} }
languageTooltipName(language) { languageTooltipName(language) {
const result = convertLangName(language, true); return convertLangName(language, true);
return convertLangName(language, false) === result ? '' : result;
} }
setLanguageSelection() { setLanguageSelection() {

View file

@ -14,8 +14,6 @@ class PluginPopupView extends AbstractViewPopup {
constructor() { constructor() {
super('Plugin'); super('Plugin');
this.onPluginSettingsUpdateResponse = this.onPluginSettingsUpdateResponse.bind(this);
this.addObservables({ this.addObservables({
saveError: '', saveError: '',
name: '', name: '',
@ -50,15 +48,11 @@ class PluginPopupView extends AbstractViewPopup {
}); });
this.saveError(''); this.saveError('');
Remote.pluginSettingsUpdate(this.onPluginSettingsUpdateResponse, list); Remote.pluginSettingsUpdate(iError =>
} iError
? this.saveError(getNotification(iError))
onPluginSettingsUpdateResponse(iError) { : this.cancelCommand()
if (iError) { , list);
this.saveError(getNotification(iError));
} else {
this.cancelCommand();
}
} }
onShow(oPlugin) { onShow(oPlugin) {

View file

@ -46,8 +46,6 @@ class LoginUserView extends AbstractViewCenter {
emailError: false, emailError: false,
passwordError: false, passwordError: false,
formHidden: false,
submitRequest: false, submitRequest: false,
submitError: '', submitError: '',
submitErrorAddidional: '', submitErrorAddidional: '',
@ -57,9 +55,6 @@ class LoginUserView extends AbstractViewCenter {
signMeType: LoginSignMeType.Unused signMeType: LoginSignMeType.Unused
}); });
this.forgotPasswordLinkUrl = Settings.app('forgotPasswordLinkUrl');
this.registrationLinkUrl = Settings.app('registrationLinkUrl');
this.formError = ko.observable(false).extend({ falseTimeout: 500 }); this.formError = ko.observable(false).extend({ falseTimeout: 500 });
this.allowLanguagesOnLogin = !!SettingsGet('AllowLanguagesOnLogin'); this.allowLanguagesOnLogin = !!SettingsGet('AllowLanguagesOnLogin');
@ -127,7 +122,8 @@ class LoginUserView extends AbstractViewCenter {
if (Notification.InvalidInputArgument == iError) { if (Notification.InvalidInputArgument == iError) {
iError = Notification.AuthError; iError = Notification.AuthError;
} }
this.submitError(getNotification(iError, oData.ErrorMessage, Notification.UnknownNotification)); this.submitError(getNotification(iError, (oData ? oData.ErrorMessage : ''),
Notification.UnknownNotification));
this.submitErrorAddidional((oData && oData.ErrorMessageAdditional) || ''); this.submitErrorAddidional((oData && oData.ErrorMessageAdditional) || '');
} else { } else {
rl.route.reload(); rl.route.reload();

View file

@ -108,16 +108,16 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
} }
} }
el = eqs(event, '.b-folders .e-item .e-link.selectable .inbox-star-icon'); el = eqs(event, '.b-folders .e-item a.selectable .inbox-star-icon');
el && fSelectFolder(el, event, !this.isInboxStarred()); el && fSelectFolder(el, event, !this.isInboxStarred());
el = eqs(event, '.b-folders .e-item .e-link.selectable'); el = eqs(event, '.b-folders .e-item a.selectable');
el && fSelectFolder(el, event, false); el && fSelectFolder(el, event, false);
}); });
shortcuts.add('arrowup,arrowdown', '', Scope.FolderList, event => { shortcuts.add('arrowup,arrowdown', '', Scope.FolderList, event => {
let items = [], index = 0; let items = [], index = 0;
dom.querySelectorAll('.b-folders .e-item .e-link:not(.hidden)').forEach(node => { dom.querySelectorAll('.b-folders .e-item a:not(.hidden)').forEach(node => {
if (node.offsetHeight || node.getClientRects().length) { if (node.offsetHeight || node.getClientRects().length) {
items.push(node); items.push(node);
if (node.matches('.focused')) { if (node.matches('.focused')) {
@ -140,7 +140,7 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
}); });
shortcuts.add('enter,open', '', Scope.FolderList, () => { shortcuts.add('enter,open', '', Scope.FolderList, () => {
const item = qs('.b-folders .e-item .e-link:not(.hidden).focused'); const item = qs('.b-folders .e-item a:not(.hidden).focused');
if (item) { if (item) {
AppUserStore.focusedState(Scope.MessageList); AppUserStore.focusedState(Scope.MessageList);
item.click(); item.click();
@ -150,7 +150,7 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
}); });
shortcuts.add('space', '', Scope.FolderList, () => { shortcuts.add('space', '', Scope.FolderList, () => {
const item = qs('.b-folders .e-item .e-link:not(.hidden).focused'), const item = qs('.b-folders .e-item a:not(.hidden).focused'),
folder = item && ko.dataFor(item); folder = item && ko.dataFor(item);
if (folder) { if (folder) {
const collapsed = folder.collapsed(); const collapsed = folder.collapsed();
@ -169,11 +169,11 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
}); });
AppUserStore.focusedState.subscribe(value => { AppUserStore.focusedState.subscribe(value => {
let el = qs('.b-folders .e-item .e-link.focused'); let el = qs('.b-folders .e-item a.focused');
el && qs('.b-folders .e-item .e-link.focused').classList.remove('focused'); el && qs('.b-folders .e-item a.focused').classList.remove('focused');
if (Scope.FolderList === value) { if (Scope.FolderList === value) {
el = qs('.b-folders .e-item .e-link.selected'); el = qs('.b-folders .e-item a.selected');
el && qs('.b-folders .e-item .e-link.selected').classList.add('focused'); el && qs('.b-folders .e-item a.selected').classList.add('focused');
} }
}); });
} }
@ -181,7 +181,7 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
scrollToFocused() { scrollToFocused() {
const scrollable = this.oContentScrollable; const scrollable = this.oContentScrollable;
if (scrollable) { if (scrollable) {
let block, focused = scrollable.querySelector('.e-item .e-link.focused'); let block, focused = scrollable.querySelector('.e-item a.focused');
if (focused) { if (focused) {
const fRect = focused.getBoundingClientRect(), const fRect = focused.getBoundingClientRect(),
sRect = scrollable.getBoundingClientRect(); sRect = scrollable.getBoundingClientRect();

View file

@ -263,8 +263,6 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
addEventListener('mailbox.message-view.toggle-full-screen', () => this.toggleFullScreen()); addEventListener('mailbox.message-view.toggle-full-screen', () => this.toggleFullScreen());
this.attachmentPreview = this.attachmentPreview.bind(this);
decorateKoCommands(this, { decorateKoCommands(this, {
closeMessageCommand: 1, closeMessageCommand: 1,
messageEditCommand: self => self.messageVisibility(), messageEditCommand: self => self.messageVisibility(),
@ -313,57 +311,6 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
this.oHeaderDom && this.viewBodyTopValue(this.message() ? this.oHeaderDom.offsetHeight : 0); this.oHeaderDom && this.viewBodyTopValue(this.message() ? this.oHeaderDom.offsetHeight : 0);
} }
// displayMailToPopup(sMailToUrl) {
// sMailToUrl = sMailToUrl.replace(/\?.+$/, '');
//
// var
// sResult = '',
// aTo = [],
// EmailModel = require('Model/Email').default,
// fParseEmailLine = function(sLine) {
// return sLine ? [decodeURIComponent(sLine)].map(sItem => {
// var oEmailModel = new EmailModel();
// oEmailModel.parse(sItem);
// return oEmailModel.email ? oEmailModel : null;
// }).filter(v => v) : null;
// }
// ;
//
// aTo = fParseEmailLine(sMailToUrl);
// sResult = aTo && aTo[0] ? aTo[0].email : '';
//
// return sResult;
// }
/**
* @param {Object} oAttachment
* @returns {boolean}
*/
attachmentPreview(/*attachment*/) {
/*
if (attachment && attachment.isImage() && !attachment.isLinked && this.message() && this.message().attachments()) {
const items = this.message().attachments.map(item => {
if (item && !item.isLinked && item.isImage()) {
if (item === attachment) {
index = listIndex;
}
++listIndex;
return {
src: item.linkPreview(),
msrc: item.linkThumbnail(),
title: item.fileName
};
}
return null;
}).filter(v => v);
if (items.length) {
}
}
*/
return true;
}
onBuild(dom) { onBuild(dom) {
this.fullScreenMode.subscribe(value => value && this.message() && AppUserStore.focusedState(Scope.MessageView)); this.fullScreenMode.subscribe(value => value && this.message() && AppUserStore.focusedState(Scope.MessageView));
@ -476,10 +423,9 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
this.initShortcuts(); this.initShortcuts();
} }
/** initShortcuts() {
* @returns {boolean} // exit fullscreen, back
*/ shortcuts.add('escape,backspace', '', Scope.MessageView, () => {
escShortcuts() {
if (this.viewModelVisible && this.message()) { if (this.viewModelVisible && this.message()) {
const preview = SettingsUserStore.usePreviewPane(); const preview = SettingsUserStore.usePreviewPane();
if (this.fullScreenMode()) { if (this.fullScreenMode()) {
@ -498,11 +444,7 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
} }
return true; return true;
} });
initShortcuts() {
// exit fullscreen, back
shortcuts.add('escape,backspace', '', Scope.MessageView, this.escShortcuts.bind(this));
// fullscreen // fullscreen
shortcuts.add('enter,open', '', Scope.MessageView, () => { shortcuts.add('enter,open', '', Scope.MessageView, () => {

View file

@ -1,9 +1,7 @@
import { Scope } from 'Common/Enums';
import { leftPanelDisabled } from 'Common/Globals'; import { leftPanelDisabled } from 'Common/Globals';
import { settings, mailbox } from 'Common/Links'; import { settings, mailbox } from 'Common/Links';
import { getFolderInboxName } from 'Common/Cache'; import { getFolderInboxName } from 'Common/Cache';
import { settingsMenuKeysHandler } from 'Knoin/Knoin';
import { AbstractViewLeft } from 'Knoin/AbstractViews'; import { AbstractViewLeft } from 'Knoin/AbstractViews';
import { ThemeStore } from 'Stores/Theme'; import { ThemeStore } from 'Stores/Theme';
@ -23,12 +21,9 @@ export class MenuSettingsUserView extends AbstractViewLeft {
onBuild(dom) { onBuild(dom) {
dom.addEventListener('click', event => dom.addEventListener('click', event =>
ThemeStore.isMobile() ThemeStore.isMobile()
&& event.target.closestWithin('.b-settings-menu .e-item.selectable', dom) && event.target.closestWithin('.b-settins-left nav a', dom)
&& leftPanelDisabled(true) && leftPanelDisabled(true)
); );
shortcuts.add('arrowup,arrowdown', '', Scope.Settings,
settingsMenuKeysHandler(dom.querySelectorAll('.b-settings-menu .e-item')));
} }
link(route) { link(route) {

View file

@ -38,17 +38,10 @@ const
} }
return win[name]; return win[name];
}, },
STORAGE_KEY = '__rlA',
TIME_KEY = '__rlT',
AUTH_KEY = 'AuthAccountHash',
storage = Storage('session'),
timestamp = () => Math.round(Date.now() / 1000),
setTimestamp = () => storage.setItem(TIME_KEY, timestamp()),
showError = () => { showError = () => {
eId('rl-loading').hidden = true; eId('rl-loading').hidden = true;
eId('rl-loading-error').hidden = false; eId('rl-loading-error').hidden = false;
p.end();
}, },
loadScript = src => { loadScript = src => {
@ -57,29 +50,12 @@ const
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const script = doc.createElement('script'); const script = doc.createElement('script');
script.onload = () => { script.onload = () => resolve();
p.set(pStep += step);
resolve();
};
script.onerror = () => reject(new Error(src)); script.onerror = () => reject(new Error(src));
script.src = src; script.src = src;
// script.async = true; // script.async = true;
doc.head.append(script); doc.head.append(script);
}); });
},
step = 100 / 7,
p = win.progressJs = {
set: percent => progress.style.width = Math.min(percent, 100) + '%',
end: () => {
if (container) {
p.set(100);
setTimeout(() => {
container.remove();
container = progress = null;
}, 600);
}
}
}; };
if (!navigator || !navigator.cookieEnabled) { if (!navigator || !navigator.cookieEnabled) {
@ -89,37 +65,15 @@ if (!navigator || !navigator.cookieEnabled) {
const layout = getCookie('rllayout'); const layout = getCookie('rllayout');
doc.documentElement.classList.toggle('rl-mobile', 'mobile' === layout || (!layout && 1000 > innerWidth)); doc.documentElement.classList.toggle('rl-mobile', 'mobile' === layout || (!layout && 1000 > innerWidth));
let pStep = 0, let progress = eId('progressjs'),
container = eId('progressjs'),
progress = container.querySelector('.progressjs-inner'),
RL_APP_DATA = {}; RL_APP_DATA = {};
if (progress) {
progress.remove();
progress = null;
}
win.rl = { win.rl = {
hash: {
// getHash
get: () => storage.getItem(STORAGE_KEY) || null,
// setHash
set: () => {
storage.setItem(STORAGE_KEY, RL_APP_DATA && RL_APP_DATA[AUTH_KEY]
? RL_APP_DATA[AUTH_KEY] : '');
setTimestamp();
},
// clearHash
clear: () => {
storage.setItem(STORAGE_KEY, '');
setTimestamp();
},
// checkTimestamp
check: () => {
if (timestamp() > (parseInt(storage.getItem(TIME_KEY) || 0, 10) || 0) + 3600000) {
// 60m
rl.hash.clear();
return true;
}
return false;
}
},
data: () => RL_APP_DATA, data: () => RL_APP_DATA,
adminArea: () => admin, adminArea: () => admin,
settings: { settings: {
@ -142,15 +96,8 @@ win.rl = {
initData: appData => { initData: appData => {
RL_APP_DATA = appData; RL_APP_DATA = appData;
rl.hash.set();
if (appData) { if (appData) {
if (appData.NewThemeLink) {
eId('app-theme-link').href = appData.NewThemeLink;
}
loadScript(appData.StaticLibJsLink) loadScript(appData.StaticLibJsLink)
.then(() => Promise.all([loadScript(appData.TemplatesLink), loadScript(appData.LangLink)]))
.then(() => loadScript(appData.StaticAppJsLink)) .then(() => loadScript(appData.StaticAppJsLink))
.then(() => appData.PluginsLink ? loadScript(appData.PluginsLink) : Promise.resolve()) .then(() => appData.PluginsLink ? loadScript(appData.PluginsLink) : Promise.resolve())
.then(() => win.__APP_BOOT ? win.__APP_BOOT(showError) : showError()) .then(() => win.__APP_BOOT ? win.__APP_BOOT(showError) : showError())
@ -164,16 +111,11 @@ win.rl = {
} }
}; };
p.set(1);
Storage('local'); Storage('local');
// init section eId('app-css').href = eId('app-css').dataset.href;
setInterval(setTimestamp, 60000); // 1m
[eId('app-css'),eId('app-theme-link')].forEach(css => css.href = css.dataset.href); loadScript(`./?/${admin ? 'Admin' : ''}AppData/0/${Math.random().toString().substr(2)}/`)
loadScript(`./?/${admin ? 'Admin' : ''}AppData/${rl.hash.get() || '0'}/${Math.random().toString().substr(2)}/`)
.then(() => {}); .then(() => {});
})(this); })(this);

18
dev/bootstrap.js vendored
View file

@ -1,9 +1,9 @@
import { doc, elementById, dropdownVisibility, Settings } from 'Common/Globals'; import { doc, dropdownVisibility, Settings } from 'Common/Globals';
import { i18n } from 'Common/Translator'; import { i18n } from 'Common/Translator';
import { root } from 'Common/Links'; import { root } from 'Common/Links';
export default (App) => { export default App => {
addEventListener('keydown', event => { addEventListener('keydown', event => {
event = event || window.event; event = event || window.event;
@ -135,17 +135,11 @@ export default (App) => {
}); });
}; };
window.__APP_BOOT = fErrorCallback => { window.__APP_BOOT = () => {
const cb = () => setTimeout(() => { const cb = () => {
if (rl.TEMPLATES) {
elementById('rl-templates').innerHTML = rl.TEMPLATES;
setTimeout(() => App.bootstart(), 10);
} else {
fErrorCallback();
}
window.__APP_BOOT = null; window.__APP_BOOT = null;
}, 10); App.bootstart();
};
('loading' !== doc.readyState) ? cb() : doc.addEventListener('DOMContentLoaded', cb); ('loading' !== doc.readyState) ? cb() : doc.addEventListener('DOMContentLoaded', cb);
}; };
}; };

View file

@ -3,7 +3,7 @@
"title": "SnappyMail", "title": "SnappyMail",
"description": "Simple, modern & fast web-based email client", "description": "Simple, modern & fast web-based email client",
"private": true, "private": true,
"version": "2.5.0-rc.3", "version": "2.5.2",
"homepage": "https://snappymail.eu", "homepage": "https://snappymail.eu",
"author": { "author": {
"name": "DJ Maze", "name": "DJ Maze",
@ -64,7 +64,6 @@
"gulp-terser": "^1.4.0", "gulp-terser": "^1.4.0",
"gulp-util": "3.0.8", "gulp-util": "3.0.8",
"node-fs": "0.1.7", "node-fs": "0.1.7",
"openpgp": "2.6.2",
"rimraf": "3.0.2" "rimraf": "3.0.2"
}, },
"dependencies": { "dependencies": {

View file

@ -14,6 +14,8 @@ class AutoDomainGrabPlugin extends \RainLoop\Plugins\AbstractPlugin
{ {
const const
NAME = '', NAME = '',
VERSION = '2.1',
REQUIRED = '2.5.0',
CATEGORY = 'General', CATEGORY = 'General',
DESCRIPTION = ''; DESCRIPTION = '';

View file

@ -4,7 +4,9 @@ class BlackListPlugin extends \RainLoop\Plugins\AbstractPlugin
{ {
const const
NAME = 'Black list', NAME = 'Black list',
VERSION = '2.0', VERSION = '2.1',
RELEASE = '2021-04-21',
REQUIRED = '2.5.0',
CATEGORY = 'Login', CATEGORY = 'Login',
DESCRIPTION = 'Simple black list plugin (with wildcard and exceptions functionality).'; DESCRIPTION = 'Simple black list plugin (with wildcard and exceptions functionality).';

View file

@ -5,7 +5,7 @@ class CKEditorPlugin extends \RainLoop\Plugins\AbstractPlugin
const const
NAME = 'CKEditor', NAME = 'CKEditor',
VERSION = '2.1', VERSION = '2.1',
RELEASE = '2021-04-01', RELEASE = '2021-04-19',
REQUIRED = '2.5.0', REQUIRED = '2.5.0',
DESCRIPTION = 'Use CKEditor instead of Squire as WYSIWYG'; DESCRIPTION = 'Use CKEditor instead of Squire as WYSIWYG';

View file

@ -4,6 +4,8 @@ class CustomAuthExamplePlugin extends \RainLoop\Plugins\AbstractPlugin
{ {
const const
NAME = '', NAME = '',
VERSION = '2.1',
REQUIRED = '2.5.0',
CATEGORY = 'Login', CATEGORY = 'Login',
DESCRIPTION = ''; DESCRIPTION = '';

View file

@ -4,7 +4,9 @@ class CustomLoginMappingPlugin extends \RainLoop\Plugins\AbstractPlugin
{ {
const const
NAME = 'Custom Login Mapping', NAME = 'Custom Login Mapping',
VERSION = '2.0', VERSION = '2.1',
RELEASE = '2021-04-21',
REQUIRED = '2.5.0',
CATEGORY = 'Login', CATEGORY = 'Login',
DESCRIPTION = 'Plugin which allows you to set up custom username by email address.'; DESCRIPTION = 'Plugin which allows you to set up custom username by email address.';

View file

@ -0,0 +1,33 @@
(rl => {
if (rl) {
const
forgotUrl = rl.settings.get('forgotPasswordLinkUrl'),
registerUrl = rl.settings.get('registrationLinkUrl');
if (forgotUrl || registerUrl) {
addEventListener('rl-view-model', e => {
if (e.detail && 'Login' === e.detail.viewModelTemplateID) {
const container = e.detail.viewModelDom.querySelector('#plugin-Login-BottomControlGroup'),
forgot = 'LOGIN/LABEL_FORGOT_PASSWORD',
register = 'LOGIN/LABEL_REGISTRATION';
if (container) {
let html = '';
if (forgotUrl) {
html = html + '<p class="forgot-link">'
+ '<a href="'+forgotUrl+'" target="_blank" class="g-ui-link" data-i18n="'+forgot+'">'+rl.i18n(forgot)+'</a>'
+ '</p>';
}
if (registerUrl) {
html = html + '<p class="registration-link">'
+ '<a href="'+registerUrl+'" target="_blank" class="g-ui-link" data-i18n="'+register+'">'+rl.i18n(register)+'</a>'
+ '</p>';
}
container.append(Element.fromHTML('<div class="controls clearfix">' + html + '</div>'));
}
}
});
}
}
})(window.rl);

View file

@ -0,0 +1,42 @@
<?php
class LoginRegisterPlugin extends \RainLoop\Plugins\AbstractPlugin
{
const
NAME = 'Register and Forgot',
VERSION = '2.0',
RELEASE = '2021-06-01',
REQUIRED = '2.5.2',
CATEGORY = 'Login',
DESCRIPTION = 'Links on login screen for registration and forgot password';
public function Init() : void
{
$this->UseLangs(true);
$this->addJs('LoginRegister.js');
$this->addHook('filter.app-data', 'FilterAppData');
}
public function configMapping() : array
{
return [
\RainLoop\Plugins\Property::NewInstance("forgot_password_link_url")
// ->SetLabel('TAB_LOGIN/LABEL_FORGOT_PASSWORD_LINK_URL')
->SetLabel('Forgot password url')
->SetType(\RainLoop\Enumerations\PluginPropertyType::URL),
\RainLoop\Plugins\Property::NewInstance("registration_link_url")
// ->SetLabel('TAB_LOGIN/LABEL_REGISTRATION_LINK_URL')
->SetLabel('Register url')
->SetType(\RainLoop\Enumerations\PluginPropertyType::URL),
];
}
public function FilterAppData($bAdmin, &$aResult)
{
if (!$bAdmin && \is_array($aResult) && empty($aResult['Auth'])) {
$aResult['forgotPasswordLinkUrl'] = \trim($this->Config()->Get('plugin', 'forgot_password_link_url', ''));
$aResult['registrationLinkUrl'] = \trim($this->Config()->Get('plugin', 'registration_link_url', ''));
}
}
}

View file

@ -0,0 +1,10 @@
{
"LOGIN": {
"LABEL_FORGOT_PASSWORD": "Passwort vergessen",
"LABEL_REGISTRATION": "Registrieren"
},
"TAB_LOGIN": {
"LABEL_FORGOT_PASSWORD_LINK_URL": "Forgot password url",
"LABEL_REGISTRATION_LINK_URL": "Register url"
}
}

View file

@ -0,0 +1,10 @@
{
"LOGIN": {
"LABEL_FORGOT_PASSWORD": "Forgot password",
"LABEL_REGISTRATION": "Register"
},
"TAB_LOGIN": {
"LABEL_FORGOT_PASSWORD_LINK_URL": "Forgot password url",
"LABEL_REGISTRATION_LINK_URL": "Register url"
}
}

View file

@ -0,0 +1,10 @@
{
"LOGIN": {
"LABEL_FORGOT_PASSWORD": "Olvidé mi contraseña",
"LABEL_REGISTRATION": "Registrarse"
},
"TAB_LOGIN": {
"LABEL_FORGOT_PASSWORD_LINK_URL": "Forgot password url",
"LABEL_REGISTRATION_LINK_URL": "Register url"
}
}

View file

@ -0,0 +1,10 @@
{
"LOGIN": {
"LABEL_FORGOT_PASSWORD": "Mot de passe oublié",
"LABEL_REGISTRATION": "S'enregistrer"
},
"TAB_LOGIN": {
"LABEL_FORGOT_PASSWORD_LINK_URL": "Forgot password url",
"LABEL_REGISTRATION_LINK_URL": "Register url"
}
}

View file

@ -0,0 +1,10 @@
{
"LOGIN": {
"LABEL_FORGOT_PASSWORD": "Elfelejtett jelszó",
"LABEL_REGISTRATION": "Regisztráció"
},
"TAB_LOGIN": {
"LABEL_FORGOT_PASSWORD_LINK_URL": "Forgot password url",
"LABEL_REGISTRATION_LINK_URL": "Register url"
}
}

View file

@ -0,0 +1,10 @@
{
"LOGIN": {
"LABEL_FORGOT_PASSWORD": "Wachtwoord vergeten",
"LABEL_REGISTRATION": "Registreren"
},
"TAB_LOGIN": {
"LABEL_FORGOT_PASSWORD_LINK_URL": "Forgot password url",
"LABEL_REGISTRATION_LINK_URL": "Register url"
}
}

View file

@ -0,0 +1,10 @@
{
"LOGIN": {
"LABEL_FORGOT_PASSWORD": "Glömt lösenord",
"LABEL_REGISTRATION": "Registrera"
},
"TAB_LOGIN": {
"LABEL_FORGOT_PASSWORD_LINK_URL": "Forgot password url",
"LABEL_REGISTRATION_LINK_URL": "Register url"
}
}

View file

@ -0,0 +1,10 @@
{
"LOGIN": {
"LABEL_FORGOT_PASSWORD": "忘记密码",
"LABEL_REGISTRATION": "注册"
},
"TAB_LOGIN": {
"LABEL_FORGOT_PASSWORD_LINK_URL": "Forgot password url",
"LABEL_REGISTRATION_LINK_URL": "Register url"
}
}

View file

@ -4,7 +4,9 @@ class OverrideSmtpCredentialsPlugin extends \RainLoop\Plugins\AbstractPlugin
{ {
const const
NAME = 'Override Smtp Credentials', NAME = 'Override Smtp Credentials',
VERSION = '2.0', VERSION = '2.1',
RELEASE = '2021-04-21',
REQUIRED = '2.5.0',
CATEGORY = 'Filters', CATEGORY = 'Filters',
DESCRIPTION = 'Plugin which allows you to override smtp credentials specified users.'; DESCRIPTION = 'Plugin which allows you to override smtp credentials specified users.';

View file

@ -4,6 +4,8 @@ class ProxyauthLoginExamplePlugin extends \RainLoop\Plugins\AbstractPlugin
{ {
const const
NAME = '', NAME = '',
VERSION = '2.1',
REQUIRED = '2.5.0',
CATEGORY = 'General', CATEGORY = 'General',
DESCRIPTION = ''; DESCRIPTION = '';

View file

@ -4,7 +4,9 @@ class WhiteListPlugin extends \RainLoop\Plugins\AbstractPlugin
{ {
const const
NAME = 'White list', NAME = 'White list',
VERSION = '2.0', VERSION = '2.1',
RELEASE = '2021-04-21',
REQUIRED = '2.5.0',
CATEGORY = 'Login', CATEGORY = 'Login',
DESCRIPTION = 'Simple white list plugin (with wildcard and exceptions functionality).'; DESCRIPTION = 'Simple white list plugin (with wildcard and exceptions functionality).';

View file

@ -145,6 +145,10 @@ if ($brotli = trim(`which brotli`)) {
unlink(__DIR__ . '/snappymail/v/0.0.0/static/js/min/boot.min.js.br'); unlink(__DIR__ . '/snappymail/v/0.0.0/static/js/min/boot.min.js.br');
} }
unlink(__DIR__ . '/snappymail/v/0.0.0/static/js/openpgp.js');
unlink(__DIR__ . '/snappymail/v/0.0.0/static/js/openpgp.js.br');
unlink(__DIR__ . '/snappymail/v/0.0.0/static/js/openpgp.js.gz');
// Temporary rename folder to speed up PharData // Temporary rename folder to speed up PharData
//if (!rename('snappymail/v/0.0.0', "snappymail/v/{$package->version}")){ //if (!rename('snappymail/v/0.0.0', "snappymail/v/{$package->version}")){
if (!rename('snappymail/v/0.0.0', "snappymail/v/{$package->version}")) { if (!rename('snappymail/v/0.0.0', "snappymail/v/{$package->version}")) {

View file

@ -28,6 +28,11 @@ if (!\defined('RAINLOOP_APP_LIBRARIES_PATH'))
if (\class_exists('RainLoop\Api')) if (\class_exists('RainLoop\Api'))
{ {
if (!\SnappyMail\HTTP\SecFetch::isEntering() && !\SnappyMail\HTTP\SecFetch::isSameOrigin()) {
\MailSo\Base\Http::StatusHeader(403);
exit('Disallowed Sec-Fetch-Site: ' . ($_SERVER['HTTP_SEC_FETCH_SITE'] ?? ''));
}
\MailSo\Base\Loader::Init(); \MailSo\Base\Loader::Init();
if (!empty($_ENV['RAINLOOP_INCLUDE_AS_API'])) if (!empty($_ENV['RAINLOOP_INCLUDE_AS_API']))

View file

@ -43,7 +43,7 @@ abstract class DateTimeHelper
} }
$sDateTime = \trim(\preg_replace('/ \([a-zA-Z0-9]+\)$/', '', $sDateTime)); $sDateTime = \trim(\preg_replace('/ \([a-zA-Z0-9]+\)$/', '', $sDateTime));
$oDateTime = \DateTime::createFromFormat('D, d M Y H:i:s O', $sDateTime, \MailSo\Base\DateTimeHelper::GetUtcTimeZoneObject()); $oDateTime = \DateTime::createFromFormat(\DateTime::RFC2822, $sDateTime, static::GetUtcTimeZoneObject());
return $oDateTime ? $oDateTime->getTimestamp() : 0; return $oDateTime ? $oDateTime->getTimestamp() : 0;
} }
@ -61,10 +61,10 @@ abstract class DateTimeHelper
if (\preg_match('/^[a-z]{2,4}, /i', $sDateTime)) // RFC2822 ~ "Thu, 10 Jun 2010 08:58:33 -0700 (PDT)" if (\preg_match('/^[a-z]{2,4}, /i', $sDateTime)) // RFC2822 ~ "Thu, 10 Jun 2010 08:58:33 -0700 (PDT)"
{ {
return \MailSo\Base\DateTimeHelper::ParseRFC2822DateString($sDateTime); return static::ParseRFC2822DateString($sDateTime);
} }
$oDateTime = \DateTime::createFromFormat('d-M-Y H:i:s O', $sDateTime, \MailSo\Base\DateTimeHelper::GetUtcTimeZoneObject()); $oDateTime = \DateTime::createFromFormat('d-M-Y H:i:s O', $sDateTime, static::GetUtcTimeZoneObject());
return $oDateTime ? $oDateTime->getTimestamp() : 0; return $oDateTime ? $oDateTime->getTimestamp() : 0;
} }
@ -79,7 +79,7 @@ abstract class DateTimeHelper
return 0; return 0;
} }
$oDateTime = \DateTime::createFromFormat('Y-m-d H:i:s O', $sDateTime, \MailSo\Base\DateTimeHelper::GetUtcTimeZoneObject()); $oDateTime = \DateTime::createFromFormat('Y-m-d H:i:s O', $sDateTime, static::GetUtcTimeZoneObject());
return $oDateTime ? $oDateTime->getTimestamp() : 0; return $oDateTime ? $oDateTime->getTimestamp() : 0;
} }
@ -88,11 +88,16 @@ abstract class DateTimeHelper
*/ */
public static function TryToParseSpecEtagFormat(string $sDateTime) : int public static function TryToParseSpecEtagFormat(string $sDateTime) : int
{ {
$sDateTime = \trim(\preg_replace('/ \([a-zA-Z0-9]+\)$/', '', \trim($sDateTime))); $oDateTime = \DateTime::createFromFormat(\DateTime::RFC3339_EXTENDED, $sDateTime, static::GetUtcTimeZoneObject());
$sDateTime = \trim(\preg_replace('/(:[\d]{2})\.[\d]{3}/', '$1', \trim($sDateTime))); if ($oDateTime) {
$sDateTime = \trim(\preg_replace('/(-[\d]{2})T([\d]{2}:)/', '$1 $2', \trim($sDateTime))); return $oDateTime->getTimestamp();
$sDateTime = \trim(\preg_replace('/([\-+][\d]{2}):([\d]{2})$/', ' $1$2', \trim($sDateTime))); }
return \MailSo\Base\DateTimeHelper::ParseDateStringType1($sDateTime); $sDateTime = \preg_replace('/ \([a-zA-Z0-9]+\)$/', '', \trim($sDateTime));
$sDateTime = \preg_replace('/(:[\d]{2})\.[\d]{3}/', '$1', \trim($sDateTime));
$sDateTime = \preg_replace('/(-[\d]{2})T([\d]{2}:)/', '$1 $2', \trim($sDateTime));
$sDateTime = \preg_replace('/([\-+][\d]{2}):([\d]{2})$/', ' $1$2', \trim($sDateTime));
return static::ParseDateStringType1($sDateTime);
} }
} }

View file

@ -31,66 +31,6 @@ class Http
return $oInstance; return $oInstance;
} }
public function HasQuery(string $sKey) : bool
{
return isset($_GET[$sKey]);
}
/**
* @param mixed $mDefault = null
*
* @return mixed
*/
public function GetQuery(string $sKey, $mDefault = null)
{
return isset($_GET[$sKey]) ? $_GET[$sKey] : $mDefault;
}
public function GetQueryAsArray() : ?array
{
return isset($_GET) && \is_array($_GET) ? $_GET : null;
}
public function HasPost(string $sKey) : bool
{
return isset($_POST[$sKey]);
}
/**
* @param mixed $mDefault = null
*
* @return mixed
*/
public function GetPost(string $sKey, $mDefault = null)
{
return isset($_POST[$sKey]) ? $_POST[$sKey] : $mDefault;
}
public function GetPostAsArray() : ?array
{
return isset($_POST) && \is_array($_POST) ? $_POST : null;
}
public function HasRequest(string $sKey) : bool
{
return isset($_REQUEST[$sKey]);
}
/**
* @param mixed $mDefault = null
*
* @return mixed
*/
public function GetRequest(string $sKey, $mDefault = null)
{
return isset($_REQUEST[$sKey]) ? $_REQUEST[$sKey] : $mDefault;
}
public function HasServer(string $sKey) : bool
{
return isset($_SERVER[$sKey]);
}
/** /**
* @param mixed $mDefault = null * @param mixed $mDefault = null
* *
@ -101,26 +41,6 @@ class Http
return isset($_SERVER[$sKey]) ? $_SERVER[$sKey] : $mDefault; return isset($_SERVER[$sKey]) ? $_SERVER[$sKey] : $mDefault;
} }
public function HasEnv(string $sKey) : bool
{
return isset($_ENV[$sKey]);
}
/**
* @param mixed $mDefault = null
*
* @return mixed
*/
public function GetEnv(string $sKey, $mDefault = null)
{
return isset($_ENV[$sKey]) ? $_ENV[$sKey] : $mDefault;
}
public function ServerProtocol() : string
{
return $this->GetServer('SERVER_PROTOCOL', 'HTTP/1.0');
}
public function GetMethod() : string public function GetMethod() : string
{ {
return $this->GetServer('REQUEST_METHOD', ''); return $this->GetServer('REQUEST_METHOD', '');
@ -136,11 +56,6 @@ class Http
return ('GET' === $this->GetMethod()); return ('GET' === $this->GetMethod());
} }
public function GetQueryString() : string
{
return $this->GetServer('QUERY_STRING', '');
}
public function CheckLocalhost(string $sServer) : bool public function CheckLocalhost(string $sServer) : bool
{ {
return \in_array(\strtolower(\trim($sServer)), array( return \in_array(\strtolower(\trim($sServer)), array(
@ -229,7 +144,7 @@ class Http
if ($bWithRemoteUserData) if ($bWithRemoteUserData)
{ {
$sUser = \trim($this->HasServer('REMOTE_USER') ? $this->GetServer('REMOTE_USER', '') : ''); $sUser = \trim($this->GetServer('REMOTE_USER', ''));
$sHost = (0 < \strlen($sUser) ? $sUser.'@' : '').$sHost; $sHost = (0 < \strlen($sUser) ? $sUser.'@' : '').$sHost;
} }
@ -529,7 +444,7 @@ class Http
} }
else else
{ {
$this->StatusHeader(304); static::StatusHeader(304);
$bResult = true; $bResult = true;
} }
} }
@ -570,9 +485,8 @@ class Http
} }
} }
public function StatusHeader(int $iStatus, string $sCustomStatusText = '') : void public static function StatusHeader(int $iStatus, string $sCustomStatusText = '') : void
{ {
$iStatus = (int) $iStatus;
if (99 < $iStatus) if (99 < $iStatus)
{ {
$aStatus = array( $aStatus = array(
@ -588,11 +502,15 @@ class Http
416 => 'Requested range not satisfiable' 416 => 'Requested range not satisfiable'
); );
$sCustomStatusText = \trim($sCustomStatusText);
$sHeaderHead = \ini_get('cgi.rfc2616_headers') && false !== \strpos(\strtolower(\php_sapi_name()), 'cgi') ? 'Status:' : $this->ServerProtocol();
$sHeaderText = (0 === \strlen($sCustomStatusText) && isset($aStatus[$iStatus]) ? $aStatus[$iStatus] : $sCustomStatusText); $sHeaderText = (0 === \strlen($sCustomStatusText) && isset($aStatus[$iStatus]) ? $aStatus[$iStatus] : $sCustomStatusText);
\header(\trim($sHeaderHead.' '.$iStatus.' '.$sHeaderText), true, $iStatus); \http_response_code($iStatus);
if (isset($_SERVER['SERVER_PROTOCOL'])) {
\header("{$_SERVER['SERVER_PROTOCOL']} {$iStatus} {$sHeaderText}", true, $iStatus);
}
if (\ini_get('cgi.rfc2616_headers') && false !== \strpos(\strtolower(\php_sapi_name()), 'cgi')) {
\header("Status: {$iStatus} {$sHeaderText}");
}
} }
} }
@ -602,9 +520,9 @@ class Http
return '' === $sUrl ? '/' : '/'.$sUrl.'/'; return '' === $sUrl ? '/' : '/'.$sUrl.'/';
} }
public function GetUrl() public function GetUrl() : string
{ {
return $this->GetServer('REQUEST_URI', ''); return $_SERVER['REQUEST_URI'] ?? '';
} }
public function GetFullUrl() : string public function GetFullUrl() : string

View file

@ -65,7 +65,7 @@ class SubStreams
$sHashName = \MailSo\Base\Utils::Md5Rand(); $sHashName = \MailSo\Base\Utils::Md5Rand();
self::$aStreams[$sHashName] = array_map(function($mItem) { self::$aStreams[$sHashName] = \array_map(function ($mItem) {
return \is_resource($mItem) ? $mItem : return \is_resource($mItem) ? $mItem :
\MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString($mItem); \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString($mItem);
}, $aSubStreams); }, $aSubStreams);

View file

@ -132,7 +132,7 @@ class Xxtea
return $aV; return $aV;
} }
private static function int32(int $iN) : int private static function int32($iN) : int
{ {
return $iN & 0xffffffff; return $iN & 0xffffffff;
} }

View file

@ -16,7 +16,7 @@ namespace MailSo\Cache\Drivers;
* @package Cache * @package Cache
* @subpackage Drivers * @subpackage Drivers
*/ */
class APC implements \MailSo\Cache\DriverInterface class APCU implements \MailSo\Cache\DriverInterface
{ {
/** /**
* @var string * @var string
@ -35,25 +35,25 @@ class APC implements \MailSo\Cache\DriverInterface
public function Set(string $sKey, string $sValue) : bool public function Set(string $sKey, string $sValue) : bool
{ {
return \apc_store($this->generateCachedKey($sKey), (string) $sValue); return \apcu_store($this->generateCachedKey($sKey), (string) $sValue);
} }
public function Get(string $sKey) : string public function Get(string $sKey) : string
{ {
$sValue = \apc_fetch($this->generateCachedKey($sKey)); $sValue = \apcu_fetch($this->generateCachedKey($sKey));
return \is_string($sValue) ? $sValue : ''; return \is_string($sValue) ? $sValue : '';
} }
public function Delete(string $sKey) : void public function Delete(string $sKey) : void
{ {
\apc_delete($this->generateCachedKey($sKey)); \apcu_delete($this->generateCachedKey($sKey));
} }
public function GC(int $iTimeToClearInHours = 24) : bool public function GC(int $iTimeToClearInHours = 24) : bool
{ {
if (0 === $iTimeToClearInHours) if (0 === $iTimeToClearInHours)
{ {
return \apc_clear_cache('user'); return \apcu_clear_cache('user');
} }
return false; return false;

View file

@ -104,31 +104,24 @@ class FetchResponse
*/ */
public function GetFetchValue(string $sFetchItemName) public function GetFetchValue(string $sFetchItemName)
{ {
$mReturn = null; if (Enumerations\FetchType::INDEX === $sFetchItemName) {
return $this->oImapResponse->ResponseList[1];
}
if (isset($this->oImapResponse->ResponseList[3]) && \is_array($this->oImapResponse->ResponseList[3])) {
$bNextIsValue = false; $bNextIsValue = false;
foreach ($this->oImapResponse->ResponseList[3] as $mItem) {
if (Enumerations\FetchType::INDEX === $sFetchItemName) if ($bNextIsValue) {
{ return $mItem;
$mReturn = $this->oImapResponse->ResponseList[1];
}
else if (isset($this->oImapResponse->ResponseList[3]) && \is_array($this->oImapResponse->ResponseList[3]))
{
foreach ($this->oImapResponse->ResponseList[3] as $mItem)
{
if ($bNextIsValue)
{
$mReturn = $mItem;
break;
} }
if ($sFetchItemName === $mItem) if ($sFetchItemName === $mItem) {
{
$bNextIsValue = true; $bNextIsValue = true;
} }
} }
} }
return $mReturn; return null;
} }
public function GetHeaderFieldsValue(string $sRfc822SubMimeIndex = '') : string public function GetHeaderFieldsValue(string $sRfc822SubMimeIndex = '') : string

View file

@ -184,7 +184,7 @@ class ImapClient extends \MailSo\Net\NetClient
if (0 === \strpos($type, 'SCRAM-SHA-')) if (0 === \strpos($type, 'SCRAM-SHA-'))
{ {
$sAuthzid = $this->getResponseValue($this->SendRequestGetResponse('AUTHENTICATE', array($type)), Enumerations\ResponseType::CONTINUATION); $sAuthzid = $this->getResponseValue($this->SendRequestGetResponse('AUTHENTICATE', array($type)), Enumerations\ResponseType::CONTINUATION);
$this->sendRaw($SASL->authenticate($sLogin, $sPassword/*, $sAuthzid*/)); $this->sendRaw($SASL->authenticate($sLogin, $sPassword/*, $sAuthzid*/), true);
$sChallenge = $SASL->challenge($this->getResponseValue($this->getResponse(), Enumerations\ResponseType::CONTINUATION)); $sChallenge = $SASL->challenge($this->getResponseValue($this->getResponse(), Enumerations\ResponseType::CONTINUATION));
if ($this->oLogger) { if ($this->oLogger) {
$this->oLogger->AddSecret($sChallenge); $this->oLogger->AddSecret($sChallenge);
@ -211,20 +211,31 @@ class ImapClient extends \MailSo\Net\NetClient
$this->oLogger->AddSecret($sAuth); $this->oLogger->AddSecret($sAuth);
} }
if ($this->IsSupported('SASL-IR')) { if ($this->IsSupported('SASL-IR')) {
$this->SendRequestGetResponse('AUTHENTICATE', array('PLAIN', $sAuth)); $this->SendRequestGetResponse('AUTHENTICATE', array($type, $sAuth));
} else { } else {
$this->SendRequestGetResponse('AUTHENTICATE', array('PLAIN')); $this->SendRequestGetResponse('AUTHENTICATE', array($type));
$this->sendRaw($sAuth, true, '*******'); $this->sendRaw($sAuth, true, '*******');
$this->getResponse(); $this->getResponse();
} }
} }
else if ($this->IsSupported('LOGINDISABLED'))
{
$sB64 = $this->getResponseValue($this->SendRequestGetResponse('AUTHENTICATE', array($type)), Enumerations\ResponseType::CONTINUATION);
$this->sendRaw($SASL->authenticate($sLogin, $sPassword, $sB64), true);
$this->getResponse();
$sPass = $SASL->challenge(''/*UGFzc3dvcmQ6*/);
if ($this->oLogger) {
$this->oLogger->AddSecret($sPass);
}
$this->sendRaw($sPass, true, '*******');
$this->getResponse();
}
else else
{ {
if ($this->oLogger) if ($this->oLogger)
{ {
$this->oLogger->AddSecret($this->EscapeString($sPassword)); $this->oLogger->AddSecret($this->EscapeString($sPassword));
} }
$this->SendRequestGetResponse('LOGIN', $this->SendRequestGetResponse('LOGIN',
array( array(
$this->EscapeString($sLogin), $this->EscapeString($sLogin),
@ -1082,6 +1093,17 @@ class ImapClient extends \MailSo\Net\NetClient
return $oResult; return $oResult;
} }
private function skipBracketParse(?Response $oImapResponse) : bool
{
return $oImapResponse &&
$oImapResponse->ResponseType === \MailSo\Imap\Enumerations\ResponseType::UNTAGGED &&
(
($oImapResponse->StatusOrIndex === 'STATUS' && 2 === \count($oImapResponse->ResponseList)) ||
($oImapResponse->StatusOrIndex === 'LIST' && 4 === \count($oImapResponse->ResponseList)) ||
($oImapResponse->StatusOrIndex === 'LSUB' && 4 === \count($oImapResponse->ResponseList))
);
}
/** /**
* @return array|string * @return array|string
* @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Net\Exceptions\Exception
@ -1265,8 +1287,16 @@ class ImapClient extends \MailSo\Net\NetClient
{ {
case ']' === $sChar: case ']' === $sChar:
case ')' === $sChar: case ')' === $sChar:
if ($this->skipBracketParse($oImapResponse))
{
$bIsGotoDefault = true;
$bIsGotoNotAtomBracket = false;
}
else
{
++$iPos; ++$iPos;
$sPreviousAtomUpperCase = null; $sPreviousAtomUpperCase = null;
}
break 2; break 2;
case ' ' === $sChar: case ' ' === $sChar:
if ($bTreatAsAtom) if ($bTreatAsAtom)
@ -1283,12 +1313,20 @@ class ImapClient extends \MailSo\Net\NetClient
{ {
$sAtomBuilder .= $sChar; $sAtomBuilder .= $sChar;
$bIsGotoAtomBracket = true; $bIsGotoAtomBracket = true;
$this->iResponseBufParsedPos = ++$iPos;
}
else if ($this->skipBracketParse($oImapResponse))
{
$sOpenBracket = '';
$sClosingBracket = '';
$bIsGotoDefault = true;
$bIsGotoNotAtomBracket = false;
} }
else else
{ {
$bIsGotoNotAtomBracket = true; $bIsGotoNotAtomBracket = true;
}
$this->iResponseBufParsedPos = ++$iPos; $this->iResponseBufParsedPos = ++$iPos;
}
break; break;
case '{' === $sChar: case '{' === $sChar:
$bIsLiteralParsed = false; $bIsLiteralParsed = false;
@ -1416,6 +1454,10 @@ class ImapClient extends \MailSo\Net\NetClient
$sCharDef = $this->sResponseBuffer[$iPos]; $sCharDef = $this->sResponseBuffer[$iPos];
switch (true) switch (true)
{ {
case ('[' === $sCharDef || ']' === $sCharDef || '(' === $sCharDef || ')' === $sCharDef) &&
$this->skipBracketParse($oImapResponse):
++$iPos;
break;
case '[' === $sCharDef: case '[' === $sCharDef:
if (null === $sAtomBuilder) if (null === $sAtomBuilder)
{ {

View file

@ -91,6 +91,34 @@ class ResponseCollection extends \MailSo\Base\Collection
return $aReturn; return $aReturn;
} }
/**
* @param array|string $mName
*/
private function getArrayNameToStringName($mName) : string
{
if (\is_string($mName))
{
return $mName;
}
if (\is_array($mName))
{
if (0 === \count($mName))
{
return '[]';
}
foreach ($mName as &$mSubName)
{
$mSubName = "[{$this->getArrayNameToStringName($mSubName)}]";
}
return \implode('', $mName);
}
return '';
}
public function getFoldersResult(string $sStatus, bool $bUseListStatus = false) : array public function getFoldersResult(string $sStatus, bool $bUseListStatus = false) : array
{ {
$aReturn = array(); $aReturn = array();
@ -110,15 +138,12 @@ class ResponseCollection extends \MailSo\Base\Collection
* https://github.com/the-djmaze/snappymail/issues/1 * https://github.com/the-djmaze/snappymail/issues/1
* https://github.com/the-djmaze/snappymail/issues/70 * https://github.com/the-djmaze/snappymail/issues/70
*/ */
$sFullNameRaw = \array_slice($oResponse->ResponseList, 4); $aFullNameRawList = \array_slice($oResponse->ResponseList, 4);
foreach ($sFullNameRaw as &$name) { foreach ($aFullNameRawList as &$sName) {
if (\is_array($name)) { $sName = $this->getArrayNameToStringName($sName);
$name = "[{$name[0]}]";
} else if (!\is_string($name)) {
$name = '';
} }
}
$sFullNameRaw = \implode('', $sFullNameRaw); $sFullNameRaw = \implode('', $aFullNameRawList);
$oFolder = new Folder($sFullNameRaw, $oFolder = new Folder($sFullNameRaw,
$oResponse->ResponseList[3], $oResponse->ResponseList[2]); $oResponse->ResponseList[3], $oResponse->ResponseList[2]);

View file

@ -20,44 +20,32 @@ class FolderCollection extends \MailSo\Base\Collection
/** /**
* @var string * @var string
*/ */
public $Namespace; public $Namespace = '';
/** /**
* @var string * @var string
*/ */
public $FoldersHash; public $FoldersHash = '';
/** /**
* @var bool * @var bool
*/ */
public $IsThreadsSupported; public $IsThreadsSupported = false;
/** /**
* @var bool * @var bool
*/ */
public $IsSortSupported; public $IsSortSupported = false;
/** /**
* @var bool * @var bool
*/ */
public $Optimized; public $Optimized = false;
/** /**
* @var array * @var array
*/ */
public $SystemFolders; public $SystemFolders = array();
function __construct($input = array())
{
parent::__construct($input);
$this->Namespace = '';
$this->FoldersHash = '';
$this->SystemFolders = array();
$this->IsThreadsSupported = false;
$this->IsSortSupported = false;
$this->Optimized = false;
}
public function append($oFolder, bool $bToTop = false) : void public function append($oFolder, bool $bToTop = false) : void
{ {
@ -118,12 +106,7 @@ class FolderCollection extends \MailSo\Base\Collection
$oFolder = $this[0]; $oFolder = $this[0];
} }
if ($oFolder) return $oFolder ? $oFolder->Delimiter() : '/';
{
$sDelimiter = $oFolder->Delimiter();
}
return $sDelimiter;
} }
public function SetNamespace(string $sNamespace) : self public function SetNamespace(string $sNamespace) : self
@ -133,94 +116,20 @@ class FolderCollection extends \MailSo\Base\Collection
return $this; return $this;
} }
public function InitByUnsortedMailFolderArray(array $aUnsortedMailFolders) : void public function AddWithPositionSearch(Folder $oMailFolder) : void
{ {
$this->Clear();
$aSortedByLenImapFolders = array();
foreach ($aUnsortedMailFolders as /* @var $oMailFolder Folder */ $oMailFolder)
{
$aSortedByLenImapFolders[$oMailFolder->FullNameRaw()] =& $oMailFolder;
unset($oMailFolder);
}
unset($aUnsortedMailFolders);
$aAddedFolders = array();
foreach ($aSortedByLenImapFolders as /* @var $oMailFolder Folder */ $oMailFolder)
{
$sDelimiter = $oMailFolder->Delimiter();
$aFolderExplode = \explode($sDelimiter, $oMailFolder->FullNameRaw());
if (1 < \count($aFolderExplode))
{
\array_pop($aFolderExplode);
$sNonExistenFolderFullNameRaw = '';
foreach ($aFolderExplode as $sFolderExplodeItem)
{
$sNonExistenFolderFullNameRaw .= (0 < \strlen($sNonExistenFolderFullNameRaw))
? $sDelimiter.$sFolderExplodeItem : $sFolderExplodeItem;
if (!isset($aSortedByLenImapFolders[$sNonExistenFolderFullNameRaw]))
{
try
{
$aAddedFolders[$sNonExistenFolderFullNameRaw] =
Folder::NewNonExistenInstance($sNonExistenFolderFullNameRaw, $sDelimiter);
}
catch (\Throwable $oExc)
{
unset($oExc);
}
}
}
}
}
$aSortedByLenImapFolders = \array_merge($aSortedByLenImapFolders, $aAddedFolders);
unset($aAddedFolders);
\uasort($aSortedByLenImapFolders, function ($oFolderA, $oFolderB) {
return \strnatcmp($oFolderA->FullNameRaw(), $oFolderB->FullNameRaw());
});
foreach ($aSortedByLenImapFolders as $oMailFolder)
{
$this->AddWithPositionSearch($oMailFolder);
unset($oMailFolder);
}
unset($aSortedByLenImapFolders);
}
public function AddWithPositionSearch(Folder $oMailFolder) : bool
{
$oItemFolder = null;
$bIsAdded = false;
foreach ($this as $oItemFolder) foreach ($this as $oItemFolder)
{ {
if ($oMailFolder instanceof Folder && if (0 === \strpos($oMailFolder->FullNameRaw(), $oItemFolder->FullNameRaw().$oItemFolder->Delimiter()))
0 === \strpos($oMailFolder->FullNameRaw(), $oItemFolder->FullNameRaw().$oItemFolder->Delimiter()))
{ {
if ($oItemFolder->SubFolders(true)->AddWithPositionSearch($oMailFolder)) $oItemFolder->SubFolders(true)->AddWithPositionSearch($oMailFolder);
{ return;
$bIsAdded = true;
}
break;
} }
} }
if (!$bIsAdded && $oMailFolder instanceof Folder)
{
$bIsAdded = true;
$this->append($oMailFolder); $this->append($oMailFolder);
} }
return $bIsAdded;
}
public function SortByCallback(callable $fCallback) : void public function SortByCallback(callable $fCallback) : void
{ {
if (\is_callable($fCallback)) if (\is_callable($fCallback))
@ -241,7 +150,7 @@ class FolderCollection extends \MailSo\Base\Collection
public function jsonSerialize() public function jsonSerialize()
{ {
return array_merge(parent::jsonSerialize(), array( return \array_merge(parent::jsonSerialize(), array(
'Namespace' => $this->GetNamespace(), 'Namespace' => $this->GetNamespace(),
'FoldersHash' => $this->FoldersHash ?: '', 'FoldersHash' => $this->FoldersHash ?: '',
'IsThreadsSupported' => $this->IsThreadsSupported, 'IsThreadsSupported' => $this->IsThreadsSupported,

View file

@ -281,31 +281,6 @@ class MailClient
$oBodyStructure = null; $oBodyStructure = null;
$oMessage = null; $oMessage = null;
$aBodyPeekMimeIndexes = array();
$aSignatureMimeIndexes = array();
$aFetchResponse = $this->oImapClient->Fetch(array(\MailSo\Imap\Enumerations\FetchType::BODYSTRUCTURE), $iIndex, $bIndexIsUid);
if (0 < \count($aFetchResponse) && isset($aFetchResponse[0]))
{
$oBodyStructure = $aFetchResponse[0]->GetFetchBodyStructure();
if ($oBodyStructure)
{
foreach ($oBodyStructure->SearchHtmlOrPlainParts() as $oPart)
{
$aBodyPeekMimeIndexes[] = array($oPart->PartID(), $oPart->Size());
}
$aSignatureParts = $oBodyStructure->SearchByContentType('application/pgp-signature');
if (is_array($aSignatureParts) && 0 < \count($aSignatureParts))
{
foreach ($aSignatureParts as $oPart)
{
$aSignatureMimeIndexes[] = $oPart->PartID();
}
}
}
}
$aFetchItems = array( $aFetchItems = array(
\MailSo\Imap\Enumerations\FetchType::INDEX, \MailSo\Imap\Enumerations\FetchType::INDEX,
\MailSo\Imap\Enumerations\FetchType::UID, \MailSo\Imap\Enumerations\FetchType::UID,
@ -315,25 +290,31 @@ class MailClient
$this->getEnvelopeOrHeadersRequestString() $this->getEnvelopeOrHeadersRequestString()
); );
if (0 < \count($aBodyPeekMimeIndexes)) $aFetchResponse = $this->oImapClient->Fetch(array(\MailSo\Imap\Enumerations\FetchType::BODYSTRUCTURE), $iIndex, $bIndexIsUid);
if (0 < \count($aFetchResponse) && isset($aFetchResponse[0]))
{ {
foreach ($aBodyPeekMimeIndexes as $aTextMimeData) $oBodyStructure = $aFetchResponse[0]->GetFetchBodyStructure();
if ($oBodyStructure)
{ {
$sLine = \MailSo\Imap\Enumerations\FetchType::BODY_PEEK.'['.$aTextMimeData[0].']'; foreach ($oBodyStructure->SearchHtmlOrPlainParts() as $oPart)
if (0 < $iBodyTextLimit && $iBodyTextLimit < $aTextMimeData[1]) {
$sLine = \MailSo\Imap\Enumerations\FetchType::BODY_PEEK.'['.$oPart->PartID().']';
if (0 < $iBodyTextLimit && $iBodyTextLimit < $oPart->Size())
{ {
$sLine .= "<0.{$iBodyTextLimit}>"; $sLine .= "<0.{$iBodyTextLimit}>";
} }
$aFetchItems[] = $sLine; $aFetchItems[] = $sLine;
} }
}
if (0 < \count($aSignatureMimeIndexes)) $aSignatureParts = $oBodyStructure->SearchByContentType('application/pgp-signature');
if (is_array($aSignatureParts) && 0 < \count($aSignatureParts))
{ {
foreach ($aSignatureMimeIndexes as $sTextMimeIndex) foreach ($aSignatureParts as $oPart)
{ {
$aFetchItems[] = \MailSo\Imap\Enumerations\FetchType::BODY_PEEK.'['.$sTextMimeIndex.']'; $aFetchItems[] = \MailSo\Imap\Enumerations\FetchType::BODY_PEEK.'['.$oPart->PartID().']';
}
}
} }
} }
@ -1439,14 +1420,12 @@ class MailClient
if (0 < \count($aFetchResponse)) if (0 < \count($aFetchResponse))
{ {
$aFetchIndexArray = array(); $aFetchIndexArray = array();
$oFetchResponseItem = null;
foreach ($aFetchResponse as /* @var $oFetchResponseItem \MailSo\Imap\FetchResponse */ $oFetchResponseItem) foreach ($aFetchResponse as /* @var $oFetchResponseItem \MailSo\Imap\FetchResponse */ $oFetchResponseItem)
{ {
$aFetchIndexArray[($bIndexAsUid) $aFetchIndexArray[$bIndexAsUid
? $oFetchResponseItem->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::UID) ? $oFetchResponseItem->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::UID)
: $oFetchResponseItem->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::INDEX)] =& $oFetchResponseItem; : $oFetchResponseItem->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::INDEX)
] = $oFetchResponseItem;
unset($oFetchResponseItem);
} }
foreach ($aRequestIndexOrUids as $iFUid) foreach ($aRequestIndexOrUids as $iFUid)
@ -1979,8 +1958,6 @@ class MailClient
public function Folders(string $sParent = '', string $sListPattern = '*', bool $bUseListSubscribeStatus = true, int $iOptimizationLimit = 0) : ?FolderCollection public function Folders(string $sParent = '', string $sListPattern = '*', bool $bUseListSubscribeStatus = true, int $iOptimizationLimit = 0) : ?FolderCollection
{ {
$oFolderCollection = null;
$aImapSubscribedFoldersHelper = null; $aImapSubscribedFoldersHelper = null;
if ($bUseListSubscribeStatus) if ($bUseListSubscribeStatus)
{ {
@ -2000,11 +1977,11 @@ class MailClient
} }
$aFolders = $this->oImapClient->FolderList($sParent, $sListPattern); $aFolders = $this->oImapClient->FolderList($sParent, $sListPattern);
if (!$aFolders) {
return null;
}
if ($aFolders)
{
$aMailFoldersHelper = array(); $aMailFoldersHelper = array();
foreach ($aFolders as /* @var $oImapFolder \MailSo\Imap\Folder */ $oImapFolder) foreach ($aFolders as /* @var $oImapFolder \MailSo\Imap\Folder */ $oImapFolder)
{ {
$aMailFoldersHelper[] = new Folder($oImapFolder, $aMailFoldersHelper[] = new Folder($oImapFolder,
@ -2015,18 +1992,69 @@ class MailClient
$iCount = \count($aMailFoldersHelper); $iCount = \count($aMailFoldersHelper);
$aMailFoldersHelper = $this->folderListOptimization($aMailFoldersHelper, $iOptimizationLimit); $aMailFoldersHelper = $this->folderListOptimization($aMailFoldersHelper, $iOptimizationLimit);
if (!$aMailFoldersHelper) {
return null;
}
if ($aMailFoldersHelper)
{
$oFolderCollection = new FolderCollection; $oFolderCollection = new FolderCollection;
$oFolderCollection->InitByUnsortedMailFolderArray($aMailFoldersHelper); $oFolderCollection->IsThreadsSupported = $this->IsThreadsSupported();
$oFolderCollection->IsSortSupported = $this->oImapClient->IsSupported('SORT');
$oFolderCollection->Optimized = $iCount !== \count($aMailFoldersHelper); $oFolderCollection->Optimized = $iCount !== \count($aMailFoldersHelper);
$aSortedByLenImapFolders = array();
foreach ($aMailFoldersHelper as /* @var $oMailFolder Folder */ $oMailFolder)
{
$aSortedByLenImapFolders[$oMailFolder->FullNameRaw()] = $oMailFolder;
}
unset($aMailFoldersHelper);
$aAddedFolders = array();
foreach ($aSortedByLenImapFolders as /* @var $oMailFolder Folder */ $oMailFolder)
{
$sDelimiter = $oMailFolder->Delimiter();
$aFolderExplode = \explode($sDelimiter, $oMailFolder->FullNameRaw());
if (1 < \count($aFolderExplode))
{
\array_pop($aFolderExplode);
$sNonExistenFolderFullNameRaw = '';
foreach ($aFolderExplode as $sFolderExplodeItem)
{
$sNonExistenFolderFullNameRaw .= (0 < \strlen($sNonExistenFolderFullNameRaw))
? $sDelimiter.$sFolderExplodeItem : $sFolderExplodeItem;
if (!isset($aSortedByLenImapFolders[$sNonExistenFolderFullNameRaw]))
{
try
{
$aAddedFolders[$sNonExistenFolderFullNameRaw] =
Folder::NewNonExistenInstance($sNonExistenFolderFullNameRaw, $sDelimiter);
}
catch (\Throwable $oExc)
{
unset($oExc);
}
}
}
} }
} }
if ($oFolderCollection) $aSortedByLenImapFolders = \array_merge($aSortedByLenImapFolders, $aAddedFolders);
unset($aAddedFolders);
\uasort($aSortedByLenImapFolders, function ($oFolderA, $oFolderB) {
return \strnatcmp($oFolderA->FullNameRaw(), $oFolderB->FullNameRaw());
});
foreach ($aSortedByLenImapFolders as $oMailFolder)
{ {
$oFolderCollection->AddWithPositionSearch($oMailFolder);
unset($oMailFolder);
}
unset($aSortedByLenImapFolders);
$oFolderCollection->SortByCallback(function ($oFolderA, $oFolderB) { $oFolderCollection->SortByCallback(function ($oFolderA, $oFolderB) {
$sA = \strtoupper($oFolderA->FullNameRaw()); $sA = \strtoupper($oFolderA->FullNameRaw());
$sB = \strtoupper($oFolderB->FullNameRaw()); $sB = \strtoupper($oFolderB->FullNameRaw());
@ -2047,11 +2075,6 @@ class MailClient
$oFolderCollection->SetNamespace($oNamespace->GetPersonalNamespace()); $oFolderCollection->SetNamespace($oNamespace->GetPersonalNamespace());
} }
$oFolderCollection->IsThreadsSupported = $this->IsThreadsSupported();
$oFolderCollection->IsSortSupported = $this->oImapClient->IsSupported('SORT');
}
return $oFolderCollection; return $oFolderCollection;
} }

View file

@ -31,157 +31,61 @@ class Message implements \JsonSerializable
$iHeaderTimeStampInUTC = 0, $iHeaderTimeStampInUTC = 0,
$sHeaderDate = '', $sHeaderDate = '',
$aFlags = [], $aFlags = [],
$aFlagsLowerCase = []; $aFlagsLowerCase = [],
/** /**
* @var \MailSo\Mime\EmailCollection * @var \MailSo\Mime\EmailCollection
*/ */
private $oFrom; $oFrom = null,
$oSender = null,
$oReplyTo = null,
$oDeliveredTo = null,
$oTo = null,
$oCc = null,
$oBcc = null,
/** $sInReplyTo = '',
* @var \MailSo\Mime\EmailCollection
*/
private $oSender;
/** $sPlain = '',
* @var \MailSo\Mime\EmailCollection $sHtml = '',
*/
private $oReplyTo;
/**
* @var \MailSo\Mime\EmailCollection
*/
private $oDeliveredTo;
/**
* @var \MailSo\Mime\EmailCollection
*/
private $oTo;
/**
* @var \MailSo\Mime\EmailCollection
*/
private $oCc;
/**
* @var \MailSo\Mime\EmailCollection
*/
private $oBcc;
/**
* @var string
*/
private $sInReplyTo;
/**
* @var string
*/
private $sPlain;
/**
* @var string
*/
private $sHtml;
/** /**
* @var AttachmentCollection * @var AttachmentCollection
*/ */
private $oAttachments; $oAttachments = null,
/** /**
* @var array * @var array
*/ */
private $aDraftInfo; $aDraftInfo = null,
/** $sReferences = '',
* @var string
*/
private $sReferences;
/** /**
* @var int * @var int
*/ */
private $iSensitivity; $iSensitivity,
$iPriority,
/** $sDeliveryReceipt = '',
* @var int
*/
private $iPriority;
/** $sReadReceipt = '',
* @var string
*/
private $sDeliveryReceipt;
/** $aUnsubsribeLinks = array(),
* @var string
*/
private $sReadReceipt;
/** $aThreads = array(),
* @var array
*/
private $aUnsubsribeLinks;
/** $bTextPartIsTrimmed = false,
* @var array
*/
private $aThreads;
/** $sPgpSignature = '',
* @var bool $sPgpSignatureMicAlg = '',
*/ $bPgpSigned = false,
private $bTextPartIsTrimmed; $bPgpEncrypted = false;
/**
* @var string
*/
private $sPgpSignature;
/**
* @var bool
*/
private $bPgpSigned;
/**
* @var bool
*/
private $bPgpEncrypted;
function __construct() function __construct()
{ {
$this->oFrom = null;
$this->oSender = null;
$this->oReplyTo = null;
$this->oDeliveredTo = null;
$this->oTo = null;
$this->oCc = null;
$this->oBcc = null;
$this->sPlain = '';
$this->sHtml = '';
$this->oAttachments = null;
$this->aDraftInfo = null;
$this->sInReplyTo = '';
$this->sReferences = '';
$this->aUnsubsribeLinks = array();
$this->iSensitivity = \MailSo\Mime\Enumerations\Sensitivity::NOTHING; $this->iSensitivity = \MailSo\Mime\Enumerations\Sensitivity::NOTHING;
$this->iPriority = \MailSo\Mime\Enumerations\MessagePriority::NORMAL; $this->iPriority = \MailSo\Mime\Enumerations\MessagePriority::NORMAL;
$this->sDeliveryReceipt = '';
$this->sReadReceipt = '';
$this->aThreads = array();
$this->bTextPartIsTrimmed = false;
$this->sPgpSignature = '';
$this->bPgpSigned = false;
$this->bPgpEncrypted = false;
return $this;
} }
public function Plain() : string public function Plain() : string
@ -199,6 +103,11 @@ class Message implements \JsonSerializable
return $this->sPgpSignature; return $this->sPgpSignature;
} }
public function PgpSignatureMicAlg() : string
{
return $this->sPgpSignatureMicAlg;
}
public function isPgpSigned() : bool public function isPgpSigned() : bool
{ {
return $this->bPgpSigned; return $this->bPgpSigned;
@ -611,6 +520,24 @@ class Message implements \JsonSerializable
$this->sInReplyTo = $oFetchResponse->GetFetchEnvelopeValue(8, ''); $this->sInReplyTo = $oFetchResponse->GetFetchEnvelopeValue(8, '');
} }
// Content-Type: multipart/signed; micalg="pgp-sha256"; protocol="application/pgp-signature"
if ('multipart/signed' === \strtolower($this->sContentType)
&& 'application/pgp-signature' === \strtolower($oHeaders->ParameterValue(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, \MailSo\Mime\Enumerations\Parameter::PROTOCOL)))
{
$aPgpSignatureParts = $oBodyStructure ? $oBodyStructure->SearchByContentType('application/pgp-signature') : null;
if ($this->bPgpSigned = !empty($aPgpSignatureParts)) {
$sPgpSignatureText = $oFetchResponse->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::BODY.'['.$aPgpSignatureParts[0]->PartID().']');
if (\is_string($sPgpSignatureText) && 0 < \strlen($sPgpSignatureText) && 0 < \strpos($sPgpSignatureText, 'BEGIN PGP SIGNATURE')) {
$this->sPgpSignature = \trim($sPgpSignatureText);
$this->sPgpSignatureMicAlg = (string) $oHeaders->ParameterValue(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'micalg');
}
}
}
// Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"
$this->bPgpEncrypted = ('multipart/encrypted' === \strtolower($this->sContentType)
&& 'application/pgp-encrypted' === \strtolower($oHeaders->ParameterValue(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, \MailSo\Mime\Enumerations\Parameter::PROTOCOL)));
$aTextParts = $oBodyStructure ? $oBodyStructure->SearchHtmlOrPlainParts() : null; $aTextParts = $oBodyStructure ? $oBodyStructure->SearchHtmlOrPlainParts() : null;
if ($aTextParts) if ($aTextParts)
{ {
@ -674,39 +601,17 @@ class Message implements \JsonSerializable
} }
$aMatch = array(); $aMatch = array();
if (\preg_match('/-----BEGIN PGP SIGNATURE-----(.+)-----END PGP SIGNATURE-----/ism', $this->sPlain, $aMatch) && !empty($aMatch[0])) if (!$this->bPgpSigned && \preg_match('/-----BEGIN PGP SIGNATURE-----(.+)-----END PGP SIGNATURE-----/ism', $this->sPlain, $aMatch) && !empty($aMatch[0]))
{ {
$this->sPgpSignature = \trim($aMatch[0]); $this->sPgpSignature = \trim($aMatch[0]);
$this->bPgpSigned = true; $this->bPgpSigned = true;
} }
$aMatch = array(); $this->bPgpEncrypted = !$this->bPgpEncrypted && false !== \stripos($this->sPlain, '-----BEGIN PGP MESSAGE-----');
if (\preg_match('/-----BEGIN PGP MESSAGE-----/ism', $this->sPlain, $aMatch) && !empty($aMatch[0]))
{
$this->bPgpEncrypted = true;
}
unset($aHtmlParts, $aPlainParts, $aMatch); unset($aHtmlParts, $aPlainParts, $aMatch);
} }
// if (empty($this->sPgpSignature) && 'multipart/signed' === \strtolower($this->sContentType) &&
// 'application/pgp-signature' === \strtolower($oHeaders->ParameterValue(
// \MailSo\Mime\Enumerations\Header::CONTENT_TYPE,
// \MailSo\Mime\Enumerations\Parameter::PROTOCOL
// )))
// {
// $aPgpSignatureParts = $oBodyStructure ? $oBodyStructure->SearchByContentType('application/pgp-signature') : null;
// if (\is_array($aPgpSignatureParts) && 0 < \count($aPgpSignatureParts) && isset($aPgpSignatureParts[0]))
// {
// $sPgpSignatureText = $oFetchResponse->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::BODY.'['.$aPgpSignatureParts[0]->PartID().']');
// if (\is_string($sPgpSignatureText) && 0 < \strlen($sPgpSignatureText) && 0 < \strpos($sPgpSignatureText, 'BEGIN PGP SIGNATURE'))
// {
// $this->sPgpSignature = \trim($sPgpSignatureText);
// $this->bPgpSigned = true;
// }
// }
// }
if ($oBodyStructure) if ($oBodyStructure)
{ {
$aAttachmentsParts = $oBodyStructure->SearchAttachmentsParts(); $aAttachmentsParts = $oBodyStructure->SearchAttachmentsParts();

View file

@ -301,9 +301,9 @@ class Message
return $this; return $this;
} }
public function SetSender(EmailCollection $oEmails) : self public function SetSender(Email $oEmail) : self
{ {
$this->aHeadersValue[Enumerations\Header::SENDER] = $oEmails; $this->aHeadersValue[Enumerations\Header::SENDER] = $oEmail;
return $this; return $this;
} }

View file

@ -12,8 +12,9 @@ class Actions
use Actions\User; use Actions\User;
use Actions\Raw; use Actions\Raw;
use Actions\Response; use Actions\Response;
use Actions\Localization;
use Actions\Themes;
const AUTH_TFA_SIGN_ME_TOKEN_KEY = 'rltfasmauth';
const AUTH_SIGN_ME_TOKEN_KEY = 'rlsmauth'; const AUTH_SIGN_ME_TOKEN_KEY = 'rlsmauth';
const AUTH_MAILTO_TOKEN_KEY = 'rlmailtoauth'; const AUTH_MAILTO_TOKEN_KEY = 'rlmailtoauth';
const AUTH_SPEC_TOKEN_KEY = 'rlspecauth'; const AUTH_SPEC_TOKEN_KEY = 'rlspecauth';
@ -110,11 +111,6 @@ class Actions
*/ */
private $sSpecAuthToken; private $sSpecAuthToken;
/**
* @var string
*/
private $sUpdateAuthToken;
/** /**
* @access private * @access private
*/ */
@ -139,7 +135,6 @@ class Actions
$this->oSuggestionsProvider = null; $this->oSuggestionsProvider = null;
$this->sSpecAuthToken = ''; $this->sSpecAuthToken = '';
$this->sUpdateAuthToken = '';
$this->bIsJson = false; $this->bIsJson = false;
$oConfig = $this->Config(); $oConfig = $this->Config();
@ -155,13 +150,6 @@ class Actions
return $this; return $this;
} }
public function SetUpdateAuthToken(string $sUpdateAuthToken): self
{
$this->sUpdateAuthToken = $sUpdateAuthToken;
return $this;
}
public function SetIsJson(bool $bIsJson): self public function SetIsJson(bool $bIsJson): self
{ {
$this->bIsJson = $bIsJson; $this->bIsJson = $bIsJson;
@ -174,11 +162,6 @@ class Actions
return $this->sSpecAuthToken; return $this->sSpecAuthToken;
} }
public function GetUpdateAuthToken(): string
{
return $this->sUpdateAuthToken;
}
public function GetIsJson(): bool public function GetIsJson(): bool
{ {
return $this->bIsJson; return $this->bIsJson;
@ -313,9 +296,9 @@ class Actions
} }
} }
public function ParseQueryAuthString(): string public function ParseQueryString(): string
{ {
$sQuery = \trim($this->Http()->GetQueryString()); $sQuery = \trim($_SERVER['QUERY_STRING'] ?? '');
$iPos = \strpos($sQuery, '&'); $iPos = \strpos($sQuery, '&');
if (0 < $iPos) { if (0 < $iPos) {
@ -324,7 +307,7 @@ class Actions
$sQuery = \trim(\trim($sQuery), ' /'); $sQuery = \trim(\trim($sQuery), ' /');
$aSubQuery = $this->Http()->GetQuery('q'); $aSubQuery = $_GET['q'] ?? null;
if (\is_array($aSubQuery)) { if (\is_array($aSubQuery)) {
$aSubQuery = \array_map(function ($sS) { $aSubQuery = \array_map(function ($sS) {
return \trim(\trim($sS), ' /'); return \trim(\trim($sS), ' /');
@ -335,14 +318,31 @@ class Actions
} }
} }
if ('' === $this->GetSpecAuthToken()) { return $sQuery;
$aPaths = \explode('/', $sQuery);
if (!empty($aPaths[0]) && !empty($aPaths[1]) && '_' === substr($aPaths[1], 0, 1)) {
$this->SetSpecAuthToken($aPaths[1]);
}
} }
return $sQuery; // rlspecauth / AuthAccountHash
public function getAuthAccountHash() : string
{
if ('' === $this->sSpecAuthToken && !\strlen($this->GetSpecAuthLogoutTokenWithDeletion())) {
$sAuthAccountHash = $this->GetSpecAuthTokenCookie() ?: $this->GetSpecAuthToken();
if (empty($sAuthAccountHash)) {
$oAccount = $this->GetAccountFromSignMeToken();
if ($oAccount) try
{
$this->CheckMailConnection($oAccount);
$this->AuthToken($oAccount);
$sAuthAccountHash = $this->GetSpecAuthToken();
}
catch (\Throwable $oException)
{
$oException = null;
$this->ClearSignMeData($oAccount);
}
}
$this->SetSpecAuthToken($sAuthAccountHash);
}
return $this->GetSpecAuthToken();
} }
private function compileLogParams(string $sLine, ?Model\Account $oAccount = null, bool $bUrlEncode = false, array $aAdditionalParams = array()): string private function compileLogParams(string $sLine, ?Model\Account $oAccount = null, bool $bUrlEncode = false, array $aAdditionalParams = array()): string
@ -360,7 +360,7 @@ class Actions
if (false !== \strpos($sLine, '{imap:') || false !== \strpos($sLine, '{smtp:')) { if (false !== \strpos($sLine, '{imap:') || false !== \strpos($sLine, '{smtp:')) {
if (!$oAccount) { if (!$oAccount) {
$this->ParseQueryAuthString(); $this->getAuthAccountHash();
$oAccount = $this->getAccountFromToken(false); $oAccount = $this->getAccountFromToken(false);
} }
@ -414,7 +414,7 @@ class Actions
if (\preg_match('/\{user:(email|login|domain)\}/i', $sLine)) { if (\preg_match('/\{user:(email|login|domain)\}/i', $sLine)) {
if (!$oAccount) { if (!$oAccount) {
$this->ParseQueryAuthString(); $this->getAuthAccountHash();
$oAccount = $this->getAccountFromToken(false); $oAccount = $this->getAccountFromToken(false);
} }
@ -505,14 +505,9 @@ class Actions
} }
} }
public function GetSpecAuthTokenWithDeletion(): string public function GetSpecAuthTokenCookie(): string
{ {
$sResult = Utils::GetCookie(self::AUTH_SPEC_TOKEN_KEY, ''); return Utils::GetCookie(self::AUTH_SPEC_TOKEN_KEY, '');
if (0 < strlen($sResult)) {
Utils::ClearCookie(self::AUTH_SPEC_TOKEN_KEY);
}
return $sResult;
} }
public function GetSpecAuthLogoutTokenWithDeletion(): string public function GetSpecAuthLogoutTokenWithDeletion(): string
@ -691,9 +686,9 @@ class Actions
$oDriver = new \MailSo\Cache\Drivers\File(APP_PRIVATE_DATA . 'cache', $sKey); $oDriver = new \MailSo\Cache\Drivers\File(APP_PRIVATE_DATA . 'cache', $sKey);
break; break;
case ('APC' === $sDriver || 'APCU' === $sDriver) && case ('APCU' === $sDriver) &&
\MailSo\Base\Utils::FunctionExistsAndEnabled(array( \MailSo\Base\Utils::FunctionExistsAndEnabled(array(
'apc_store', 'apc_fetch', 'apc_delete', 'apc_clear_cache')): 'apcu_store', 'apcu_fetch', 'apcu_delete', 'apcu_clear_cache')):
$oDriver = new \MailSo\Cache\Drivers\APC($sKey); $oDriver = new \MailSo\Cache\Drivers\APC($sKey);
break; break;
@ -800,7 +795,7 @@ class Actions
); );
$this->oLogger->Write( $this->oLogger->Write(
'[APC:' . (\MailSo\Base\Utils::FunctionExistsAndEnabled('apc_fetch') ? 'on' : 'off') . '[APCU:' . (\MailSo\Base\Utils::FunctionExistsAndEnabled('apcu_fetch') ? 'on' : 'off') .
'][MB:' . (\MailSo\Base\Utils::FunctionExistsAndEnabled('mb_convert_encoding') ? 'on' : 'off') . '][MB:' . (\MailSo\Base\Utils::FunctionExistsAndEnabled('mb_convert_encoding') ? 'on' : 'off') .
'][PDO:' . (\class_exists('PDO') ? (\implode(',', \Pdo::getAvailableDrivers()) ?: '~') : 'off') . '][PDO:' . (\class_exists('PDO') ? (\implode(',', \Pdo::getAvailableDrivers()) ?: '~') : 'off') .
'][Streams:' . \implode(',', \stream_get_transports()) . '][Streams:' . \implode(',', \stream_get_transports()) .
@ -995,8 +990,6 @@ class Actions
'allowHtmlEditorSourceButton' => (bool)$oConfig->Get('labs', 'allow_html_editor_source_button', false), 'allowHtmlEditorSourceButton' => (bool)$oConfig->Get('labs', 'allow_html_editor_source_button', false),
'allowHtmlEditorBitiButtons' => (bool)$oConfig->Get('labs', 'allow_html_editor_biti_buttons', false), 'allowHtmlEditorBitiButtons' => (bool)$oConfig->Get('labs', 'allow_html_editor_biti_buttons', false),
'allowCtrlEnterOnCompose' => (bool)$oConfig->Get('labs', 'allow_ctrl_enter_on_compose', false), 'allowCtrlEnterOnCompose' => (bool)$oConfig->Get('labs', 'allow_ctrl_enter_on_compose', false),
'forgotPasswordLinkUrl' => \trim($oConfig->Get('login', 'forgot_password_link_url', '')),
'registrationLinkUrl' => \trim($oConfig->Get('login', 'registration_link_url', '')),
'hideSubmitButton' => (bool)$oConfig->Get('login', 'hide_submit_button', true), 'hideSubmitButton' => (bool)$oConfig->Get('login', 'hide_submit_button', true),
'useImapThread' => (bool)$oConfig->Get('labs', 'use_imap_thread', false), 'useImapThread' => (bool)$oConfig->Get('labs', 'use_imap_thread', false),
'useImapSubscribe' => (bool)$oConfig->Get('labs', 'use_imap_list_subscribe', true), 'useImapSubscribe' => (bool)$oConfig->Get('labs', 'use_imap_list_subscribe', true),
@ -1015,18 +1008,14 @@ class Actions
) : array()); ) : array());
} }
public function AppData(bool $bAdmin, string $sAuthAccountHash = ''): array public function AppData(bool $bAdmin): array
{ {
if (0 < \strlen($sAuthAccountHash) && \preg_match('/[^_\-\.a-zA-Z0-9]/', $sAuthAccountHash)) {
$sAuthAccountHash = '';
}
$oAccount = null; $oAccount = null;
$oConfig = $this->Config(); $oConfig = $this->Config();
/* /*
required by Index.html and rl.js: required by Index.html and rl.js:
NewThemeLink TemplatesLink LangLink PluginsLink AuthAccountHash PluginsLink
*/ */
$value = \ini_get('upload_max_filesize'); $value = \ini_get('upload_max_filesize');
@ -1041,7 +1030,6 @@ class Actions
'Auth' => false, 'Auth' => false,
'AccountHash' => '', 'AccountHash' => '',
'AccountSignMe' => false, 'AccountSignMe' => false,
'AuthAccountHash' => '',
'MailToEmail' => '', 'MailToEmail' => '',
'Email' => '', 'Email' => '',
'DevEmail' => '', 'DevEmail' => '',
@ -1088,10 +1076,6 @@ class Actions
'UserBackgroundHash' => '' 'UserBackgroundHash' => ''
); );
if (0 < \strlen($sAuthAccountHash)) {
$aResult['AuthAccountHash'] = $sAuthAccountHash;
}
$oSettings = null; $oSettings = null;
$passfile = APP_PRIVATE_DATA.'admin_password.txt'; $passfile = APP_PRIVATE_DATA.'admin_password.txt';
@ -1104,6 +1088,9 @@ class Actions
$oConfig->Save(); $oConfig->Save();
} }
$sLanguage = $oConfig->Get('webmail', 'language', 'en');
$UserLanguageRaw = $this->detectUserLanguage($bAdmin);
if (!$bAdmin) { if (!$bAdmin) {
$oAccount = $this->getAccountFromToken(false); $oAccount = $this->getAccountFromToken(false);
if ($oAccount) { if ($oAccount) {
@ -1152,7 +1139,60 @@ class Actions
if (!empty($aResult['StartupUrl'])) { if (!empty($aResult['StartupUrl'])) {
$aResult['StartupUrl'] = $this->compileLogParams($aResult['StartupUrl'], $oAccount, true); $aResult['StartupUrl'] = $this->compileLogParams($aResult['StartupUrl'], $oAccount, true);
} }
} else {
$aResult['ParentEmail'] = $oAccount->ParentEmail();
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
if ($oSettingsLocal instanceof Settings) {
$aResult['SentFolder'] = (string)$oSettingsLocal->GetConf('SentFolder', '');
$aResult['DraftFolder'] = (string)$oSettingsLocal->GetConf('DraftFolder', '');
$aResult['SpamFolder'] = (string)$oSettingsLocal->GetConf('SpamFolder', '');
$aResult['TrashFolder'] = (string)$oSettingsLocal->GetConf('TrashFolder', '');
$aResult['ArchiveFolder'] = (string)$oSettingsLocal->GetConf('ArchiveFolder', '');
$aResult['HideUnsubscribed'] = (bool)$oSettingsLocal->GetConf('HideUnsubscribed', $aResult['HideUnsubscribed']);
}
if ($this->GetCapa(false, Enumerations\Capa::SETTINGS, $oAccount)) {
if ($oSettings instanceof Settings) {
if ($oConfig->Get('webmail', 'allow_languages_on_settings', true)) {
$sLanguage = (string)$oSettings->GetConf('Language', $sLanguage);
}
$aResult['EditorDefaultType'] = (string)$oSettings->GetConf('EditorDefaultType', $aResult['EditorDefaultType']);
$aResult['ShowImages'] = (bool)$oSettings->GetConf('ShowImages', $aResult['ShowImages']);
$aResult['RemoveColors'] = (bool)$oSettings->GetConf('RemoveColors', $aResult['RemoveColors']);
$aResult['ContactsAutosave'] = (bool)$oSettings->GetConf('ContactsAutosave', $aResult['ContactsAutosave']);
$aResult['MPP'] = (int)$oSettings->GetConf('MPP', $aResult['MPP']);
$aResult['SoundNotification'] = (bool)$oSettings->GetConf('SoundNotification', $aResult['SoundNotification']);
$aResult['DesktopNotifications'] = (bool)$oSettings->GetConf('DesktopNotifications', $aResult['DesktopNotifications']);
$aResult['UseCheckboxesInList'] = (bool)$oSettings->GetConf('UseCheckboxesInList', $aResult['UseCheckboxesInList']);
$aResult['AllowDraftAutosave'] = (bool)$oSettings->GetConf('AllowDraftAutosave', $aResult['AllowDraftAutosave']);
$aResult['AutoLogout'] = (int)$oSettings->GetConf('AutoLogout', $aResult['AutoLogout']);
$aResult['Layout'] = (int)$oSettings->GetConf('Layout', $aResult['Layout']);
if (!$this->GetCapa(false, Enumerations\Capa::AUTOLOGOUT, $oAccount)) {
$aResult['AutoLogout'] = 0;
}
if ($this->GetCapa(false, Enumerations\Capa::USER_BACKGROUND, $oAccount)) {
$aResult['UserBackgroundName'] = (string)$oSettings->GetConf('UserBackgroundName', $aResult['UserBackgroundName']);
$aResult['UserBackgroundHash'] = (string)$oSettings->GetConf('UserBackgroundHash', $aResult['UserBackgroundHash']);
}
}
if ($oSettingsLocal instanceof Settings) {
$aResult['UseThreads'] = (bool)$oSettingsLocal->GetConf('UseThreads', $aResult['UseThreads']);
$aResult['ReplySameFolder'] = (bool)$oSettingsLocal->GetConf('ReplySameFolder', $aResult['ReplySameFolder']);
}
}
}
else {
if ($oConfig->Get('login', 'allow_languages_on_login', true)
&& $oConfig->Get('login', 'determine_user_language', true)) {
$sLanguage = $this->ValidateLanguage($UserLanguageRaw, $sLanguage, false);
}
$aResult['DevEmail'] = $oConfig->Get('labs', 'dev_email', ''); $aResult['DevEmail'] = $oConfig->Get('labs', 'dev_email', '');
$aResult['DevPassword'] = $oConfig->Get('labs', 'dev_password', ''); $aResult['DevPassword'] = $oConfig->Get('labs', 'dev_password', '');
@ -1198,98 +1238,22 @@ class Actions
$aResult['ProjectHash'] = \md5($aResult['AccountHash'] . APP_VERSION . $this->Plugins()->Hash()); $aResult['ProjectHash'] = \md5($aResult['AccountHash'] . APP_VERSION . $this->Plugins()->Hash());
$sLanguage = $oConfig->Get('webmail', 'language', 'en');
$sLanguageAdmin = $oConfig->Get('webmail', 'language_admin', 'en');
$sTheme = $oConfig->Get('webmail', 'theme', 'Default');
if (!$bAdmin && $oAccount) {
$aResult['ParentEmail'] = $oAccount->ParentEmail();
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
if ($oSettingsLocal instanceof Settings) {
$aResult['SentFolder'] = (string)$oSettingsLocal->GetConf('SentFolder', '');
$aResult['DraftFolder'] = (string)$oSettingsLocal->GetConf('DraftFolder', '');
$aResult['SpamFolder'] = (string)$oSettingsLocal->GetConf('SpamFolder', '');
$aResult['TrashFolder'] = (string)$oSettingsLocal->GetConf('TrashFolder', '');
$aResult['ArchiveFolder'] = (string)$oSettingsLocal->GetConf('ArchiveFolder', '');
$aResult['HideUnsubscribed'] = (bool)$oSettingsLocal->GetConf('HideUnsubscribed', $aResult['HideUnsubscribed']);
}
if ($this->GetCapa(false, Enumerations\Capa::SETTINGS, $oAccount)) {
if ($oSettings instanceof Settings) {
if ($oConfig->Get('webmail', 'allow_languages_on_settings', true)) {
$sLanguage = (string)$oSettings->GetConf('Language', $sLanguage);
}
$aResult['EditorDefaultType'] = (string)$oSettings->GetConf('EditorDefaultType', $aResult['EditorDefaultType']);
$aResult['ShowImages'] = (bool)$oSettings->GetConf('ShowImages', $aResult['ShowImages']);
$aResult['RemoveColors'] = (bool)$oSettings->GetConf('RemoveColors', $aResult['RemoveColors']);
$aResult['ContactsAutosave'] = (bool)$oSettings->GetConf('ContactsAutosave', $aResult['ContactsAutosave']);
$aResult['MPP'] = (int)$oSettings->GetConf('MPP', $aResult['MPP']);
$aResult['SoundNotification'] = (bool)$oSettings->GetConf('SoundNotification', $aResult['SoundNotification']);
$aResult['DesktopNotifications'] = (bool)$oSettings->GetConf('DesktopNotifications', $aResult['DesktopNotifications']);
$aResult['UseCheckboxesInList'] = (bool)$oSettings->GetConf('UseCheckboxesInList', $aResult['UseCheckboxesInList']);
$aResult['AllowDraftAutosave'] = (bool)$oSettings->GetConf('AllowDraftAutosave', $aResult['AllowDraftAutosave']);
$aResult['AutoLogout'] = (int)$oSettings->GetConf('AutoLogout', $aResult['AutoLogout']);
$aResult['Layout'] = (int)$oSettings->GetConf('Layout', $aResult['Layout']);
if (!$this->GetCapa(false, Enumerations\Capa::AUTOLOGOUT, $oAccount)) {
$aResult['AutoLogout'] = 0;
}
if ($this->GetCapa(false, Enumerations\Capa::USER_BACKGROUND, $oAccount)) {
$aResult['UserBackgroundName'] = (string)$oSettings->GetConf('UserBackgroundName', $aResult['UserBackgroundName']);
$aResult['UserBackgroundHash'] = (string)$oSettings->GetConf('UserBackgroundHash', $aResult['UserBackgroundHash']);
}
}
if ($oSettingsLocal instanceof Settings) {
$aResult['UseThreads'] = (bool)$oSettingsLocal->GetConf('UseThreads', $aResult['UseThreads']);
$aResult['ReplySameFolder'] = (bool)$oSettingsLocal->GetConf('ReplySameFolder', $aResult['ReplySameFolder']);
if ($this->GetCapa(false, Enumerations\Capa::THEMES, $oAccount)) {
$sTheme = (string)$oSettingsLocal->GetConf('Theme', $sTheme);
}
}
}
}
if (!$aResult['Auth']) {
if (!$bAdmin) {
if ($oConfig->Get('login', 'allow_languages_on_login', true) &&
$oConfig->Get('login', 'determine_user_language', true)) {
$sLanguage = $this->ValidateLanguage(
$this->detectUserLanguage($bAdmin), $sLanguage, false);
}
}
}
$sTheme = $this->ValidateTheme($sTheme);
$sStaticCache = $this->StaticCache(); $sStaticCache = $this->StaticCache();
$aResult['Theme'] = $sTheme; $aResult['Theme'] = $this->GetTheme($bAdmin);
$aResult['NewThemeLink'] = $this->ThemeLink($sTheme, $bAdmin);
$aResult['Language'] = $this->ValidateLanguage($sLanguage, '', false); $aResult['Language'] = $this->ValidateLanguage($sLanguage, '', false);
$aResult['LanguageAdmin'] = $this->ValidateLanguage($sLanguageAdmin, '', true); $aResult['UserLanguage'] = $this->ValidateLanguage($UserLanguageRaw, '', false, true);
if ($bAdmin) {
$aResult['UserLanguageRaw'] = $this->detectUserLanguage($bAdmin); $aResult['LanguageAdmin'] = $this->ValidateLanguage($oConfig->Get('webmail', 'language_admin', 'en'), '', true);
$aResult['UserLanguageAdmin'] = $this->ValidateLanguage($UserLanguageRaw, '', true, true);
$aResult['UserLanguage'] = $this->ValidateLanguage($aResult['UserLanguageRaw'], '', false, true); }
$aResult['UserLanguageAdmin'] = $this->ValidateLanguage($aResult['UserLanguageRaw'], '', true, true);
$aResult['PluginsLink'] = ''; $aResult['PluginsLink'] = '';
if (0 < $this->Plugins()->Count() && $this->Plugins()->HaveJs($bAdmin)) { if (0 < $this->Plugins()->Count() && $this->Plugins()->HaveJs($bAdmin)) {
$aResult['PluginsLink'] = './?/Plugins/0/' . ($bAdmin ? 'Admin' : 'User') . '/' . $sStaticCache . '/'; $aResult['PluginsLink'] = './?/Plugins/0/' . ($bAdmin ? 'Admin' : 'User') . '/' . $sStaticCache . '/';
} }
$aResult['LangLink'] = './?/Lang/0/' . ($bAdmin ? 'Admin' : 'App') . '/' .
($bAdmin ? $aResult['LanguageAdmin'] : $aResult['Language']) . '/' . $sStaticCache . '/';
// $aResult['TemplatesLink'] = './?/Templates/0/'.($bAdmin ? 'Admin' : 'App').'/'.$sStaticCache.'/';
$aResult['TemplatesLink'] = './?/Templates/0/' . ($bAdmin ? 'Admin' : 'App') . '/' . $sStaticCache . '/';
$bAppJsDebug = !!$this->Config()->Get('labs', 'use_app_debug_js', false); $bAppJsDebug = !!$this->Config()->Get('labs', 'use_app_debug_js', false);
$aResult['StaticLibJsLink'] = $this->StaticPath('js/' . ($bAppJsDebug ? '' : 'min/') . $aResult['StaticLibJsLink'] = $this->StaticPath('js/' . ($bAppJsDebug ? '' : 'min/') .
@ -1311,52 +1275,19 @@ class Actions
return $aResult; return $aResult;
} }
private function getUserLanguagesFromHeader(): array protected function requestSleep(int $iDelay = 1): void
{ {
$aResult = $aList = array(); $time = \microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
$sAcceptLang = \strtolower($this->Http()->GetServer('HTTP_ACCEPT_LANGUAGE', 'en')); if ($iDelay > $time) {
if (!empty($sAcceptLang) && \preg_match_all('/([a-z]{1,8}(?:-[a-z]{1,8})?)(?:;q=([0-9.]+))?/', $sAcceptLang, $aList)) { \usleep(($iDelay - $time) * 1000000);
$aResult = \array_combine($aList[1], $aList[2]);
foreach ($aResult as $n => $v) {
$aResult[$n] = $v ? $v : 1;
}
\arsort($aResult, SORT_NUMERIC);
}
return $aResult;
}
public function detectUserLanguage(bool $bAdmin = false): string
{
$sResult = '';
$aLangs = $this->getUserLanguagesFromHeader();
foreach (\array_keys($aLangs) as $sLang) {
$sLang = $this->ValidateLanguage($sLang, '', $bAdmin, true);
if (!empty($sLang)) {
$sResult = $sLang;
break;
} }
} }
return $sResult; protected function loginErrorDelay(): void
}
private function requestSleep(int $iWait = 1, int $iDelay = 1): void
{
if (0 < $iDelay && 0 < $iWait) {
if ($iWait > \time() - $_SERVER['REQUEST_TIME_FLOAT']) {
\sleep($iDelay);
}
}
}
private function loginErrorDelay(): void
{ {
$iDelay = (int)$this->Config()->Get('labs', 'login_fault_delay', 0); $iDelay = (int)$this->Config()->Get('labs', 'login_fault_delay', 0);
if (0 < $iDelay) { if (0 < $iDelay) {
$this->requestSleep(1, $iDelay); $this->requestSleep($iDelay);
} }
} }
@ -2073,13 +2004,13 @@ class Actions
if (!empty($sKey) && ($bForce || ($this->Config()->Get('cache', 'enable', true) && $this->Config()->Get('cache', 'http', true)))) { if (!empty($sKey) && ($bForce || ($this->Config()->Get('cache', 'enable', true) && $this->Config()->Get('cache', 'http', true)))) {
$iExpires = $this->Config()->Get('cache', 'http_expires', 3600); $iExpires = $this->Config()->Get('cache', 'http_expires', 3600);
if (0 < $iExpires) { if (0 < $iExpires) {
$this->oHttp->ServerUseCache($this->etag($sKey), 1382478804, \time() + $iExpires); $this->Http()->ServerUseCache($this->etag($sKey), 1382478804, \time() + $iExpires);
$bResult = true; $bResult = true;
} }
} }
if (!$bResult) { if (!$bResult) {
$this->oHttp->ServerNoCache(); $this->Http()->ServerNoCache();
} }
return $bResult; return $bResult;
@ -2090,7 +2021,7 @@ class Actions
if (!empty($sKey) && ($bForce || $this->Config()->Get('cache', 'enable', true) && $this->Config()->Get('cache', 'http', true))) { if (!empty($sKey) && ($bForce || $this->Config()->Get('cache', 'enable', true) && $this->Config()->Get('cache', 'http', true))) {
$sIfNoneMatch = $this->Http()->GetHeader('If-None-Match', ''); $sIfNoneMatch = $this->Http()->GetHeader('If-None-Match', '');
if ($this->etag($sKey) === $sIfNoneMatch) { if ($this->etag($sKey) === $sIfNoneMatch) {
$this->Http()->StatusHeader(304); \MailSo\Base\Http::StatusHeader(304);
$this->cacheByKey($sKey); $this->cacheByKey($sKey);
exit(0); exit(0);
} }
@ -2132,123 +2063,11 @@ class Actions
return $sCache; return $sCache;
} }
public function ThemeLink(string $sTheme, bool $bAdmin): string
{
return './?/Css/0/' . ($bAdmin ? 'Admin' : 'User') . '/-/' . $sTheme . '/-/' . $this->StaticCache() . '/Hash/-/';
}
public function ValidateTheme(string $sTheme): string
{
return \in_array($sTheme, $this->GetThemes()) ?
$sTheme : $this->Config()->Get('themes', 'default', 'Default');
}
public function ValidateLanguage(string $sLanguage, string $sDefault = '', bool $bAdmin = false, bool $bAllowEmptyResult = false): string
{
$aLang = \SnappyMail\L10n::getLanguages($bAdmin);
$aHelper = array(
'ar' => 'ar-SA',
'cs' => 'cs-CZ',
'no' => 'nb-NO',
'ua' => 'uk-UA',
'cn' => 'zh-CN',
'zh' => 'zh-CN',
'tw' => 'zh-TW',
'fa' => 'fa-IR'
);
$sLanguage = isset($aHelper[$sLanguage]) ? $aHelper[$sLanguage] : \strtr($sLanguage, '_', '-');
$sDefault = isset($aHelper[$sDefault]) ? $aHelper[$sDefault] : \strtr($sDefault, '_', '-');
if (\in_array($sLanguage, $aLang)) {
return $sLanguage;
}
$sLangCountry = \preg_replace_callback('/-([a-zA-Z]{2})$/', function ($aData) {
return \strtoupper($aData[0]);
}, $sLanguage);
if (\in_array($sLangCountry, $aLang)) {
return $sLangCountry;
}
if (\in_array($sDefault, $aLang)) {
return $sDefault;
}
if ($bAllowEmptyResult) {
return '';
}
$sResult = $this->Config()->Get('webmail', $bAdmin ? 'language_admin' : 'language', 'en');
return \in_array($sResult, $aLang) ? $sResult : 'en';
}
public function ValidateContactPdoType(string $sType): string public function ValidateContactPdoType(string $sType): string
{ {
return \in_array($sType, \RainLoop\Common\PdoAbstract::getAvailableDrivers()) ? $sType : 'sqlite'; return \in_array($sType, \RainLoop\Common\PdoAbstract::getAvailableDrivers()) ? $sType : 'sqlite';
} }
/**
* @staticvar array $aCache
*/
public function GetThemes(): array
{
static $aCache = array();
if ($aCache) {
return $aCache;
}
$bClear = false;
$bDefault = false;
$aCache = array();
$sDir = APP_VERSION_ROOT_PATH . 'themes';
if (\is_dir($sDir)) {
$rDirH = \opendir($sDir);
if ($rDirH) {
while (($sFile = \readdir($rDirH)) !== false) {
if ('.' !== $sFile[0] && \is_dir($sDir . '/' . $sFile) && \file_exists($sDir . '/' . $sFile . '/styles.less')) {
if ('Default' === $sFile) {
$bDefault = true;
} else if ('Clear' === $sFile) {
$bClear = true;
} else {
$aCache[] = $sFile;
}
}
}
closedir($rDirH);
}
}
$sDir = APP_INDEX_ROOT_PATH . 'themes'; // custom user themes
if (\is_dir($sDir)) {
$rDirH = \opendir($sDir);
if ($rDirH) {
while (($sFile = \readdir($rDirH)) !== false) {
if ('.' !== $sFile[0] && \is_dir($sDir . '/' . $sFile) && \file_exists($sDir . '/' . $sFile . '/styles.less')) {
$aCache[] = $sFile . '@custom';
}
}
\closedir($rDirH);
}
}
$aCache = \array_unique($aCache);
\sort($aCache);
if ($bDefault) {
\array_unshift($aCache, 'Default');
}
if ($bClear) {
\array_push($aCache, 'Clear');
}
return $aCache;
}
public function ProcessTemplate(string $sName, string $sHtml): string public function ProcessTemplate(string $sName, string $sHtml): string
{ {
$sHtml = \preg_replace('/<script/i', '<x-script', $sHtml); $sHtml = \preg_replace('/<script/i', '<x-script', $sHtml);
@ -2296,55 +2115,6 @@ class Actions
\header('Location: ' . $sUrl); \header('Location: ' . $sUrl);
} }
public function GetLanguageAndTheme(bool $bAdmin = false): array
{
$sTheme = $this->Config()->Get('webmail', 'theme', 'Default');
if ($bAdmin) {
$sLanguage = $this->Config()->Get('webmail', 'language_admin', 'en');
} else {
$oAccount = $this->GetAccount();
$sLanguage = $this->Config()->Get('webmail', 'language', 'en');
if ($oAccount) {
$oSettings = $this->SettingsProvider()->Load($oAccount);
if ($oSettings instanceof Settings) {
$sLanguage = $oSettings->GetConf('Language', $sLanguage);
}
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
if ($oSettingsLocal instanceof Settings) {
$sTheme = $oSettingsLocal->GetConf('Theme', $sTheme);
}
}
}
$sLanguage = $this->ValidateLanguage($sLanguage, '', $bAdmin);
$sTheme = $this->ValidateTheme($sTheme);
return array($sLanguage, $sTheme);
}
public function StaticI18N(string $sKey): string
{
static $sLang = null;
static $aLang = null;
if (null === $sLang) {
$aList = $this->GetLanguageAndTheme();
$sLang = $aList[0];
}
if (null === $aLang) {
$sLang = $this->ValidateLanguage($sLang, 'en');
$aLang = \SnappyMail\L10n::load($sLang, 'static');
$this->Plugins()->ReadLang($sLang, $aLang);
}
return $aLang[$aKey] ?? $sKey;
}
public function StaticPath(string $sPath): string public function StaticPath(string $sPath): string
{ {
return Utils::WebStaticPath() . $sPath; return Utils::WebStaticPath() . $sPath;

View file

@ -30,14 +30,6 @@ trait Folders
{ {
$this->Plugins()->RunHook('filter.folders-post', array($oAccount, $oFolderCollection)); $this->Plugins()->RunHook('filter.folders-post', array($oAccount, $oFolderCollection));
$aFolders = $oFolderCollection->getArrayCopy();
foreach ($aFolders as $i => $oFolder) {
if (!$oFolder->IsSelectable()) {
unset($aFolders[$i]);
}
}
$oFolderCollection->exchangeArray(\array_values($aFolders));
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount); $oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
$aSystemFolders = array(); $aSystemFolders = array();
@ -351,7 +343,7 @@ trait Folders
$aFlagsFilteredUids = array(); $aFlagsFilteredUids = array();
if (0 < strlen($sFlagsUids)) if (0 < strlen($sFlagsUids))
{ {
$aFlagsUids = explode(',', $sFlagsUids); $aFlagsUids = \explode(',', $sFlagsUids);
$aFlagsFilteredUids = \array_filter(\array_map('intval', $aFlagsUids)); $aFlagsFilteredUids = \array_filter(\array_map('intval', $aFlagsUids));
} }

View file

@ -0,0 +1,150 @@
<?php
namespace RainLoop\Actions;
trait Localization
{
public function GetLanguage(bool $bAdmin = false): string
{
$oConfig = $this->Config();
if ($bAdmin) {
$sLanguage = $oConfig->Get('webmail', 'language_admin', 'en');
} else {
$sLanguage = $oConfig->Get('webmail', 'language', 'en');
if ($oAccount = $this->getAccountFromToken(false)) {
if ($oConfig->Get('webmail', 'allow_languages_on_settings', true)
&& $this->GetCapa(false, \RainLoop\Enumerations\Capa::SETTINGS, $oAccount)
&& ($oSettings = $this->SettingsProvider()->Load($oAccount))) {
$sLanguage = $oSettings->GetConf('Language', $sLanguage);
}
} else if ($oConfig->Get('login', 'allow_languages_on_login', true) && $oConfig->Get('login', 'determine_user_language', true)) {
$sLanguage = $this->ValidateLanguage($this->detectUserLanguage($bAdmin), $sLanguage, false);
}
}
return $this->ValidateLanguage($sLanguage, '', $bAdmin) ?: 'en';
}
public function ValidateLanguage(string $sLanguage, string $sDefault = '', bool $bAdmin = false, bool $bAllowEmptyResult = false): string
{
$aLang = \SnappyMail\L10n::getLanguages($bAdmin);
$aHelper = array(
'ar' => 'ar-SA',
'cs' => 'cs-CZ',
'no' => 'nb-NO',
'ua' => 'uk-UA',
'cn' => 'zh-CN',
'zh' => 'zh-CN',
'tw' => 'zh-TW',
'fa' => 'fa-IR'
);
$sLanguage = isset($aHelper[$sLanguage]) ? $aHelper[$sLanguage] : \strtr($sLanguage, '_', '-');
$sDefault = isset($aHelper[$sDefault]) ? $aHelper[$sDefault] : \strtr($sDefault, '_', '-');
if (\in_array($sLanguage, $aLang)) {
return $sLanguage;
}
$sLanguage = \preg_replace('/^([a-zA-Z]{2})$/', '\1-\1', $sLanguage);
$sLangCountry = \preg_replace_callback('/-([a-zA-Z]{2})$/', function ($aData) {
return \strtoupper($aData[0]);
}, $sLanguage);
if (\in_array($sLangCountry, $aLang)) {
return $sLangCountry;
}
if (\in_array($sDefault, $aLang)) {
return $sDefault;
}
if ($bAllowEmptyResult) {
return '';
}
$sResult = $this->Config()->Get('webmail', $bAdmin ? 'language_admin' : 'language', 'en');
return \in_array($sResult, $aLang) ? $sResult : 'en';
}
private function getUserLanguagesFromHeader(): array
{
$aResult = $aList = array();
$sAcceptLang = \strtolower($this->Http()->GetServer('HTTP_ACCEPT_LANGUAGE', 'en'));
if (!empty($sAcceptLang) && \preg_match_all('/([a-z]{1,8}(?:-[a-z]{1,8})?)(?:;q=([0-9.]+))?/', $sAcceptLang, $aList)) {
$aResult = \array_combine($aList[1], $aList[2]);
foreach ($aResult as $n => $v) {
$aResult[$n] = $v ? $v : 1;
}
\arsort($aResult, SORT_NUMERIC);
}
return $aResult;
}
public function detectUserLanguage(bool $bAdmin): string
{
$sResult = '';
$aLangs = $this->getUserLanguagesFromHeader();
foreach (\array_keys($aLangs) as $sLang) {
$sLang = $this->ValidateLanguage($sLang, '', $bAdmin, true);
if (!empty($sLang)) {
$sResult = $sLang;
break;
}
}
return $sResult;
}
public function StaticI18N(string $sKey): string
{
static $sLang = null;
static $aLang = null;
if (null === $sLang) {
$sLang = $this->GetLanguage();
}
if (null === $aLang) {
$sLang = $this->ValidateLanguage($sLang, 'en');
$aLang = \SnappyMail\L10n::load($sLang, 'static');
$this->Plugins()->ReadLang($sLang, $aLang);
}
return $aLang[$aKey] ?? $sKey;
}
public function compileLanguage(string $sLanguage, bool $bAdmin = false) : string
{
$sLanguage = \strtr($sLanguage, '_', '-');
$aResultLang = \json_decode(\file_get_contents(APP_VERSION_ROOT_PATH.'app/localization/langs.json'), true);
$langs = \array_flip(\SnappyMail\L10n::getLanguages($bAdmin));
$aResultLang['LANGS_NAMES'] = \array_intersect_key($aResultLang['LANGS_NAMES'], $langs);
$aResultLang['LANGS_NAMES_EN'] = \array_intersect_key($aResultLang['LANGS_NAMES_EN'], $langs);
$aResultLang = \array_replace_recursive(
$aResultLang,
\SnappyMail\L10n::load($sLanguage, ($bAdmin ? 'admin' : 'user'))
);
$this->Plugins()->ReadLang($sLanguage, $aResultLang);
$sResult = \json_encode($aResultLang, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK);
$sTimeFormat = '';
$options = [$sLanguage, \substr($sLanguage, 0, 2), 'en'];
foreach ($options as $lang) {
$sFileName = APP_VERSION_ROOT_PATH.'app/localization/'.$lang.'/relativetimeformat.js';
if (\is_file($sFileName)) {
$sTimeFormat = \preg_replace('/^\\s+/', '', \file_get_contents($sFileName));
break;
}
}
return "document.documentElement.lang = '{$sLanguage}';\nrl.I18N={$sResult};\nrl.relativeTime = {$sTimeFormat};";
}
}

Some files were not shown because too many files have changed in this diff Show more