mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-08-25 10:09:20 +03:00
Merge branch 'master' into master
This commit is contained in:
commit
628f8077a3
393 changed files with 74330 additions and 64616 deletions
8
.cmds
8
.cmds
|
|
@ -6,8 +6,14 @@ yarn upgrade xxx@1.2.3
|
||||||
# transifex
|
# transifex
|
||||||
tx pull -a
|
tx pull -a
|
||||||
|
|
||||||
# dependencies checker (checking only)
|
# dependencies
|
||||||
yarn outdated
|
yarn outdated
|
||||||
|
yarn upgrade-interactive --exact --latest
|
||||||
|
|
||||||
# webpack
|
# webpack
|
||||||
webpack --color --watch
|
webpack --color --watch
|
||||||
|
|
||||||
|
#
|
||||||
|
gpg --import x
|
||||||
|
gpg --detach-sign --armor --openpgp -u 87DA4591 x
|
||||||
|
for ff in `ls *.zip`; do gpg --detach-sign --armor --openpgp -u 87DA4591 $ff; done
|
||||||
|
|
|
||||||
233
.docker/mail/setup.sh
Executable file
233
.docker/mail/setup.sh
Executable file
|
|
@ -0,0 +1,233 @@
|
||||||
|
#! /bin/sh
|
||||||
|
|
||||||
|
##
|
||||||
|
# Wrapper for various setup scripts included in the docker-mailserver
|
||||||
|
#
|
||||||
|
|
||||||
|
INFO=$(docker ps \
|
||||||
|
--no-trunc \
|
||||||
|
--format="{{.Image}}\t{{.Names}}\t{{.Command}}" | \
|
||||||
|
grep "/bin/sh -c 'supervisord -c /etc/supervisor/supervisord.conf'")
|
||||||
|
|
||||||
|
IMAGE_NAME=$(echo $INFO | awk '{print $1}')
|
||||||
|
CONTAINER_NAME=$(echo $INFO | awk '{print $2}')
|
||||||
|
CONFIG_PATH="$(pwd)/config"
|
||||||
|
|
||||||
|
if [ -z "$IMAGE_NAME" ]; then
|
||||||
|
IMAGE_NAME=tvial/docker-mailserver:latest
|
||||||
|
fi
|
||||||
|
|
||||||
|
_inspect() {
|
||||||
|
if _docker_image_exists "$IMAGE_NAME"; then
|
||||||
|
echo "Image: $IMAGE_NAME"
|
||||||
|
else
|
||||||
|
echo "Image: '$IMAGE_NAME' can’t be found."
|
||||||
|
fi
|
||||||
|
if [ -n "$CONTAINER_NAME" ]; then
|
||||||
|
echo "Container: $CONTAINER_NAME"
|
||||||
|
else
|
||||||
|
echo "Container: Not running, please start docker-mailserver."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_usage() {
|
||||||
|
echo "Usage: $0 [-i IMAGE_NAME] [-c CONTAINER_NAME] <subcommand> <subcommand> [args]
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
|
||||||
|
-i IMAGE_NAME The name of the docker-mailserver image, by default
|
||||||
|
'tvial/docker-mailserver:latest'.
|
||||||
|
-c CONTAINER_NAME The name of the running container.
|
||||||
|
|
||||||
|
-p PATH config folder path (default: $(pwd)/config)
|
||||||
|
|
||||||
|
SUBCOMMANDS:
|
||||||
|
|
||||||
|
email:
|
||||||
|
|
||||||
|
$0 email add <email> [<password>]
|
||||||
|
$0 email update <email> [<password>]
|
||||||
|
$0 email del <email>
|
||||||
|
$0 email restrict <add|del|list> <send|receive> [<email>]
|
||||||
|
$0 email list
|
||||||
|
|
||||||
|
alias:
|
||||||
|
$0 alias add <email> <recipient>
|
||||||
|
$0 alias del <email> <recipient>
|
||||||
|
$0 alias list
|
||||||
|
|
||||||
|
config:
|
||||||
|
|
||||||
|
$0 config dkim
|
||||||
|
$0 config ssl
|
||||||
|
|
||||||
|
debug:
|
||||||
|
|
||||||
|
$0 debug fetchmail
|
||||||
|
$0 debug fail2ban [<unban> <ip-address>]
|
||||||
|
$0 debug show-mail-logs
|
||||||
|
$0 debug inspect
|
||||||
|
$0 debug login <commands>
|
||||||
|
"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
_docker_image_exists() {
|
||||||
|
if docker history -q "$1" >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_docker_image() {
|
||||||
|
if ! _docker_image_exists "$IMAGE_NAME"; then
|
||||||
|
echo "Image '$IMAGE_NAME' not found. Pulling ..."
|
||||||
|
docker pull "$IMAGE_NAME"
|
||||||
|
fi
|
||||||
|
docker run \
|
||||||
|
--rm \
|
||||||
|
-v "$CONFIG_PATH":/tmp/docker-mailserver \
|
||||||
|
-ti "$IMAGE_NAME" $@
|
||||||
|
}
|
||||||
|
|
||||||
|
_docker_container() {
|
||||||
|
if [ -n "$CONTAINER_NAME" ]; then
|
||||||
|
docker exec -ti "$CONTAINER_NAME" $@
|
||||||
|
else
|
||||||
|
echo "The docker-mailserver is not running!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
while getopts ":c:i:p:" OPT; do
|
||||||
|
case $OPT in
|
||||||
|
c)
|
||||||
|
CONTAINER_NAME="$OPTARG"
|
||||||
|
;;
|
||||||
|
i)
|
||||||
|
IMAGE_NAME="$OPTARG"
|
||||||
|
;;
|
||||||
|
p)
|
||||||
|
case "$OPTARG" in
|
||||||
|
/*)
|
||||||
|
CONFIG_PATH="$OPTARG"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
CONFIG_PATH="$(pwd)/$OPTARG"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if [ ! -d "$CONFIG_PATH" ]; then
|
||||||
|
echo "Directory doesn't exist"
|
||||||
|
_usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
\?)
|
||||||
|
echo "Invalid option: -$OPTARG" >&2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
shift $((OPTIND-1))
|
||||||
|
|
||||||
|
case $1 in
|
||||||
|
|
||||||
|
email)
|
||||||
|
shift
|
||||||
|
case $1 in
|
||||||
|
add)
|
||||||
|
shift
|
||||||
|
_docker_image addmailuser $@
|
||||||
|
;;
|
||||||
|
update)
|
||||||
|
shift
|
||||||
|
_docker_image updatemailuser $@
|
||||||
|
;;
|
||||||
|
del)
|
||||||
|
shift
|
||||||
|
_docker_image delmailuser $@
|
||||||
|
;;
|
||||||
|
restrict)
|
||||||
|
shift
|
||||||
|
_docker_container restrict-access $@
|
||||||
|
;;
|
||||||
|
list)
|
||||||
|
_docker_image listmailuser
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
_usage
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
|
||||||
|
alias)
|
||||||
|
shift
|
||||||
|
case $1 in
|
||||||
|
add)
|
||||||
|
shift
|
||||||
|
_docker_image addalias $@
|
||||||
|
;;
|
||||||
|
del)
|
||||||
|
shift
|
||||||
|
_docker_image delalias $@
|
||||||
|
;;
|
||||||
|
list)
|
||||||
|
shift
|
||||||
|
_docker_image listalias $@
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
_usage
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
|
||||||
|
config)
|
||||||
|
shift
|
||||||
|
case $1 in
|
||||||
|
dkim)
|
||||||
|
_docker_image generate-dkim-config
|
||||||
|
;;
|
||||||
|
ssl)
|
||||||
|
_docker_image generate-ssl-certificate
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
_usage
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
|
||||||
|
debug)
|
||||||
|
shift
|
||||||
|
case $1 in
|
||||||
|
fetchmail)
|
||||||
|
_docker_image debug-fetchmail
|
||||||
|
;;
|
||||||
|
fail2ban)
|
||||||
|
shift
|
||||||
|
_docker_container fail2ban $@
|
||||||
|
;;
|
||||||
|
show-mail-logs)
|
||||||
|
_docker_container cat /var/log/mail/mail.log
|
||||||
|
;;
|
||||||
|
inspect)
|
||||||
|
_inspect
|
||||||
|
;;
|
||||||
|
login)
|
||||||
|
shift
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
_docker_container /bin/bash
|
||||||
|
else
|
||||||
|
_docker_container /bin/bash -c "$@"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
_usage
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
_usage
|
||||||
|
;;
|
||||||
|
esac
|
||||||
51
.docker/nginx/default.conf
Normal file
51
.docker/nginx/default.conf
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80 default;
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name localhost _;
|
||||||
|
root /var/www;
|
||||||
|
|
||||||
|
index index.php index.html;
|
||||||
|
|
||||||
|
autoindex on;
|
||||||
|
charset utf-8;
|
||||||
|
client_max_body_size 500m;
|
||||||
|
|
||||||
|
ssl_certificate /etc/nginx/ssl/localhost.cert;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/localhost.key;
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
gzip_disable "msie6";
|
||||||
|
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;
|
||||||
|
gzip_vary on;
|
||||||
|
|
||||||
|
access_log /var/log/nginx/default.access.log;
|
||||||
|
error_log /var/log/nginx/default.error.log;
|
||||||
|
|
||||||
|
location = /favicon.ico { access_log off; log_not_found off; }
|
||||||
|
location ~* favicon\.(ico|png)$ { access_log off; log_not_found off; }
|
||||||
|
location = /browserconfig.xml { access_log off; log_not_found off; }
|
||||||
|
location = /robots.txt { access_log off; log_not_found off; }
|
||||||
|
location = /humans.txt { access_log off; log_not_found off; }
|
||||||
|
location = /apple-touch-icon.png { access_log off; log_not_found off; }
|
||||||
|
location = /apple-touch-icon-precomposed.png { access_log off; log_not_found off; }
|
||||||
|
location ~ /\.ht { deny all; return 404; }
|
||||||
|
location ~ /\.git { deny all; return 404; }
|
||||||
|
location ~ /\.svn { deny all; return 404; }
|
||||||
|
|
||||||
|
location ~* ^.+\.(?:jpe?g|gif|bmp|ico|png|css|js|swf)$ {
|
||||||
|
expires 30d;
|
||||||
|
access_log off;
|
||||||
|
add_header Pragma public;
|
||||||
|
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ \.php(/|$) {
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_split_path_info ^(.+\.php)(/.*)$;
|
||||||
|
fastcgi_index index.php;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
|
fastcgi_param HTTPS off;
|
||||||
|
fastcgi_pass php:9000;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
.docker/nginx/ssl.sh
Normal file
41
.docker/nginx/ssl.sh
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
SERVER_NAME=localhost
|
||||||
|
SUBJECT="/C=RU/ST=RND/L=Taganrog/O=Umbrella Web/CN=${SERVER_NAME}"
|
||||||
|
|
||||||
|
mkdir -p ./ssl
|
||||||
|
|
||||||
|
if [ -f ./ssl/${SERVER_NAME}.cert ]; then
|
||||||
|
rm -rf ./ssl/${SERVER_NAME}.cert
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f ./ssl/${SERVER_NAME}.key ]; then
|
||||||
|
rm -rf ./ssl/${SERVER_NAME}.key
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generating ROOT pem files
|
||||||
|
openssl req -x509 -new -nodes -newkey rsa:2048 -keyout ./ssl/server_rootCA.key -sha256 -days 1024 -out ./ssl/server_rootCA.pem -subj "${SUBJECT}" 2> /dev/null
|
||||||
|
|
||||||
|
# Generating v3.ext file
|
||||||
|
cat <<EOF > ./ssl/v3.ext
|
||||||
|
authorityKeyIdentifier=keyid,issuer
|
||||||
|
basicConstraints=CA:FALSE
|
||||||
|
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
|
||||||
|
[alt_names]
|
||||||
|
DNS.1 = ${SERVER_NAME}
|
||||||
|
DNS.2 = www.${SERVER_NAME}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo " - Generating SSL key file"
|
||||||
|
openssl req -new -newkey rsa:2048 -sha256 -nodes -newkey rsa:2048 -keyout ./ssl/${SERVER_NAME}.key -subj "${SUBJECT}" -out ./ssl/server_rootCA.csr 2> /dev/null
|
||||||
|
|
||||||
|
echo " - Generating SSL certificate file"
|
||||||
|
openssl x509 -req -in ./ssl/server_rootCA.csr -CA ./ssl/server_rootCA.pem -CAkey ./ssl/server_rootCA.key -CAcreateserial -out ./ssl/${SERVER_NAME}.cert -days 3650 -sha256 -extfile ./ssl/v3.ext 2> /dev/null
|
||||||
|
|
||||||
|
# echo " - Adding certificate into local keychain"
|
||||||
|
# sudo security add-trusted-cert -d -r trustRoot -k "/Library/Keychains/System.keychain" ./ssl/server_rootCA.pem 2> /dev/null
|
||||||
|
|
||||||
|
echo " - Runing garbage collector"
|
||||||
|
rm -rf ./ssl/server_rootCA.csr ./ssl/server_rootCA.key ./ssl/server_rootCA.pem ./ssl/v3.ext ./.srl
|
||||||
0
.docker/nginx/ssl/.gitempty
Normal file
0
.docker/nginx/ssl/.gitempty
Normal file
6
.docker/node/Dockerfile
Normal file
6
.docker/node/Dockerfile
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
FROM node:9.11.2-alpine
|
||||||
|
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
RUN yarn global add gulp@3.9.1
|
||||||
|
|
||||||
|
CMD ["node", "--version"]
|
||||||
30
.docker/php/Dockerfile
Normal file
30
.docker/php/Dockerfile
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
FROM php:7.3-fpm
|
||||||
|
|
||||||
|
RUN apt-get update
|
||||||
|
|
||||||
|
RUN apt-get install -y \
|
||||||
|
git unzip wget zip curl mlocate \
|
||||||
|
libmcrypt-dev libicu-dev libpcre3-dev libicu-dev \
|
||||||
|
build-essential chrpath libssl-dev \
|
||||||
|
libxft-dev libfreetype6 libfreetype6-dev \
|
||||||
|
libpng-dev libjpeg62-turbo-dev \
|
||||||
|
libfontconfig1 libfontconfig1-dev libzip-dev
|
||||||
|
|
||||||
|
RUN pecl install mcrypt-1.0.2 && \
|
||||||
|
docker-php-ext-enable mcrypt
|
||||||
|
|
||||||
|
RUN docker-php-ext-configure intl && \
|
||||||
|
docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ && \
|
||||||
|
docker-php-ext-install opcache pdo_mysql zip intl gd
|
||||||
|
|
||||||
|
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||||
|
|
||||||
|
RUN curl --location --output /usr/local/bin/phpunit https://phar.phpunit.de/phpunit.phar && chmod +x /usr/local/bin/phpunit
|
||||||
|
|
||||||
|
RUN apt-get -y autoremove && apt-get clean
|
||||||
|
|
||||||
|
RUN sed -i '/^;catch_workers_output/ccatch_workers_output = yes' '/usr/local/etc/php-fpm.d/www.conf'
|
||||||
|
|
||||||
|
EXPOSE 9000
|
||||||
|
|
||||||
|
CMD ["php-fpm"]
|
||||||
8
.docker/php/rainloop.ini
Normal file
8
.docker/php/rainloop.ini
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
date.timezone = UTC
|
||||||
|
upload_max_filesize = 1G
|
||||||
|
post_max_size = 1G
|
||||||
|
|
||||||
|
# log_errors = On
|
||||||
|
# display_errors = On
|
||||||
|
# error_reporting = E_ALL
|
||||||
|
# error_log = /dev/stderr
|
||||||
5
.docker/tx/Dockerfile
Normal file
5
.docker/tx/Dockerfile
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
FROM python:3.6-alpine
|
||||||
|
|
||||||
|
RUN pip install transifex-client
|
||||||
|
|
||||||
|
CMD ["tx", "--version"]
|
||||||
|
|
@ -11,7 +11,7 @@ module.exports = {
|
||||||
'commonjs': true,
|
'commonjs': true,
|
||||||
'es6': true
|
'es6': true
|
||||||
},
|
},
|
||||||
'plugins': ['compat'],
|
// 'plugins': ['compat'],
|
||||||
'globals': {
|
'globals': {
|
||||||
'RL_COMMUNITY': true,
|
'RL_COMMUNITY': true,
|
||||||
'RL_ES6': true
|
'RL_ES6': true
|
||||||
|
|
@ -20,11 +20,11 @@ module.exports = {
|
||||||
// http://eslint.org/docs/rules/
|
// http://eslint.org/docs/rules/
|
||||||
'rules': {
|
'rules': {
|
||||||
// plugins
|
// plugins
|
||||||
'compat/compat': 2,
|
// 'compat/compat': 2,
|
||||||
|
|
||||||
// errors
|
// errors
|
||||||
'no-cond-assign': [2, 'always'],
|
'no-cond-assign': [2, 'always'],
|
||||||
'no-console': 2,
|
'no-console': 0,
|
||||||
'no-constant-condition': 2,
|
'no-constant-condition': 2,
|
||||||
'no-control-regex': 2,
|
'no-control-regex': 2,
|
||||||
'no-debugger': 2,
|
'no-debugger': 2,
|
||||||
|
|
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -18,6 +18,12 @@
|
||||||
/build/local
|
/build/local
|
||||||
/build/dist
|
/build/dist
|
||||||
/build/tmp
|
/build/tmp
|
||||||
|
/build/docker
|
||||||
|
/.docker/.cache
|
||||||
|
/.docker/mail/config
|
||||||
|
/.docker/nginx/ssl/*
|
||||||
|
!/.docker/nginx/ssl/.gitempty
|
||||||
|
/dist
|
||||||
/data
|
/data
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/tests/fix.php
|
/tests/fix.php
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
filter:
|
|
||||||
path:
|
|
||||||
- 'rainloop-webmail/rainloop/v/0.0.0/app/libraries/MailSo/*'
|
|
||||||
- 'rainloop-webmail/rainloop/v/0.0.0/app/libraries/RainLoop/*'
|
|
||||||
- 'dev/*'
|
|
||||||
- '/*.js'
|
|
||||||
excluded_paths:
|
|
||||||
- '*.min.js'
|
|
||||||
- '*/min/*'
|
|
||||||
- '*.min.css'
|
|
||||||
- 'vendors/*'
|
|
||||||
- 'build/*'
|
|
||||||
- 'data/*'
|
|
||||||
checks:
|
|
||||||
javascript: true
|
|
||||||
php: true
|
|
||||||
13
.travis.yml
13
.travis.yml
|
|
@ -1,13 +0,0 @@
|
||||||
language: php
|
|
||||||
php:
|
|
||||||
- '5.4'
|
|
||||||
- '5.5'
|
|
||||||
- '5.6'
|
|
||||||
# - '7.0'
|
|
||||||
# - '7.1'
|
|
||||||
#before_script:
|
|
||||||
# - sudo apt-get install -y build-essential libssl-dev
|
|
||||||
# - curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.1/install.sh | bash
|
|
||||||
# - nvm install 4.4.5
|
|
||||||
# - nvm use 4.4.5
|
|
||||||
# - npm install -g eslint babel-eslint
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
[main]
|
[main]
|
||||||
host = https://www.transifex.com
|
host = https://www.transifex.com
|
||||||
type = YAML
|
|
||||||
minimum_perc = 60
|
minimum_perc = 60
|
||||||
|
type = YAML
|
||||||
|
|
||||||
[rainloop-webmail.rainloop-webmail]
|
[rainloop-webmail.rainloop-webmail]
|
||||||
file_filter = rainloop/v/0.0.0/app/localization/webmail/<lang>.yml
|
file_filter = rainloop/v/0.0.0/app/localization/webmail/<lang>.yml
|
||||||
|
|
@ -12,3 +12,4 @@ source_lang = en
|
||||||
file_filter = rainloop/v/0.0.0/app/localization/admin/<lang>.yml
|
file_filter = rainloop/v/0.0.0/app/localization/admin/<lang>.yml
|
||||||
source_file = rainloop/v/0.0.0/app/localization/admin/_source.en.yml
|
source_file = rainloop/v/0.0.0/app/localization/admin/_source.en.yml
|
||||||
source_lang = en
|
source_lang = en
|
||||||
|
|
||||||
|
|
|
||||||
74
Makefile
Normal file
74
Makefile
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
#!make
|
||||||
|
|
||||||
|
rebuild: _down
|
||||||
|
docker-compose build --no-cache
|
||||||
|
|
||||||
|
up: _up status
|
||||||
|
_up:
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
stop: _stop status
|
||||||
|
_stop:
|
||||||
|
docker-compose stop
|
||||||
|
|
||||||
|
down: _down status
|
||||||
|
_down:
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
restart: _stop _up status
|
||||||
|
|
||||||
|
status:
|
||||||
|
@docker-compose ps
|
||||||
|
|
||||||
|
tx:
|
||||||
|
@docker-compose run --no-deps --rm tx tx pull -a -s -f -d
|
||||||
|
|
||||||
|
console-node:
|
||||||
|
@docker-compose run --no-deps --rm node sh
|
||||||
|
console-tx:
|
||||||
|
@docker-compose run --no-deps --rm tx sh
|
||||||
|
console-php:
|
||||||
|
@docker-compose exec php sh
|
||||||
|
console: console-node
|
||||||
|
|
||||||
|
logs:
|
||||||
|
@docker-compose logs --tail=100 -f
|
||||||
|
logs-db:
|
||||||
|
@docker-compose logs --tail=100 -f db
|
||||||
|
logs-php:
|
||||||
|
@docker-compose logs --tail=100 -f php
|
||||||
|
logs-node:
|
||||||
|
@docker-compose logs --tail=100 -f node
|
||||||
|
logs-nginx:
|
||||||
|
@docker-compose logs --tail=100 -f nginx
|
||||||
|
logs-mail:
|
||||||
|
@docker-compose logs --tail=100 -f mail
|
||||||
|
logs-tx:
|
||||||
|
@docker-compose logs --tail=100 -f tx
|
||||||
|
|
||||||
|
rl-lint:
|
||||||
|
@docker-compose run --no-deps --rm node gulp lint
|
||||||
|
rl-dev:
|
||||||
|
@docker-compose run --no-deps --rm node gulp
|
||||||
|
rl-watch-css:
|
||||||
|
@docker-compose run --no-deps --rm node npm run watch-css
|
||||||
|
rl-watch-js:
|
||||||
|
@docker-compose run --no-deps --rm node npm run watch-js
|
||||||
|
|
||||||
|
rl-build:
|
||||||
|
@docker-compose run --no-deps --rm node gulp all
|
||||||
|
rl-build-pro:
|
||||||
|
@docker-compose run --no-deps --rm node gulp all --pro
|
||||||
|
|
||||||
|
yarn-install:
|
||||||
|
@docker-compose run --no-deps --rm node yarn install
|
||||||
|
yarn-outdated:
|
||||||
|
@docker-compose run --no-deps --rm node yarn outdated
|
||||||
|
yarn-upgrade:
|
||||||
|
@docker-compose run --no-deps --rm node yarn upgrade-interactive --exact --latest
|
||||||
|
|
||||||
|
gpg:
|
||||||
|
docker run -it --rm -w=/var/www \
|
||||||
|
-v $(shell pwd)/.docker/.cache/.gnupg:/root/.gnupg \
|
||||||
|
-v $(shell pwd):/var/www \
|
||||||
|
ubuntu:latest bash
|
||||||
|
|
@ -26,4 +26,4 @@ Information about installing the product, check the [documentation page](http://
|
||||||
**GNU AFFERO GENERAL PUBLIC LICENSE Version 3 (AGPL)**.
|
**GNU AFFERO GENERAL PUBLIC LICENSE Version 3 (AGPL)**.
|
||||||
http://www.gnu.org/licenses/agpl-3.0.html
|
http://www.gnu.org/licenses/agpl-3.0.html
|
||||||
|
|
||||||
Copyright (c) 2017 Rainloop Team
|
Copyright (c) 2018 Rainloop Team
|
||||||
|
|
|
||||||
BIN
assets/favicon-light.ico
Normal file
BIN
assets/favicon-light.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/favicon-light.png
Normal file
BIN
assets/favicon-light.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 866 B |
|
|
@ -3,7 +3,7 @@
|
||||||
* ownCloud/Nextcloud - RainLoop Webmail package
|
* ownCloud/Nextcloud - RainLoop Webmail package
|
||||||
*
|
*
|
||||||
* @author RainLoop Team
|
* @author RainLoop Team
|
||||||
* @copyright 2017 RainLoop Team
|
* @copyright 2018 RainLoop Team
|
||||||
*
|
*
|
||||||
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
* ownCloud - RainLoop mail plugin
|
* ownCloud - RainLoop mail plugin
|
||||||
*
|
*
|
||||||
* @author RainLoop Team
|
* @author RainLoop Team
|
||||||
* @copyright 2017 RainLoop Team
|
|
||||||
*
|
*
|
||||||
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
* ownCloud - RainLoop mail plugin
|
* ownCloud - RainLoop mail plugin
|
||||||
*
|
*
|
||||||
* @author RainLoop Team
|
* @author RainLoop Team
|
||||||
* @copyright 2017 RainLoop Team
|
|
||||||
*
|
*
|
||||||
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
* ownCloud - RainLoop mail plugin
|
* ownCloud - RainLoop mail plugin
|
||||||
*
|
*
|
||||||
* @author RainLoop Team
|
* @author RainLoop Team
|
||||||
* @copyright 2017 RainLoop Team
|
|
||||||
*
|
*
|
||||||
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
* ownCloud - RainLoop mail plugin
|
* ownCloud - RainLoop mail plugin
|
||||||
*
|
*
|
||||||
* @author RainLoop Team
|
* @author RainLoop Team
|
||||||
* @copyright 2017 RainLoop Team
|
|
||||||
*
|
*
|
||||||
* https://github.com/RainLoop/owncloud
|
* https://github.com/RainLoop/owncloud
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
* ownCloud - RainLoop mail plugin
|
* ownCloud - RainLoop mail plugin
|
||||||
*
|
*
|
||||||
* @author RainLoop Team
|
* @author RainLoop Team
|
||||||
* @copyright 2017 RainLoop Team
|
|
||||||
*
|
*
|
||||||
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
* ownCloud - RainLoop mail plugin
|
* ownCloud - RainLoop mail plugin
|
||||||
*
|
*
|
||||||
* @author RainLoop Team
|
* @author RainLoop Team
|
||||||
* @copyright 2017 RainLoop Team
|
|
||||||
*
|
*
|
||||||
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
* ownCloud - RainLoop mail plugin
|
* ownCloud - RainLoop mail plugin
|
||||||
*
|
*
|
||||||
* @author RainLoop Team
|
* @author RainLoop Team
|
||||||
* @copyright 2017 RainLoop Team
|
|
||||||
*
|
*
|
||||||
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -63,25 +63,115 @@ class OC_RainLoop_Helper
|
||||||
return $sUrl;
|
return $sUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public static function mcryptSupported()
|
||||||
|
{
|
||||||
|
return function_exists('mcrypt_encrypt') &&
|
||||||
|
function_exists('mcrypt_decrypt') &&
|
||||||
|
defined('MCRYPT_RIJNDAEL_256') &&
|
||||||
|
defined('MCRYPT_MODE_ECB');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function openSslSupportedMethod()
|
||||||
|
{
|
||||||
|
$method = 'AES-256-CBC';
|
||||||
|
return function_exists('openssl_encrypt') &&
|
||||||
|
function_exists('openssl_decrypt') &&
|
||||||
|
function_exists('openssl_random_pseudo_bytes') &&
|
||||||
|
function_exists('openssl_cipher_iv_length') &&
|
||||||
|
function_exists('openssl_get_cipher_methods') &&
|
||||||
|
defined('OPENSSL_RAW_DATA') && defined('OPENSSL_ZERO_PADDING') &&
|
||||||
|
in_array($method, openssl_get_cipher_methods()) ? $method : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sMethod
|
||||||
|
* @param string $sPassword
|
||||||
|
* @param string $sSalt
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function encodePasswordSsl($sMethod, $sPassword, $sSalt)
|
||||||
|
{
|
||||||
|
$sData = base64_encode($sPassword);
|
||||||
|
|
||||||
|
$iv = @openssl_random_pseudo_bytes(openssl_cipher_iv_length($sMethod));
|
||||||
|
$r = @openssl_encrypt($sData, $sMethod, md5($sSalt), OPENSSL_RAW_DATA, $iv);
|
||||||
|
|
||||||
|
return @base64_encode(base64_encode($r).'|'.base64_encode($iv));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sMethod
|
||||||
|
* @param string $sPassword
|
||||||
|
* @param string $sSalt
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function decodePasswordSsl($sMethod, $sPassword, $sSalt)
|
||||||
|
{
|
||||||
|
$sLine = base64_decode(trim($sPassword));
|
||||||
|
$aParts = explode('|', $sLine, 2);
|
||||||
|
|
||||||
|
if (is_array($aParts) && !empty($aParts[0]) && !empty($aParts[1])) {
|
||||||
|
|
||||||
|
$sData = @base64_decode($aParts[0]);
|
||||||
|
$iv = @base64_decode($aParts[1]);
|
||||||
|
|
||||||
|
return @base64_decode(trim(
|
||||||
|
@openssl_decrypt($sData, $sMethod, md5($sSalt), OPENSSL_RAW_DATA, $iv)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sPassword
|
||||||
|
* @param string $sSalt
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public static function encodePassword($sPassword, $sSalt)
|
public static function encodePassword($sPassword, $sSalt)
|
||||||
{
|
{
|
||||||
if (function_exists('mcrypt_encrypt') && function_exists('mcrypt_create_iv') && function_exists('mcrypt_get_iv_size') &&
|
$method = self::openSslSupportedMethod();
|
||||||
defined('MCRYPT_RIJNDAEL_256') && defined('MCRYPT_MODE_ECB') && defined('MCRYPT_RAND'))
|
if ($method)
|
||||||
{
|
{
|
||||||
return @trim(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($sSalt), base64_encode($sPassword),
|
return self::encodePasswordSsl($method, $sPassword, $sSalt);
|
||||||
MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND))));
|
}
|
||||||
|
else if (self::mcryptSupported())
|
||||||
|
{
|
||||||
|
return @trim(base64_encode(
|
||||||
|
@mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($sSalt), base64_encode($sPassword), MCRYPT_MODE_ECB)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return @trim(base64_encode($sPassword));
|
return @trim(base64_encode($sPassword));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sPassword
|
||||||
|
* @param string $sSalt
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public static function decodePassword($sPassword, $sSalt)
|
public static function decodePassword($sPassword, $sSalt)
|
||||||
{
|
{
|
||||||
if (function_exists('mcrypt_encrypt') && function_exists('mcrypt_create_iv') && function_exists('mcrypt_get_iv_size') &&
|
$method = self::openSslSupportedMethod();
|
||||||
defined('MCRYPT_RIJNDAEL_256') && defined('MCRYPT_MODE_ECB') && defined('MCRYPT_RAND'))
|
if ($method)
|
||||||
{
|
{
|
||||||
return @base64_decode(trim(@mcrypt_decrypt(MCRYPT_RIJNDAEL_256, md5($sSalt), base64_decode(trim($sPassword)),
|
return self::decodePasswordSsl($method, $sPassword, $sSalt);
|
||||||
MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND))));
|
}
|
||||||
|
else if (self::mcryptSupported())
|
||||||
|
{
|
||||||
|
return @base64_decode(trim(
|
||||||
|
@mcrypt_decrypt(MCRYPT_RIJNDAEL_256, md5($sSalt), base64_decode(trim($sPassword)), MCRYPT_MODE_ECB)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return @base64_decode(trim($sPassword));
|
return @base64_decode(trim($sPassword));
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
* ownCloud - RainLoop mail plugin
|
* ownCloud - RainLoop mail plugin
|
||||||
*
|
*
|
||||||
* @author RainLoop Team
|
* @author RainLoop Team
|
||||||
* @copyright 2017 RainLoop Team
|
|
||||||
*
|
*
|
||||||
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
122
build/plugin.xml
122
build/plugin.xml
|
|
@ -1,122 +0,0 @@
|
||||||
<project name="Build" basedir=".">
|
|
||||||
|
|
||||||
<property name="INDEX_ROOT" value=".." />
|
|
||||||
<property name="DIST_PATH" value="dist" />
|
|
||||||
<property name="DIST_PLUGINS_PATH" value="dist/plugins" />
|
|
||||||
<property name="GUID" value="tmp" />
|
|
||||||
|
|
||||||
<target name="_pre_">
|
|
||||||
<mkdir dir="${DIST_PATH}" />
|
|
||||||
<mkdir dir="${DIST_PLUGINS_PATH}" />
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="_build_plugin_" depends="_pre_">
|
|
||||||
|
|
||||||
<loadfile property="plugin-version" srcfile="${INDEX_ROOT}/plugins/${plugin-name}/VERSION" />
|
|
||||||
|
|
||||||
<mkdir dir="${DIST_PLUGINS_PATH}/${plugin-name}-${plugin-version}-${GUID}" />
|
|
||||||
<mkdir dir="${DIST_PLUGINS_PATH}/${plugin-name}-${plugin-version}-${GUID}/${plugin-name}" />
|
|
||||||
|
|
||||||
<copy todir="${DIST_PLUGINS_PATH}/${plugin-name}-${plugin-version}-${GUID}/${plugin-name}">
|
|
||||||
<fileset dir="${INDEX_ROOT}/plugins/${plugin-name}" />
|
|
||||||
</copy>
|
|
||||||
|
|
||||||
<zip destfile="${DIST_PLUGINS_PATH}/${plugin-name}-${plugin-version}.zip"
|
|
||||||
basedir="${DIST_PLUGINS_PATH}/${plugin-name}-${plugin-version}-${GUID}" update="true" />
|
|
||||||
|
|
||||||
<delete dir="${DIST_PLUGINS_PATH}/${plugin-name}-${plugin-version}-${GUID}" />
|
|
||||||
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="add-x-originating-ip-header">
|
|
||||||
<antcall target="_build_plugin_">
|
|
||||||
<param name="plugin-name" value="add-x-originating-ip-header"/>
|
|
||||||
</antcall>
|
|
||||||
</target>
|
|
||||||
<target name="google-analytics">
|
|
||||||
<antcall target="_build_plugin_">
|
|
||||||
<param name="plugin-name" value="google-analytics"/>
|
|
||||||
</antcall>
|
|
||||||
</target>
|
|
||||||
<target name="piwik-analytics">
|
|
||||||
<antcall target="_build_plugin_">
|
|
||||||
<param name="plugin-name" value="piwik-analytics"/>
|
|
||||||
</antcall>
|
|
||||||
</target>
|
|
||||||
<target name="convert-headers-styles">
|
|
||||||
<antcall target="_build_plugin_">
|
|
||||||
<param name="plugin-name" value="convert-headers-styles"/>
|
|
||||||
</antcall>
|
|
||||||
</target>
|
|
||||||
<target name="recaptcha">
|
|
||||||
<antcall target="_build_plugin_">
|
|
||||||
<param name="plugin-name" value="recaptcha"/>
|
|
||||||
</antcall>
|
|
||||||
</target>
|
|
||||||
<target name="cpanel-change-password">
|
|
||||||
<antcall target="_build_plugin_">
|
|
||||||
<param name="plugin-name" value="cpanel-change-password"/>
|
|
||||||
</antcall>
|
|
||||||
</target>
|
|
||||||
<target name="directadmin-change-password">
|
|
||||||
<antcall target="_build_plugin_">
|
|
||||||
<param name="plugin-name" value="directadmin-change-password"/>
|
|
||||||
</antcall>
|
|
||||||
</target>
|
|
||||||
<target name="ispconfig-change-password">
|
|
||||||
<antcall target="_build_plugin_">
|
|
||||||
<param name="plugin-name" value="ispconfig-change-password"/>
|
|
||||||
</antcall>
|
|
||||||
</target>
|
|
||||||
<target name="poppassd-change-password">
|
|
||||||
<antcall target="_build_plugin_">
|
|
||||||
<param name="plugin-name" value="poppassd-change-password"/>
|
|
||||||
</antcall>
|
|
||||||
</target>
|
|
||||||
<target name="hmailserver-change-password">
|
|
||||||
<antcall target="_build_plugin_">
|
|
||||||
<param name="plugin-name" value="hmailserver-change-password"/>
|
|
||||||
</antcall>
|
|
||||||
</target>
|
|
||||||
<target name="ldap-change-password">
|
|
||||||
<antcall target="_build_plugin_">
|
|
||||||
<param name="plugin-name" value="ldap-change-password"/>
|
|
||||||
</antcall>
|
|
||||||
</target>
|
|
||||||
<target name="ldap-contacts-suggestions">
|
|
||||||
<antcall target="_build_plugin_">
|
|
||||||
<param name="plugin-name" value="ldap-contacts-suggestions"/>
|
|
||||||
</antcall>
|
|
||||||
</target>
|
|
||||||
<target name="snowfall-on-login-screen">
|
|
||||||
<antcall target="_build_plugin_">
|
|
||||||
<param name="plugin-name" value="snowfall-on-login-screen"/>
|
|
||||||
</antcall>
|
|
||||||
</target>
|
|
||||||
<target name="black-list">
|
|
||||||
<antcall target="_build_plugin_">
|
|
||||||
<param name="plugin-name" value="black-list"/>
|
|
||||||
</antcall>
|
|
||||||
</target>
|
|
||||||
<target name="white-list">
|
|
||||||
<antcall target="_build_plugin_">
|
|
||||||
<param name="plugin-name" value="white-list"/>
|
|
||||||
</antcall>
|
|
||||||
</target>
|
|
||||||
<target name="override-smtp-credentials">
|
|
||||||
<antcall target="_build_plugin_">
|
|
||||||
<param name="plugin-name" value="override-smtp-credentials"/>
|
|
||||||
</antcall>
|
|
||||||
</target>
|
|
||||||
<target name="custom-login-mapping">
|
|
||||||
<antcall target="_build_plugin_">
|
|
||||||
<param name="plugin-name" value="custom-login-mapping"/>
|
|
||||||
</antcall>
|
|
||||||
</target>
|
|
||||||
<target name="video-on-login-screen">
|
|
||||||
<antcall target="_build_plugin_">
|
|
||||||
<param name="plugin-name" value="video-on-login-screen"/>
|
|
||||||
</antcall>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
</project>
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
if (!\class_exists('Crypt_RSA'))
|
|
||||||
{
|
|
||||||
\set_include_path(\get_include_path().PATH_SEPARATOR.\dirname(__FILE__).'/../rainloop/v/0.0.0/app/libraries/phpseclib');
|
|
||||||
include_once 'Crypt/RSA.php';
|
|
||||||
\defined('CRYPT_RSA_MODE') || \define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
$rsa = new Crypt_RSA();
|
|
||||||
$key = $rsa->createKey(1024);
|
|
||||||
|
|
||||||
var_dump($key);
|
|
||||||
30
build/test_ssl_connection.php
Normal file
30
build/test_ssl_connection.php
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// simple connection test
|
||||||
|
|
||||||
|
$host = 'imap.gmail.com';
|
||||||
|
$port = 993;
|
||||||
|
|
||||||
|
echo $host.':'.$port;
|
||||||
|
|
||||||
|
$streamContextSettings = array(
|
||||||
|
'ssl' => array(
|
||||||
|
'verify_host' => true,
|
||||||
|
'verify_peer' => true,
|
||||||
|
'verify_peer_name' => true,
|
||||||
|
'allow_self_signed' => false
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$streamContext = stream_context_create($streamContextSettings);
|
||||||
|
|
||||||
|
$errorStr = '';
|
||||||
|
$errorNo = 0;
|
||||||
|
|
||||||
|
$connection = stream_socket_client($host.':'.$port, $errorNo, $errorStr, 5, STREAM_CLIENT_CONNECT, $streamContext);
|
||||||
|
if (is_resource($connection)) {
|
||||||
|
echo ' = OK';
|
||||||
|
fclose($connection);
|
||||||
|
} else {
|
||||||
|
echo ' = ERROR ([#'.$errorNo.'] '.$errorStr.')';
|
||||||
|
}
|
||||||
|
|
@ -342,6 +342,8 @@ class AbstractApp extends AbstractBoot
|
||||||
|
|
||||||
if (!mobile)
|
if (!mobile)
|
||||||
{
|
{
|
||||||
|
$html.addClass('rl-desktop');
|
||||||
|
|
||||||
ssm.addState({
|
ssm.addState({
|
||||||
id: 'mobile',
|
id: 'mobile',
|
||||||
query: '(max-width: 767px)',
|
query: '(max-width: 767px)',
|
||||||
|
|
|
||||||
|
|
@ -494,7 +494,7 @@ class AppUser extends AbstractApp
|
||||||
if (item.userId)
|
if (item.userId)
|
||||||
{
|
{
|
||||||
email.clear();
|
email.clear();
|
||||||
email.mailsoParse(item.userId.userid);
|
email.parse(item.userId.userid);
|
||||||
if (email.validate())
|
if (email.validate())
|
||||||
{
|
{
|
||||||
aEmails.push(email.email);
|
aEmails.push(email.email);
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,13 @@ import {getHash, setHash, clearHash} from 'Storage/RainLoop';
|
||||||
|
|
||||||
let RL_APP_DATA_STORAGE = null;
|
let RL_APP_DATA_STORAGE = null;
|
||||||
|
|
||||||
/* eslint-disable */
|
/* eslint-disable camelcase,spaced-comment */
|
||||||
window.__rlah = () => getHash();
|
window.__rlah = () => getHash();
|
||||||
window.__rlah_set = () => setHash();
|
window.__rlah_set = () => setHash();
|
||||||
window.__rlah_clear = () => clearHash();
|
window.__rlah_clear = () => clearHash();
|
||||||
window.__rlah_data = () => RL_APP_DATA_STORAGE;
|
window.__rlah_data = () => RL_APP_DATA_STORAGE;
|
||||||
/* eslint-enable */
|
|
||||||
|
|
||||||
const useJsNextBundle = (function() {
|
const useJsNextBundle = (function() {
|
||||||
|
|
||||||
/* eslint-disable */
|
|
||||||
// try {
|
// try {
|
||||||
//
|
//
|
||||||
// (function() {
|
// (function() {
|
||||||
|
|
@ -47,10 +44,9 @@ const useJsNextBundle = (function() {
|
||||||
// return true;
|
// return true;
|
||||||
// }
|
// }
|
||||||
// catch (e) {}
|
// catch (e) {}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
/* eslint-enable */
|
|
||||||
}());
|
}());
|
||||||
|
/* eslint-enable */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} id
|
* @param {string} id
|
||||||
|
|
@ -89,11 +85,12 @@ function includeLayout()
|
||||||
{
|
{
|
||||||
const app = window.document.getElementById('rl-app');
|
const app = window.document.getElementById('rl-app');
|
||||||
|
|
||||||
require('style-loader!Styles/@Boot.css');
|
require('Styles/@Boot.css');
|
||||||
|
|
||||||
if (app)
|
if (app)
|
||||||
{
|
{
|
||||||
app.innerHTML = require('Html/Layout.html').replace(/[\r\n\t]+/g, '');
|
const layout = require('Html/Layout.html');
|
||||||
|
app.innerHTML = ((layout && layout.default ? layout.default : layout) || '').replace(/[\r\n\t]+/g, '');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -225,7 +222,7 @@ function runApp()
|
||||||
p.setOptions({theme: 'rainloop'});
|
p.setOptions({theme: 'rainloop'});
|
||||||
p.start().set(5);
|
p.start().set(5);
|
||||||
|
|
||||||
const libs = jassl(appData.StaticLibJsLink).then(() => {
|
const libs = () => jassl(appData.StaticLibJsLink).then(() => {
|
||||||
if (window.$)
|
if (window.$)
|
||||||
{
|
{
|
||||||
window.$('#rl-check').remove();
|
window.$('#rl-check').remove();
|
||||||
|
|
@ -243,12 +240,14 @@ function runApp()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const common = window.Promise.all([
|
libs()
|
||||||
|
.then(() => {
|
||||||
|
p.set(20);
|
||||||
|
return window.Promise.all([
|
||||||
jassl(appData.TemplatesLink),
|
jassl(appData.TemplatesLink),
|
||||||
jassl(appData.LangLink)
|
jassl(appData.LangLink)
|
||||||
]);
|
]);
|
||||||
|
})
|
||||||
window.Promise.all([libs, common])
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
p.set(30);
|
p.set(30);
|
||||||
return jassl(useJsNextBundle ? appData.StaticAppJsNextLink : appData.StaticAppJsLink);
|
return jassl(useJsNextBundle ? appData.StaticAppJsNextLink : appData.StaticAppJsLink);
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import _ from '_';
|
||||||
import ko from 'ko';
|
import ko from 'ko';
|
||||||
import {$body} from 'Common/Globals';
|
import {$body} from 'Common/Globals';
|
||||||
import {EventKeyCode, Magics} from 'Common/Enums';
|
import {EventKeyCode, Magics} from 'Common/Enums';
|
||||||
import {trim, inArray, changeTheme} from 'Common/Utils';
|
import {trim, deModule, inArray, changeTheme} from 'Common/Utils';
|
||||||
import {reload as translatorReload} from 'Common/Translator';
|
import {reload as translatorReload} from 'Common/Translator';
|
||||||
|
|
||||||
import * as Settings from 'Storage/Settings';
|
import * as Settings from 'Storage/Settings';
|
||||||
|
|
@ -22,7 +22,7 @@ let
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function cmdError(cmd) {
|
function cmdError(cmd) {
|
||||||
return require('Html/Cmds/Error.html').replace('{{ cmd }}', cmd);
|
return deModule(require('Html/Cmds/Error.html')).replace('{{ cmd }}', cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -37,7 +37,7 @@ function cmdClear(dom) {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function cmdHelp(cmds) {
|
function cmdHelp(cmds) {
|
||||||
return require('Html/Cmds/Help.html').replace('{{ commands }}', cmds.join(' '));
|
return deModule(require('Html/Cmds/Help.html')).replace('{{ commands }}', cmds.join(' '));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -49,7 +49,7 @@ function cmdTheme(param, themes) {
|
||||||
changeTheme(param);
|
changeTheme(param);
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return require('Html/Cmds/ThemeEmpty.html').replace('{{ themes }}', themes.join(', '));
|
return deModule(require('Html/Cmds/ThemeEmpty.html')).replace('{{ themes }}', themes.join(', '));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -61,14 +61,14 @@ function cmdLang(param, isAdmin, langs) {
|
||||||
translatorReload(isAdmin, param);
|
translatorReload(isAdmin, param);
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return require('Html/Cmds/LangEmpty.html').replace('{{ langs }}', langs.join(', '));
|
return deModule(require('Html/Cmds/LangEmpty.html')).replace('{{ langs }}', langs.join(', '));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function cmdVersion() {
|
function cmdVersion() {
|
||||||
return require('Html/Cmds/Version.html').replace('{{ version }}',
|
return deModule(require('Html/Cmds/Version.html')).replace('{{ version }}',
|
||||||
Settings.appSettingsGet('version') + ' (' + Settings.appSettingsGet('appVersionType') + ')');
|
Settings.appSettingsGet('version') + ' (' + Settings.appSettingsGet('appVersionType') + ')');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -231,7 +231,7 @@ class CmdContoller
|
||||||
|
|
||||||
if (h && h[0])
|
if (h && h[0])
|
||||||
{
|
{
|
||||||
h.append($('<div></div>').html(require('Html/Cmds/Main.html').replace('{{ cmd }}', cmdLine)));
|
h.append($('<div></div>').html(deModule(require('Html/Cmds/Main.html')).replace('{{ cmd }}', cmdLine)));
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
h.append($('<div></div>').html(result));
|
h.append($('<div></div>').html(result));
|
||||||
|
|
|
||||||
|
|
@ -41,3 +41,5 @@ export const RAINLOOP_TRIAL_KEY = 'RAINLOOP-TRIAL-KEY';
|
||||||
export const DATA_IMAGE_USER_DOT_PIC = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAAAuCAYAAABXuSs3AAAHHklEQVRoQ7VZW08bVxCeXRuwIbTGXIwNtBBaqjwgVUiR8lDlbza9qe1DpVZ9aNQ/0KpPeaJK07SpcuEeCEmUAObm21bfrL9lONjexSYrWfbunj37zXdmvpkz9oIgCKTD0Wg0xPd94TDP83Q0zvWa50vzklSrdanVanqf4/D84GBGr+F+Op3S8fqoJxLOdnZgTvsO/nYhenHA+UC7CWF1uXwkb9++ldPTUwVerVbVqFQqpR8YPjQ0JCMjI5LNDijoRgP3PQVu5+5Eor2XGLg7IV4GkIdHJ/LmzRs5ODiIwNbrdR0O0GCcq4Xz4eFhmZyclP7+tDQaIik/BG5XKQn4SwG3zJTLZXn9+rUclI8UHD5YVoDDN8bSzXhONwL48fFxGR4eilzFZT1uFRIB5yT8BqCdnR3Z3d0VP9Un6XRawYJpggVrZBv38ME4XKtUKnLt2jUplUoy1PR/l3U7T6sVSAQcgMAkj8PDQ9ne3pajoyMRL7zeKsYZWHgWYDGmv78/mmdwcFA+mJlSgziHDWrERrsjEXDXegTi1tZW+DLxI2bxIrqFNYTXyDyCFweMAHCwb8e4RnTNuOsqe3t7sra21pTD0Kct666E8XlcZyzw9/RUUXK5nK5oUinUQI6TQ3cynO/v78vq6qrKXCNwlTiJJpyNGc3nZHp6uqV2dwrQWOCtZBDAV1ZWwsQk7f0wiQn5kffbAu/0/KWBYzIC1+XukfGx0RGZmppKlC2tIV0Bh4aDcZW7HhkfH8urLLZL7T2pihvlkMNnz56FiadHxicL41IsFpN41bkxsYxbRdFo9jwB8KdPn14J8KnSpBQKhQs63nPmbCVRcBUAR2Lq1VVmpksyMTFxAXjcEsQybiegESionjx5osCZOeNe1O4+EhCAX7bQSgQcxRHTMgAgcz5+/Dis/hL4uHU3/B4YGNASGHIKxuEql0k+l05AeIAF1vPnz5VxFFmdDlaJrMtZITJeSsXCOTlMunKxjLtMYOKNjQ158eJFuAuKkUOb5sEwgff19SkJUBVkThZUbnXZrtCKBQ6gbnWIkjZpyne3ejAWoGnA7Icz6irvBLgbOMicCM6TkxPx/LAkbXfgWcsazuE2kFRsKD5Z+CiqDumKncpZvieWcS6dDVD8xiYCNflpJdwcdwJOf9airLmVQ7DPzMxIYWLsXGXoVqLt5k0M3K3JUVPDZdbWNzsCp48TPFdvdnZWUz32nDha7bJ63kgAJPzSdRks9/Kf9xMJAQ1gq2NpaUmy2Yz4zar4nQC3xb99AQwCcGzLAAwuhG8YiWvcOKts+r4GOe5nMhm5efOm9lUA3E3vSZJRrKvE0fnPv//Jy5cvo5cTHIPQbSjhOoqq69evS19f6lxDKK4+sVhigZPtKJqbrQeqxd5+WR4+fKgqgT0k2XX3nhiPgETWXFhYkFzuPZ2yVq1GTSOXpE47/VjgNnD4m4GG7/LhsTx69EiwD4Vr2MwIIxgbAH18fKx1yfz8vEogNvGtWnCuhLZa9UTAreVWFsHy/b/+Vrbdl7E5REMQD2jDoUbByty+/ZnU64GkU2HzyJLhktU1cLv8nARgkYS2d3ajAgwG8qU2oLmDZ92CMaOjo7K4uCiZgbDWaRWgnZhPxLhrMUCvr69riwKZk1LHF7XqrWAO9hJxH6ozNzcnCx/PqztZg9mf6SQMscCtm2C5ke4BGMlHWTUp36036AJajDVrFMzBrhhWslQsSrFYiOqVpMriNYIgqFRq2j3FAb/zffT6zuxFXxsNzs3NTXn16lW4gYiW96w1FyedF+83xG/2FNGCRpU4NjamMsn+OZ9xE5RXqdaDdPpib6RWCzuwKF9RxqI2AVNQBwQYJoK0wdBejnqtEikP3pfP51XjUTESl12FqJEKxsEorARYDD44ONTeID7YpsEnrRvQfWAI2e8WfDaTUSIwJ0iBCmFOtOUAHvVMPp/TPwvYFVYFIuP8l+DBgwdaa2Miqwa0GgYwfeMltovbDfh6c1vIgMYcliSsKv4IWFr6VDHxvldvBAH+1sA+cnl5WYOPmmr9ir+1l9I0Cgz0yjhXjfJJ0JROnmezWbl165ayr/5fqwcBNr7IfhjMqKcvESSM4eRcCasQ3bDNObmKPLdGUGpZsN24cUNLBm9zazu4d++e6qpNBFaTuUS26U5dpuR1CxyA7J9ddrMRqlz4pwLLYawymPd++/2PADt2ugcGwq9gCCdhQ96C6xWwa6j1ceuq+I0EhW0i8MAIVJfeL3d/DVD8EKi12P6/2S2jV/EccVB54O/ejz/9HGCpoBBMta5rXMXLu53D1XAwjhXwvvv+h4BAXVe4bOu3O3ChxF08LiZFG3fel199G9CH3fLyqv24NcB44MRhpdK788U3CpyKwsCw590xmfSpzsBt0Fqc3ud3vtZigxWcVZCklVpSiN0w3q5E/h9TGMIUuA3+EQAAAABJRU5ErkJggg==';
|
export const DATA_IMAGE_USER_DOT_PIC = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAAAuCAYAAABXuSs3AAAHHklEQVRoQ7VZW08bVxCeXRuwIbTGXIwNtBBaqjwgVUiR8lDlbza9qe1DpVZ9aNQ/0KpPeaJK07SpcuEeCEmUAObm21bfrL9lONjexSYrWfbunj37zXdmvpkz9oIgCKTD0Wg0xPd94TDP83Q0zvWa50vzklSrdanVanqf4/D84GBGr+F+Op3S8fqoJxLOdnZgTvsO/nYhenHA+UC7CWF1uXwkb9++ldPTUwVerVbVqFQqpR8YPjQ0JCMjI5LNDijoRgP3PQVu5+5Eor2XGLg7IV4GkIdHJ/LmzRs5ODiIwNbrdR0O0GCcq4Xz4eFhmZyclP7+tDQaIik/BG5XKQn4SwG3zJTLZXn9+rUclI8UHD5YVoDDN8bSzXhONwL48fFxGR4eilzFZT1uFRIB5yT8BqCdnR3Z3d0VP9Un6XRawYJpggVrZBv38ME4XKtUKnLt2jUplUoy1PR/l3U7T6sVSAQcgMAkj8PDQ9ne3pajoyMRL7zeKsYZWHgWYDGmv78/mmdwcFA+mJlSgziHDWrERrsjEXDXegTi1tZW+DLxI2bxIrqFNYTXyDyCFweMAHCwb8e4RnTNuOsqe3t7sra21pTD0Kct666E8XlcZyzw9/RUUXK5nK5oUinUQI6TQ3cynO/v78vq6qrKXCNwlTiJJpyNGc3nZHp6uqV2dwrQWOCtZBDAV1ZWwsQk7f0wiQn5kffbAu/0/KWBYzIC1+XukfGx0RGZmppKlC2tIV0Bh4aDcZW7HhkfH8urLLZL7T2pihvlkMNnz56FiadHxicL41IsFpN41bkxsYxbRdFo9jwB8KdPn14J8KnSpBQKhQs63nPmbCVRcBUAR2Lq1VVmpksyMTFxAXjcEsQybiegESionjx5osCZOeNe1O4+EhCAX7bQSgQcxRHTMgAgcz5+/Dis/hL4uHU3/B4YGNASGHIKxuEql0k+l05AeIAF1vPnz5VxFFmdDlaJrMtZITJeSsXCOTlMunKxjLtMYOKNjQ158eJFuAuKkUOb5sEwgff19SkJUBVkThZUbnXZrtCKBQ6gbnWIkjZpyne3ejAWoGnA7Icz6irvBLgbOMicCM6TkxPx/LAkbXfgWcsazuE2kFRsKD5Z+CiqDumKncpZvieWcS6dDVD8xiYCNflpJdwcdwJOf9airLmVQ7DPzMxIYWLsXGXoVqLt5k0M3K3JUVPDZdbWNzsCp48TPFdvdnZWUz32nDha7bJ63kgAJPzSdRks9/Kf9xMJAQ1gq2NpaUmy2Yz4zar4nQC3xb99AQwCcGzLAAwuhG8YiWvcOKts+r4GOe5nMhm5efOm9lUA3E3vSZJRrKvE0fnPv//Jy5cvo5cTHIPQbSjhOoqq69evS19f6lxDKK4+sVhigZPtKJqbrQeqxd5+WR4+fKgqgT0k2XX3nhiPgETWXFhYkFzuPZ2yVq1GTSOXpE47/VjgNnD4m4GG7/LhsTx69EiwD4Vr2MwIIxgbAH18fKx1yfz8vEogNvGtWnCuhLZa9UTAreVWFsHy/b/+Vrbdl7E5REMQD2jDoUbByty+/ZnU64GkU2HzyJLhktU1cLv8nARgkYS2d3ajAgwG8qU2oLmDZ92CMaOjo7K4uCiZgbDWaRWgnZhPxLhrMUCvr69riwKZk1LHF7XqrWAO9hJxH6ozNzcnCx/PqztZg9mf6SQMscCtm2C5ke4BGMlHWTUp36036AJajDVrFMzBrhhWslQsSrFYiOqVpMriNYIgqFRq2j3FAb/zffT6zuxFXxsNzs3NTXn16lW4gYiW96w1FyedF+83xG/2FNGCRpU4NjamMsn+OZ9xE5RXqdaDdPpib6RWCzuwKF9RxqI2AVNQBwQYJoK0wdBejnqtEikP3pfP51XjUTESl12FqJEKxsEorARYDD44ONTeID7YpsEnrRvQfWAI2e8WfDaTUSIwJ0iBCmFOtOUAHvVMPp/TPwvYFVYFIuP8l+DBgwdaa2Miqwa0GgYwfeMltovbDfh6c1vIgMYcliSsKv4IWFr6VDHxvldvBAH+1sA+cnl5WYOPmmr9ir+1l9I0Cgz0yjhXjfJJ0JROnmezWbl165ayr/5fqwcBNr7IfhjMqKcvESSM4eRcCasQ3bDNObmKPLdGUGpZsN24cUNLBm9zazu4d++e6qpNBFaTuUS26U5dpuR1CxyA7J9ddrMRqlz4pwLLYawymPd++/2PADt2ugcGwq9gCCdhQ96C6xWwa6j1ceuq+I0EhW0i8MAIVJfeL3d/DVD8EKi12P6/2S2jV/EccVB54O/ejz/9HGCpoBBMta5rXMXLu53D1XAwjhXwvvv+h4BAXVe4bOu3O3ChxF08LiZFG3fel199G9CH3fLyqv24NcB44MRhpdK788U3CpyKwsCw590xmfSpzsBt0Fqc3ud3vtZigxWcVZCklVpSiN0w3q5E/h9TGMIUuA3+EQAAAABJRU5ErkJggg==';
|
||||||
|
|
||||||
export const DATA_IMAGE_TRANSP_PIC = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=';
|
export const DATA_IMAGE_TRANSP_PIC = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=';
|
||||||
|
|
||||||
|
export const DATA_IMAGE_LAZY_PLACEHOLDER_PIC = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC';
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,8 @@ export const ClientSideKeyName = {
|
||||||
'LastReplyAction': 6,
|
'LastReplyAction': 6,
|
||||||
'LastSignMe': 7,
|
'LastSignMe': 7,
|
||||||
'ComposeLastIdentityID': 8,
|
'ComposeLastIdentityID': 8,
|
||||||
'MessageHeaderFullInfo': 9
|
'MessageHeaderFullInfo': 9,
|
||||||
|
'MessageAttachmnetControls': 10
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ const
|
||||||
SUB_QUERY_PREFIX = '&q[]=',
|
SUB_QUERY_PREFIX = '&q[]=',
|
||||||
|
|
||||||
VERSION = Settings.appSettingsGet('version'),
|
VERSION = Settings.appSettingsGet('version'),
|
||||||
IS_MOBILE = Settings.appSettingsGet('mobile'),
|
|
||||||
|
|
||||||
WEB_PREFIX = Settings.appSettingsGet('webPath') || '',
|
WEB_PREFIX = Settings.appSettingsGet('webPath') || '',
|
||||||
VERSION_PREFIX = Settings.appSettingsGet('webVersionPath') || 'rainloop/v/' + VERSION + '/',
|
VERSION_PREFIX = Settings.appSettingsGet('webVersionPath') || 'rainloop/v/' + VERSION + '/',
|
||||||
|
|
@ -59,7 +58,7 @@ export function rootAdmin()
|
||||||
*/
|
*/
|
||||||
export function rootUser()
|
export function rootUser()
|
||||||
{
|
{
|
||||||
return IS_MOBILE ? SERVER_PREFIX + '/Mobile/' : ROOT;
|
return ROOT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -171,7 +170,7 @@ export function append()
|
||||||
*/
|
*/
|
||||||
export function change(email)
|
export function change(email)
|
||||||
{
|
{
|
||||||
return serverRequest('Change' + (IS_MOBILE ? 'Mobile' : '')) + encodeURIComponent(email) + '/';
|
return serverRequest('Change') + encodeURIComponent(email) + '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -254,6 +254,14 @@ const timeOutActionSecond = (function() {
|
||||||
|
|
||||||
export {timeOutAction, timeOutActionSecond};
|
export {timeOutAction, timeOutActionSecond};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} m
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
export function deModule(m) {
|
||||||
|
return (m && m.default ? m.default : m) || '';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
|
|
@ -629,7 +637,7 @@ export function previewMessage({title, subject, date, fromCreds, toCreds, toLabe
|
||||||
|
|
||||||
const html = bodyClone ? bodyClone.html() : '';
|
const html = bodyClone ? bodyClone.html() : '';
|
||||||
|
|
||||||
doc.write(require('Html/PreviewMessage.html')
|
doc.write(deModule(require('Html/PreviewMessage.html'))
|
||||||
.replace('{{title}}', encodeHtml(title))
|
.replace('{{title}}', encodeHtml(title))
|
||||||
.replace('{{subject}}', encodeHtml(subject))
|
.replace('{{subject}}', encodeHtml(subject))
|
||||||
.replace('{{date}}', encodeHtml(date))
|
.replace('{{date}}', encodeHtml(date))
|
||||||
|
|
@ -736,7 +744,6 @@ export function settingsSaveHelperSubscribeFunction(remote, settingName, type, f
|
||||||
*/
|
*/
|
||||||
export function findEmailAndLinks(html)
|
export function findEmailAndLinks(html)
|
||||||
{
|
{
|
||||||
// return html;
|
|
||||||
return Autolinker ? Autolinker.link(html, {
|
return Autolinker ? Autolinker.link(html, {
|
||||||
newWindow: true,
|
newWindow: true,
|
||||||
stripPrefix: false,
|
stripPrefix: false,
|
||||||
|
|
@ -1455,6 +1462,15 @@ export function mimeContentType(fileName)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} color
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isTransparent(color)
|
||||||
|
{
|
||||||
|
return 'rgba(0, 0, 0, 0)' === color || 'transparent' === color;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object} $el
|
* @param {Object} $el
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
|
|
@ -1508,14 +1524,14 @@ export function resizeAndCrop(url, value, fCallback)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} mailToUrl
|
* @param {string} mailToUrl
|
||||||
* @param {Function} PopupComposeVoreModel
|
* @param {Function} PopupComposeViewModel
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function mailToHelper(mailToUrl, PopupComposeVoreModel)
|
export function mailToHelper(mailToUrl, PopupComposeViewModel)
|
||||||
{
|
{
|
||||||
if (mailToUrl && 'mailto:' === mailToUrl.toString().substr(0, 7).toLowerCase())
|
if (mailToUrl && 'mailto:' === mailToUrl.toString().substr(0, 7).toLowerCase())
|
||||||
{
|
{
|
||||||
if (!PopupComposeVoreModel)
|
if (!PopupComposeViewModel)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -1531,28 +1547,47 @@ export function mailToHelper(mailToUrl, PopupComposeVoreModel)
|
||||||
const
|
const
|
||||||
email = mailToUrl.replace(/\?.+$/, ''),
|
email = mailToUrl.replace(/\?.+$/, ''),
|
||||||
query = mailToUrl.replace(/^[^\?]*\?/, ''),
|
query = mailToUrl.replace(/^[^\?]*\?/, ''),
|
||||||
EmailModel = require('Model/Email').default,
|
EmailModel = require('Model/Email').default;
|
||||||
emailObj = new EmailModel(),
|
|
||||||
fParseEmailLine = (line) => (line ? _.compact(_.map(decodeURIComponent(line).split(/[,]/), (item) => {
|
|
||||||
emailObj.clear();
|
|
||||||
emailObj.mailsoParse(item);
|
|
||||||
return '' !== emailObj.email ? emailObj : null;
|
|
||||||
})) : null);
|
|
||||||
|
|
||||||
to = fParseEmailLine(email);
|
|
||||||
params = simpleQueryParser(query);
|
params = simpleQueryParser(query);
|
||||||
|
|
||||||
|
if (!isUnd(params.to))
|
||||||
|
{
|
||||||
|
to = EmailModel.parseEmailLine(decodeURIComponent(email + ',' + params.to));
|
||||||
|
to = _.values(to.reduce((result, value) => {
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
if (result[value.email])
|
||||||
|
{
|
||||||
|
if (!result[value.email].name)
|
||||||
|
{
|
||||||
|
result[value.email] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result[value.email] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, {}));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
to = EmailModel.parseEmailLine(email);
|
||||||
|
}
|
||||||
|
|
||||||
if (!isUnd(params.cc))
|
if (!isUnd(params.cc))
|
||||||
{
|
{
|
||||||
cc = fParseEmailLine(decodeURIComponent(params.cc));
|
cc = EmailModel.parseEmailLine(decodeURIComponent(params.cc));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isUnd(params.bcc))
|
if (!isUnd(params.bcc))
|
||||||
{
|
{
|
||||||
bcc = fParseEmailLine(decodeURIComponent(params.bcc));
|
bcc = EmailModel.parseEmailLine(decodeURIComponent(params.bcc));
|
||||||
}
|
}
|
||||||
|
|
||||||
require('Knoin/Knoin').showScreenPopup(PopupComposeVoreModel, [
|
require('Knoin/Knoin').showScreenPopup(PopupComposeViewModel, [
|
||||||
ComposeType.Empty, null, to, cc, bcc,
|
ComposeType.Empty, null, to, cc, bcc,
|
||||||
isUnd(params.subject) ? null : pString(decodeURIComponent(params.subject)),
|
isUnd(params.subject) ? null : pString(decodeURIComponent(params.subject)),
|
||||||
isUnd(params.body) ? null : plainToHtml(pString(decodeURIComponent(params.body)))
|
isUnd(params.body) ? null : plainToHtml(pString(decodeURIComponent(params.body)))
|
||||||
|
|
|
||||||
49
dev/External/ko.js
vendored
49
dev/External/ko.js
vendored
|
|
@ -158,6 +158,7 @@ ko.bindingHandlers.tooltip = {
|
||||||
$el = $(element),
|
$el = $(element),
|
||||||
fValue = fValueAccessor(),
|
fValue = fValueAccessor(),
|
||||||
isMobile = 'on' === ($el.data('tooltip-mobile') || 'off'),
|
isMobile = 'on' === ($el.data('tooltip-mobile') || 'off'),
|
||||||
|
isI18N = 'on' === ($el.data('tooltip-i18n') || 'on'),
|
||||||
Globals = require('Common/Globals');
|
Globals = require('Common/Globals');
|
||||||
|
|
||||||
if (!Globals.bMobileDevice || isMobile)
|
if (!Globals.bMobileDevice || isMobile)
|
||||||
|
|
@ -187,7 +188,7 @@ ko.bindingHandlers.tooltip = {
|
||||||
element.__opentip.activate();
|
element.__opentip.activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('on' === ($el.data('tooltip-i18n') || 'on'))
|
if (isI18N)
|
||||||
{
|
{
|
||||||
const Translator = require('Common/Translator');
|
const Translator = require('Common/Translator');
|
||||||
|
|
||||||
|
|
@ -225,6 +226,7 @@ ko.bindingHandlers.tooltip = {
|
||||||
$el = $(element),
|
$el = $(element),
|
||||||
fValue = fValueAccessor(),
|
fValue = fValueAccessor(),
|
||||||
isMobile = 'on' === ($el.data('tooltip-mobile') || 'off'),
|
isMobile = 'on' === ($el.data('tooltip-mobile') || 'off'),
|
||||||
|
isI18N = 'on' === ($el.data('tooltip-i18n') || 'on'),
|
||||||
Globals = require('Common/Globals');
|
Globals = require('Common/Globals');
|
||||||
|
|
||||||
if ((!Globals.bMobileDevice || isMobile) && element.__opentip)
|
if ((!Globals.bMobileDevice || isMobile) && element.__opentip)
|
||||||
|
|
@ -232,9 +234,7 @@ ko.bindingHandlers.tooltip = {
|
||||||
const sValue = !ko.isObservable(fValue) && _.isFunction(fValue) ? fValue() : ko.unwrap(fValue);
|
const sValue = !ko.isObservable(fValue) && _.isFunction(fValue) ? fValue() : ko.unwrap(fValue);
|
||||||
if (sValue)
|
if (sValue)
|
||||||
{
|
{
|
||||||
element.__opentip.setContent('on' === ($el.data('tooltip-i18n') || 'on') ?
|
element.__opentip.setContent(isI18N ? require('Common/Translator').i18n(sValue) : sValue);
|
||||||
require('Common/Translator').i18n(sValue) : sValue);
|
|
||||||
|
|
||||||
element.__opentip.activate();
|
element.__opentip.activate();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -884,6 +884,7 @@ ko.bindingHandlers.emailsTags = {
|
||||||
fValue = fValueAccessor(),
|
fValue = fValueAccessor(),
|
||||||
fAllBindings = fAllBindingsAccessor(),
|
fAllBindings = fAllBindingsAccessor(),
|
||||||
fAutoCompleteSource = fAllBindings.autoCompleteSource || null,
|
fAutoCompleteSource = fAllBindings.autoCompleteSource || null,
|
||||||
|
inputDelimiters = [',', ';', '\n'],
|
||||||
fFocusCallback = (value) => {
|
fFocusCallback = (value) => {
|
||||||
if (fValue && fValue.focused)
|
if (fValue && fValue.focused)
|
||||||
{
|
{
|
||||||
|
|
@ -895,26 +896,26 @@ ko.bindingHandlers.emailsTags = {
|
||||||
parseOnBlur: true,
|
parseOnBlur: true,
|
||||||
allowDragAndDrop: true,
|
allowDragAndDrop: true,
|
||||||
focusCallback: fFocusCallback,
|
focusCallback: fFocusCallback,
|
||||||
inputDelimiters: [',', ';', '\n'],
|
inputDelimiters: inputDelimiters,
|
||||||
autoCompleteSource: fAutoCompleteSource,
|
autoCompleteSource: fAutoCompleteSource,
|
||||||
// elementHook: (el, item) => {
|
splitHook: (value) => {
|
||||||
// if (el && item)
|
const v = Utils.trim(value);
|
||||||
// {
|
if (v && -1 < inputDelimiters.indexOf(v.substr(-1))) {
|
||||||
// el.addClass('pgp');
|
return EmailModel.splitEmailLine(value);
|
||||||
// }
|
|
||||||
// },
|
|
||||||
parseHook: (input) => _.map(input, (inputValue) => {
|
|
||||||
const value = Utils.trim(inputValue);
|
|
||||||
if ('' !== value)
|
|
||||||
{
|
|
||||||
const email = new EmailModel();
|
|
||||||
email.mailsoParse(value);
|
|
||||||
return [email.toLine(false), email];
|
|
||||||
}
|
}
|
||||||
return [value, null];
|
return null;
|
||||||
|
},
|
||||||
}),
|
parseHook: (input) => _.map(
|
||||||
'change': (event) => {
|
_.flatten(_.map(
|
||||||
|
input,
|
||||||
|
(inputValue) => {
|
||||||
|
const values = EmailModel.parseEmailLine(inputValue);
|
||||||
|
return values.length ? values : inputValue;
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
(item) => (_.isObject(item) ? [item.toLine(false), item] : [item, null])
|
||||||
|
),
|
||||||
|
change: (event) => {
|
||||||
$el.data('EmailsTagsValue', event.target.value);
|
$el.data('EmailsTagsValue', event.target.value);
|
||||||
fValue(event.target.value);
|
fValue(event.target.value);
|
||||||
}
|
}
|
||||||
|
|
@ -1208,7 +1209,7 @@ ko.observable.fn.validateEmail = function() {
|
||||||
this.hasError = ko.observable(false);
|
this.hasError = ko.observable(false);
|
||||||
|
|
||||||
this.subscribe((value) => {
|
this.subscribe((value) => {
|
||||||
this.hasError('' !== value && !(/^[^@\s]+@[^@\s]+$/.test(value)));
|
this.hasError('' !== value && !((/^[^@\s]+@[^@\s]+$/).test(value)));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.valueHasMutated();
|
this.valueHasMutated();
|
||||||
|
|
@ -1220,7 +1221,7 @@ ko.observable.fn.validateSimpleEmail = function() {
|
||||||
this.hasError = ko.observable(false);
|
this.hasError = ko.observable(false);
|
||||||
|
|
||||||
this.subscribe((value) => {
|
this.subscribe((value) => {
|
||||||
this.hasError('' !== value && !(/^.+@.+$/.test(value)));
|
this.hasError('' !== value && !((/^.+@.+$/).test(value)));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.valueHasMutated();
|
this.valueHasMutated();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
|
||||||
import {trim, pString, encodeHtml} from 'Common/Utils';
|
import _ from '_';
|
||||||
|
import addressparser from 'emailjs-addressparser';
|
||||||
|
import {trim, encodeHtml, isNonEmptyArray} from 'Common/Utils';
|
||||||
|
|
||||||
class EmailModel
|
class EmailModel
|
||||||
{
|
{
|
||||||
|
|
@ -34,46 +36,6 @@ class EmailModel
|
||||||
return email.initByJson(json) ? email : null;
|
return email.initByJson(json) ? email : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @static
|
|
||||||
* @param {string} line
|
|
||||||
* @param {string=} delimiter = ';'
|
|
||||||
* @returns {Array}
|
|
||||||
*/
|
|
||||||
static splitHelper(line, delimiter = ';') {
|
|
||||||
line = line.replace(/[\r\n]+/g, '; ').replace(/[\s]+/g, ' ');
|
|
||||||
|
|
||||||
let
|
|
||||||
index = 0,
|
|
||||||
len = 0,
|
|
||||||
at = false,
|
|
||||||
char = '',
|
|
||||||
result = '';
|
|
||||||
|
|
||||||
for (len = line.length; index < len; index++)
|
|
||||||
{
|
|
||||||
char = line.charAt(index);
|
|
||||||
switch (char)
|
|
||||||
{
|
|
||||||
case '@':
|
|
||||||
at = true;
|
|
||||||
break;
|
|
||||||
case ' ':
|
|
||||||
if (at)
|
|
||||||
{
|
|
||||||
at = false;
|
|
||||||
result += delimiter;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
// no default
|
|
||||||
}
|
|
||||||
|
|
||||||
result += char;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.split(delimiter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
|
|
@ -118,32 +80,6 @@ class EmailModel
|
||||||
return -1 < (this.name + ' ' + this.email).toLowerCase().indexOf(query.toLowerCase());
|
return -1 < (this.name + ' ' + this.email).toLowerCase().indexOf(query.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} str
|
|
||||||
*/
|
|
||||||
parse(str) {
|
|
||||||
this.clear();
|
|
||||||
|
|
||||||
str = trim(str);
|
|
||||||
|
|
||||||
const
|
|
||||||
regex = /(?:"([^"]+)")? ?[<]?(.*?@[^>,]+)>?,? ?/g,
|
|
||||||
match = regex.exec(str);
|
|
||||||
|
|
||||||
if (match)
|
|
||||||
{
|
|
||||||
this.name = match[1] || '';
|
|
||||||
this.email = match[2] || '';
|
|
||||||
|
|
||||||
this.clearDuplicateName();
|
|
||||||
}
|
|
||||||
else if ((/^[^@]+@[^@]+$/).test(str))
|
|
||||||
{
|
|
||||||
this.name = '';
|
|
||||||
this.email = str;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {AjaxJsonEmail} oJsonEmail
|
* @param {AjaxJsonEmail} oJsonEmail
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
|
|
@ -176,8 +112,10 @@ class EmailModel
|
||||||
{
|
{
|
||||||
if (friendlyView && '' !== this.name)
|
if (friendlyView && '' !== this.name)
|
||||||
{
|
{
|
||||||
result = wrapWithLink ? '<a href="mailto:' + encodeHtml('"' + this.name + '" <' + this.email + '>') +
|
result = wrapWithLink ? '<a href="mailto:' + encodeHtml(this.email) + '?to=' + encodeHtml('"' + this.name + '" <' + this.email + '>') +
|
||||||
'" target="_blank" tabindex="-1">' + encodeHtml(this.name) + '</a>' : (useEncodeHtml ? encodeHtml(this.name) : this.name);
|
'" target="_blank" tabindex="-1">' + encodeHtml(this.name) + '</a>' : (useEncodeHtml ? encodeHtml(this.name) : this.name);
|
||||||
|
// result = wrapWithLink ? '<a href="mailto:' + encodeHtml('"' + this.name + '" <' + this.email + '>') +
|
||||||
|
// '" target="_blank" tabindex="-1">' + encodeHtml(this.name) + '</a>' : (useEncodeHtml ? encodeHtml(this.name) : this.name);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -187,11 +125,17 @@ class EmailModel
|
||||||
if (wrapWithLink)
|
if (wrapWithLink)
|
||||||
{
|
{
|
||||||
result = encodeHtml('"' + this.name + '" <') + '<a href="mailto:' +
|
result = encodeHtml('"' + this.name + '" <') + '<a href="mailto:' +
|
||||||
encodeHtml('"' + this.name + '" <' + this.email + '>') +
|
encodeHtml(this.email) + '?to=' + encodeHtml('"' + this.name + '" <' + this.email + '>') +
|
||||||
'" target="_blank" tabindex="-1">' +
|
'" target="_blank" tabindex="-1">' +
|
||||||
encodeHtml(result) +
|
encodeHtml(result) +
|
||||||
'</a>' +
|
'</a>' +
|
||||||
encodeHtml('>');
|
encodeHtml('>');
|
||||||
|
// result = encodeHtml('"' + this.name + '" <') + '<a href="mailto:' +
|
||||||
|
// encodeHtml('"' + this.name + '" <' + this.email + '>') +
|
||||||
|
// '" target="_blank" tabindex="-1">' +
|
||||||
|
// encodeHtml(result) +
|
||||||
|
// '</a>' +
|
||||||
|
// encodeHtml('>');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -212,166 +156,69 @@ class EmailModel
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static splitEmailLine(line) {
|
||||||
|
const parsedResult = addressparser(line);
|
||||||
|
if (isNonEmptyArray(parsedResult))
|
||||||
|
{
|
||||||
|
const result = [];
|
||||||
|
let exists = false;
|
||||||
|
parsedResult.forEach((item) => {
|
||||||
|
const address = item.address ? new EmailModel(
|
||||||
|
item.address.replace(/^[<]+(.*)[>]+$/g, '$1'),
|
||||||
|
item.name || ''
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
if (address && address.email) {
|
||||||
|
exists = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(address ? address.toLine(false) : item.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
return exists ? result : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static parseEmailLine(line) {
|
||||||
|
const parsedResult = addressparser(line);
|
||||||
|
if (isNonEmptyArray(parsedResult))
|
||||||
|
{
|
||||||
|
return _.compact(parsedResult.map(
|
||||||
|
(item) => (item.address ? new EmailModel(
|
||||||
|
item.address.replace(/^[<]+(.*)[>]+$/g, '$1'),
|
||||||
|
item.name || ''
|
||||||
|
) : null)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} $sEmailAddress
|
* @param {string} emailAddress
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
mailsoParse($sEmailAddress) {
|
parse(emailAddress) {
|
||||||
$sEmailAddress = trim($sEmailAddress);
|
emailAddress = trim(emailAddress);
|
||||||
if ('' === $sEmailAddress)
|
if ('' === emailAddress)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const substr = (str, start, len) => {
|
const result = addressparser(emailAddress);
|
||||||
str = pString(str);
|
if (isNonEmptyArray(result) && result[0])
|
||||||
let end = str.length;
|
|
||||||
|
|
||||||
if (0 > start)
|
|
||||||
{
|
{
|
||||||
start += end;
|
this.name = result[0].name || '';
|
||||||
}
|
this.email = result[0].address || '';
|
||||||
|
|
||||||
end = 'undefined' === typeof len ? end : (0 > len ? len + end : len + start);
|
|
||||||
|
|
||||||
return start >= str.length || 0 > start || start > end ? false : str.slice(start, end);
|
|
||||||
};
|
|
||||||
|
|
||||||
const substrReplace = (str, replace, start, length) => {
|
|
||||||
str = pString(str);
|
|
||||||
if (0 > start)
|
|
||||||
{
|
|
||||||
start += str.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
length = 'undefined' !== typeof length ? length : str.length;
|
|
||||||
if (0 > length)
|
|
||||||
{
|
|
||||||
length = length + str.length - start;
|
|
||||||
}
|
|
||||||
return str.slice(0, start) + replace.substr(0, length) + replace.slice(length) + str.slice(start + length);
|
|
||||||
};
|
|
||||||
|
|
||||||
let
|
|
||||||
$sName = '',
|
|
||||||
$sEmail = '',
|
|
||||||
$sComment = '',
|
|
||||||
|
|
||||||
$bInName = false,
|
|
||||||
$bInAddress = false,
|
|
||||||
$bInComment = false,
|
|
||||||
|
|
||||||
$aRegs = null,
|
|
||||||
|
|
||||||
$iStartIndex = 0,
|
|
||||||
$iEndIndex = 0,
|
|
||||||
$iCurrentIndex = 0;
|
|
||||||
|
|
||||||
while ($iCurrentIndex < $sEmailAddress.length)
|
|
||||||
{
|
|
||||||
switch ($sEmailAddress.substr($iCurrentIndex, 1))
|
|
||||||
{
|
|
||||||
case '"':
|
|
||||||
if ((!$bInName) && (!$bInAddress) && (!$bInComment))
|
|
||||||
{
|
|
||||||
$bInName = true;
|
|
||||||
$iStartIndex = $iCurrentIndex;
|
|
||||||
}
|
|
||||||
else if ((!$bInAddress) && (!$bInComment))
|
|
||||||
{
|
|
||||||
$iEndIndex = $iCurrentIndex;
|
|
||||||
$sName = substr($sEmailAddress, $iStartIndex + 1, $iEndIndex - $iStartIndex - 1);
|
|
||||||
$sEmailAddress = substrReplace($sEmailAddress, '', $iStartIndex, $iEndIndex - $iStartIndex + 1);
|
|
||||||
$iEndIndex = 0;
|
|
||||||
$iCurrentIndex = 0;
|
|
||||||
$iStartIndex = 0;
|
|
||||||
$bInName = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case '<':
|
|
||||||
if ((!$bInName) && (!$bInAddress) && (!$bInComment))
|
|
||||||
{
|
|
||||||
if (0 < $iCurrentIndex && 0 === $sName.length)
|
|
||||||
{
|
|
||||||
$sName = substr($sEmailAddress, 0, $iCurrentIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
$bInAddress = true;
|
|
||||||
$iStartIndex = $iCurrentIndex;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case '>':
|
|
||||||
if ($bInAddress)
|
|
||||||
{
|
|
||||||
$iEndIndex = $iCurrentIndex;
|
|
||||||
$sEmail = substr($sEmailAddress, $iStartIndex + 1, $iEndIndex - $iStartIndex - 1);
|
|
||||||
$sEmailAddress = substrReplace($sEmailAddress, '', $iStartIndex, $iEndIndex - $iStartIndex + 1);
|
|
||||||
$iEndIndex = 0;
|
|
||||||
$iCurrentIndex = 0;
|
|
||||||
$iStartIndex = 0;
|
|
||||||
$bInAddress = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case '(':
|
|
||||||
if ((!$bInName) && (!$bInAddress) && (!$bInComment))
|
|
||||||
{
|
|
||||||
$bInComment = true;
|
|
||||||
$iStartIndex = $iCurrentIndex;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ')':
|
|
||||||
if ($bInComment)
|
|
||||||
{
|
|
||||||
$iEndIndex = $iCurrentIndex;
|
|
||||||
$sComment = substr($sEmailAddress, $iStartIndex + 1, $iEndIndex - $iStartIndex - 1);
|
|
||||||
$sEmailAddress = substrReplace($sEmailAddress, '', $iStartIndex, $iEndIndex - $iStartIndex + 1);
|
|
||||||
$iEndIndex = 0;
|
|
||||||
$iCurrentIndex = 0;
|
|
||||||
$iStartIndex = 0;
|
|
||||||
$bInComment = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case '\\':
|
|
||||||
$iCurrentIndex += 1;
|
|
||||||
break;
|
|
||||||
// no default
|
|
||||||
}
|
|
||||||
|
|
||||||
$iCurrentIndex += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (0 === $sEmail.length)
|
|
||||||
{
|
|
||||||
$aRegs = $sEmailAddress.match(/[^@\s]+@\S+/i);
|
|
||||||
if ($aRegs && $aRegs[0])
|
|
||||||
{
|
|
||||||
$sEmail = $aRegs[0];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$sName = $sEmailAddress;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (0 < $sEmail.length && 0 === $sName.length && 0 === $sComment.length)
|
|
||||||
{
|
|
||||||
$sName = $sEmailAddress.replace($sEmail, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
$sEmail = trim($sEmail).replace(/^[<]+/, '').replace(/[>]+$/, '');
|
|
||||||
$sName = trim($sName).replace(/^["']+/, '').replace(/["']+$/, '');
|
|
||||||
$sComment = trim($sComment).replace(/^[(]+/, '').replace(/[)]+$/, '');
|
|
||||||
|
|
||||||
// Remove backslash
|
|
||||||
$sName = $sName.replace(/\\\\(.)/g, '$1');
|
|
||||||
$sComment = $sComment.replace(/\\\\(.)/g, '$1');
|
|
||||||
|
|
||||||
this.name = $sName;
|
|
||||||
this.email = $sEmail;
|
|
||||||
|
|
||||||
this.clearDuplicateName();
|
this.clearDuplicateName();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {EmailModel, EmailModel as default};
|
export {EmailModel, EmailModel as default};
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,11 @@ import $ from '$';
|
||||||
import ko from 'ko';
|
import ko from 'ko';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
import lozad from 'lozad';
|
||||||
|
|
||||||
import {MessagePriority, SignedVerifyStatus} from 'Common/Enums';
|
import {MessagePriority, SignedVerifyStatus} from 'Common/Enums';
|
||||||
import {i18n} from 'Common/Translator';
|
import {i18n} from 'Common/Translator';
|
||||||
|
import {DATA_IMAGE_LAZY_PLACEHOLDER_PIC} from 'Common/Consts';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
pInt, inArray, isArray, isUnd, trim,
|
pInt, inArray, isArray, isUnd, trim,
|
||||||
|
|
@ -743,11 +745,29 @@ class MessageModel extends AbstractModel
|
||||||
if (this.body)
|
if (this.body)
|
||||||
{
|
{
|
||||||
$('.lazy.lazy-inited[data-original]', this.body).each(function() {
|
$('.lazy.lazy-inited[data-original]', this.body).each(function() {
|
||||||
$(this).attr('src', $(this).attr('data-original')).removeAttr('data-original'); // eslint-disable-line no-invalid-this
|
$(this).attr('src', $(this).attr('data-original')) // eslint-disable-line no-invalid-this
|
||||||
|
.removeAttr('data-original').removeAttr('data-loaded');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lozad() {
|
||||||
|
lozad('img.lazy:not(.lazy-inited)', {
|
||||||
|
threshold: 0.4,
|
||||||
|
load: (element) => {
|
||||||
|
// console.log('lazy', element.dataset.original);
|
||||||
|
element.src = DATA_IMAGE_LAZY_PLACEHOLDER_PIC;
|
||||||
|
$(element)
|
||||||
|
.addClass('lazy-inited')
|
||||||
|
.attr('src', element.dataset.original)
|
||||||
|
.removeAttr('data-loaded')
|
||||||
|
.removeAttr('data-original')
|
||||||
|
.css({opacity: 0.3})
|
||||||
|
.animate({opacity: 1}, 500);
|
||||||
|
}
|
||||||
|
}).observe();
|
||||||
|
}
|
||||||
|
|
||||||
showExternalImages(lazy = false) {
|
showExternalImages(lazy = false) {
|
||||||
if (this.body && this.body.data('rl-has-images'))
|
if (this.body && this.body.data('rl-has-images'))
|
||||||
{
|
{
|
||||||
|
|
@ -762,11 +782,12 @@ class MessageModel extends AbstractModel
|
||||||
$this
|
$this
|
||||||
.addClass('lazy')
|
.addClass('lazy')
|
||||||
.attr('data-original', $this.attr(attr))
|
.attr('data-original', $this.attr(attr))
|
||||||
.removeAttr(attr);
|
.removeAttr('data-loaded');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$this.attr('src', $this.attr(attr)).removeAttr(attr);
|
$this.attr('src', $this.attr(attr))
|
||||||
|
.removeAttr('data-loaded');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -775,18 +796,12 @@ class MessageModel extends AbstractModel
|
||||||
const $this = $(this); // eslint-disable-line no-invalid-this
|
const $this = $(this); // eslint-disable-line no-invalid-this
|
||||||
let style = trim($this.attr('style'));
|
let style = trim($this.attr('style'));
|
||||||
style = '' === style ? '' : (';' === style.substr(-1) ? style + ' ' : style + '; ');
|
style = '' === style ? '' : (';' === style.substr(-1) ? style + ' ' : style + '; ');
|
||||||
$this.attr('style', style + $this.attr(attr)).removeAttr(attr);
|
$this.attr('style', style + $this.attr(attr));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (lazy)
|
if (lazy)
|
||||||
{
|
{
|
||||||
$('img.lazy', this.body).addClass('lazy-inited').lazyload({
|
this.lozad();
|
||||||
'threshold': 400,
|
|
||||||
'effect': 'fadeIn',
|
|
||||||
'skip_invisible': false,
|
|
||||||
'container': $('.RL-MailMessageView .messageView .messageItem .content')[0]
|
|
||||||
});
|
|
||||||
|
|
||||||
$win.resize();
|
$win.resize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -867,16 +882,8 @@ class MessageModel extends AbstractModel
|
||||||
|
|
||||||
if (lazy)
|
if (lazy)
|
||||||
{
|
{
|
||||||
(function($oImg, oContainer) {
|
// $('.RL-MailMessageView .messageView .messageItem .content')[0]
|
||||||
_.delay(() => {
|
_.delay(() => this.lozad(), 300);
|
||||||
$oImg.addClass('lazy-inited').lazyload({
|
|
||||||
'threshold': 400,
|
|
||||||
'effect': 'fadeIn',
|
|
||||||
'skip_invisible': false,
|
|
||||||
'container': oContainer
|
|
||||||
});
|
|
||||||
}, 300);
|
|
||||||
}($('img.lazy', self.body), $('.RL-MailMessageView .messageView .messageItem .content')[0]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
windowResize(500);
|
windowResize(500);
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ class ContactsAdminSettings
|
||||||
constructor() {
|
constructor() {
|
||||||
this.defautOptionsAfterRender = defautOptionsAfterRender;
|
this.defautOptionsAfterRender = defautOptionsAfterRender;
|
||||||
this.enableContacts = ko.observable(!!settingsGet('ContactsEnable'));
|
this.enableContacts = ko.observable(!!settingsGet('ContactsEnable'));
|
||||||
this.contactsSharing = ko.observable(!!settingsGet('ContactsSharing'));
|
|
||||||
this.contactsSync = ko.observable(!!settingsGet('ContactsSync'));
|
this.contactsSync = ko.observable(!!settingsGet('ContactsSync'));
|
||||||
|
|
||||||
const
|
const
|
||||||
|
|
@ -177,12 +176,6 @@ class ContactsAdminSettings
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.contactsSharing.subscribe((value) => {
|
|
||||||
Remote.saveAdminConfig(null, {
|
|
||||||
'ContactsSharing': boolToAjax(value)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.contactsSync.subscribe((value) => {
|
this.contactsSync.subscribe((value) => {
|
||||||
Remote.saveAdminConfig(null, {
|
Remote.saveAdminConfig(null, {
|
||||||
'ContactsSync': boolToAjax(value)
|
'ContactsSync': boolToAjax(value)
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ class GeneralAdminSettings
|
||||||
this.weakPassword = AppAdminStore.weakPassword;
|
this.weakPassword = AppAdminStore.weakPassword;
|
||||||
this.newMoveToFolder = AppAdminStore.newMoveToFolder;
|
this.newMoveToFolder = AppAdminStore.newMoveToFolder;
|
||||||
|
|
||||||
|
this.dataFolderAccess = AppAdminStore.dataFolderAccess;
|
||||||
|
|
||||||
this.mainAttachmentLimit = ko.observable(pInt(settingsGet('AttachmentLimit')) / (Magics.BitLength1024 * Magics.BitLength1024)).extend({posInterer: 25});
|
this.mainAttachmentLimit = ko.observable(pInt(settingsGet('AttachmentLimit')) / (Magics.BitLength1024 * Magics.BitLength1024)).extend({posInterer: 25});
|
||||||
|
|
||||||
this.uploadData = settingsGet('PhpUploadSizes');
|
this.uploadData = settingsGet('PhpUploadSizes');
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,13 @@ const TIME_KEY = '__rlT';
|
||||||
*/
|
*/
|
||||||
export function isStorageSupported(storageName)
|
export function isStorageSupported(storageName)
|
||||||
{
|
{
|
||||||
if (storageName in window && window[storageName] && window[storageName].setItem)
|
let storageIsAvailable = false;
|
||||||
|
try {
|
||||||
|
storageIsAvailable = storageName in window && window[storageName] && window[storageName].setItem;
|
||||||
|
}
|
||||||
|
catch(e) {} // at: window[storageName] firefox throws SecurityError: The operation is insecure. when in iframe
|
||||||
|
|
||||||
|
if (storageIsAvailable)
|
||||||
{
|
{
|
||||||
const
|
const
|
||||||
s = window[storageName],
|
s = window[storageName],
|
||||||
|
|
@ -28,6 +34,7 @@ export function isStorageSupported(storageName)
|
||||||
catch (e) {} // eslint-disable-line no-empty
|
catch (e) {} // eslint-disable-line no-empty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
|
||||||
|
import window from 'window';
|
||||||
|
import $ from '$';
|
||||||
import ko from 'ko';
|
import ko from 'ko';
|
||||||
import {settingsGet} from 'Storage/Settings';
|
import {settingsGet} from 'Storage/Settings';
|
||||||
import {AbstractAppStore} from 'Stores/AbstractApp';
|
import {AbstractAppStore} from 'Stores/AbstractApp';
|
||||||
|
|
@ -13,6 +15,8 @@ class AppAdminStore extends AbstractAppStore
|
||||||
|
|
||||||
this.weakPassword = ko.observable(false);
|
this.weakPassword = ko.observable(false);
|
||||||
this.useLocalProxyForExternalImages = ko.observable(false);
|
this.useLocalProxyForExternalImages = ko.observable(false);
|
||||||
|
|
||||||
|
this.dataFolderAccess = ko.observable(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
populate() {
|
populate() {
|
||||||
|
|
@ -23,6 +27,10 @@ class AppAdminStore extends AbstractAppStore
|
||||||
|
|
||||||
this.weakPassword(!!settingsGet('WeakPassword'));
|
this.weakPassword(!!settingsGet('WeakPassword'));
|
||||||
this.useLocalProxyForExternalImages(!!settingsGet('UseLocalProxyForExternalImages'));
|
this.useLocalProxyForExternalImages(!!settingsGet('UseLocalProxyForExternalImages'));
|
||||||
|
|
||||||
|
if (settingsGet('Auth')) {
|
||||||
|
$.get('./data/VERSION?' + window.Math.random()).then(() => this.dataFolderAccess(true));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
import ko from 'ko';
|
import ko from 'ko';
|
||||||
import _ from '_';
|
import _ from '_';
|
||||||
|
|
||||||
|
import {settingsGet} from 'Storage/Settings';
|
||||||
|
|
||||||
import {FolderType} from 'Common/Enums';
|
import {FolderType} from 'Common/Enums';
|
||||||
import {UNUSED_OPTION_VALUE} from 'Common/Consts';
|
import {UNUSED_OPTION_VALUE} from 'Common/Consts';
|
||||||
import {isArray, folderListOptionsBuilder} from 'Common/Utils';
|
import {isArray, folderListOptionsBuilder} from 'Common/Utils';
|
||||||
|
|
@ -35,6 +37,8 @@ class FolderUserStore
|
||||||
|
|
||||||
this.currentFolder = ko.observable(null).extend({toggleSubscribeProperty: [this, 'selected']});
|
this.currentFolder = ko.observable(null).extend({toggleSubscribeProperty: [this, 'selected']});
|
||||||
|
|
||||||
|
this.sieveAllowFileintoInbox = !!settingsGet('SieveAllowFileintoInbox');
|
||||||
|
|
||||||
this.computers();
|
this.computers();
|
||||||
this.subscribers();
|
this.subscribers();
|
||||||
}
|
}
|
||||||
|
|
@ -117,7 +121,7 @@ class FolderUserStore
|
||||||
this.folderMenuForFilters = ko.computed(
|
this.folderMenuForFilters = ko.computed(
|
||||||
() => folderListOptionsBuilder(
|
() => folderListOptionsBuilder(
|
||||||
this.folderListSystem(), this.folderList(),
|
this.folderListSystem(), this.folderList(),
|
||||||
['INBOX'], [['', '']], null, null, null, (item) => (item ? item.localName() : ''))
|
[(this.sieveAllowFileintoInbox ? '' : 'INBOX')], [['', '']], null, null, null, (item) => (item ? item.localName() : ''))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -276,11 +276,11 @@ html.rl-no-preview-pane {
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
opacity: 0.3;
|
.opacity(50);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.opacity(80);
|
.opacity(80);
|
||||||
border-color: #000;
|
border-color: #666;
|
||||||
background-color: #888;
|
background-color: #888;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
@ -637,17 +637,40 @@ html.rl-message-fullscreen {
|
||||||
z-index: 10000 !important;
|
z-index: 10000 !important;
|
||||||
border: @rlLowBorderSize solid @rlMainDarkColor !important;
|
border: @rlLowBorderSize solid @rlMainDarkColor !important;
|
||||||
border-radius: @rlLowBorderRadius !important;
|
border-radius: @rlLowBorderRadius !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html .messageItem {
|
||||||
|
.buttonUp, .buttonUp {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
&.scroller-shadow-top .buttonUp {
|
||||||
|
display: inline-block !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rl-desktop .messageItem {
|
||||||
|
.buttonUp, .buttonFull {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
&.scroller-shadow-top .buttonUp, .buttonFull {
|
||||||
|
display: inline-block !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rl-message-fullscreen .messageItem {
|
||||||
.buttonUnFull {
|
.buttonUnFull {
|
||||||
display: inline-block !important;
|
display: inline-block !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttonFull {
|
.buttonFull {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
.buttonUp {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
&.scroller-shadow-top .buttonUp {
|
||||||
|
display: inline-block !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nano.scroller-shadow-top .buttonUp {
|
|
||||||
display: inline-block !important;
|
|
||||||
}
|
|
||||||
|
|
@ -38,7 +38,7 @@ class AddOpenPgpKeyPopupView extends AbstractViewNext
|
||||||
|
|
||||||
let keyTrimmed = trim(this.key());
|
let keyTrimmed = trim(this.key());
|
||||||
|
|
||||||
if (/[\n]/.test(keyTrimmed))
|
if ((/[\n]/).test(keyTrimmed))
|
||||||
{
|
{
|
||||||
keyTrimmed = keyTrimmed.replace(/[\r]+/g, '').replace(/[\n]{2,}/g, '\n\n');
|
keyTrimmed = keyTrimmed.replace(/[\r]+/g, '').replace(/[\n]{2,}/g, '\n\n');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,8 @@ class ComposePopupView extends AbstractViewNext
|
||||||
this.replyTo = ko.observable('');
|
this.replyTo = ko.observable('');
|
||||||
this.replyTo.focused = ko.observable(false);
|
this.replyTo.focused = ko.observable(false);
|
||||||
|
|
||||||
|
// this.to.subscribe((v) => console.log(v));
|
||||||
|
|
||||||
ko.computed(() => {
|
ko.computed(() => {
|
||||||
switch (true)
|
switch (true)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -409,7 +409,7 @@ class ComposeOpenPgpPopupView extends AbstractViewNext
|
||||||
rec = rec.join(', ').split(',');
|
rec = rec.join(', ').split(',');
|
||||||
rec = _.compact(_.map(rec, (value) => {
|
rec = _.compact(_.map(rec, (value) => {
|
||||||
email.clear();
|
email.clear();
|
||||||
email.mailsoParse(trim(value));
|
email.parse(trim(value));
|
||||||
return '' === email.email ? false : email.email;
|
return '' === email.email ? false : email.email;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -327,7 +327,7 @@ class ContactsPopupView extends AbstractViewNext
|
||||||
properties = [];
|
properties = [];
|
||||||
|
|
||||||
_.each(this.viewProperties(), (oItem) => {
|
_.each(this.viewProperties(), (oItem) => {
|
||||||
if (oItem.type() && '' !== trim(oItem.value()))
|
if (oItem.type() && oItem.type() !== ContactPropertyType.FullName && '' !== trim(oItem.value()))
|
||||||
{
|
{
|
||||||
properties.push([oItem.type(), oItem.value(), oItem.typeStr()]);
|
properties.push([oItem.type(), oItem.value(), oItem.typeStr()]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -465,7 +465,7 @@ class MessageListMailBoxUserView extends AbstractViewNext
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (/is:unseen/.test(this.mainMessageListSearch()))
|
if ((/is:unseen/).test(this.mainMessageListSearch()))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import {
|
||||||
import {
|
import {
|
||||||
inArray, isArray, isNonEmptyArray, trim, noop,
|
inArray, isArray, isNonEmptyArray, trim, noop,
|
||||||
windowResize, windowResizeCallback, inFocus,
|
windowResize, windowResizeCallback, inFocus,
|
||||||
removeSelection, removeInFocus, mailToHelper
|
removeSelection, removeInFocus, mailToHelper, isTransparent
|
||||||
} from 'Common/Utils';
|
} from 'Common/Utils';
|
||||||
|
|
||||||
import Audio from 'Common/Audio';
|
import Audio from 'Common/Audio';
|
||||||
|
|
@ -132,6 +132,10 @@ class MessageViewMailBoxUserView extends AbstractViewNext
|
||||||
|
|
||||||
this.showAttachmnetControls = ko.observable(false);
|
this.showAttachmnetControls = ko.observable(false);
|
||||||
|
|
||||||
|
this.showAttachmnetControlsState = (v) => {
|
||||||
|
Local.set(ClientSideKeyName.MessageAttachmnetControls, !!v);
|
||||||
|
};
|
||||||
|
|
||||||
this.allowAttachmnetControls = ko.computed(
|
this.allowAttachmnetControls = ko.computed(
|
||||||
() => 0 < this.attachmentsActions().length && Settings.capa(Capa.AttachmentsActions)
|
() => 0 < this.attachmentsActions().length && Settings.capa(Capa.AttachmentsActions)
|
||||||
);
|
);
|
||||||
|
|
@ -320,6 +324,12 @@ class MessageViewMailBoxUserView extends AbstractViewNext
|
||||||
if (message)
|
if (message)
|
||||||
{
|
{
|
||||||
this.showAttachmnetControls(false);
|
this.showAttachmnetControls(false);
|
||||||
|
if (Local.get(ClientSideKeyName.MessageAttachmnetControls))
|
||||||
|
{
|
||||||
|
_.delay(() => {
|
||||||
|
this.showAttachmnetControls(true);
|
||||||
|
}, Magics.Time50ms);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.viewHash !== message.hash)
|
if (this.viewHash !== message.hash)
|
||||||
{
|
{
|
||||||
|
|
@ -448,7 +458,7 @@ class MessageViewMailBoxUserView extends AbstractViewNext
|
||||||
color = inputDom.css('background-color') || '';
|
color = inputDom.css('background-color') || '';
|
||||||
if (!inputDom.is('table'))
|
if (!inputDom.is('table'))
|
||||||
{
|
{
|
||||||
color = 'rgba(0, 0, 0, 0)' === color || 'transparent' === color ? '' : color;
|
color = isTransparent(color) ? '' : color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -477,7 +487,7 @@ class MessageViewMailBoxUserView extends AbstractViewNext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result = 'rgba(0, 0, 0, 0)' === result || 'transparent' === result ? '' : result;
|
result = isTransparent(result) ? '' : result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -529,7 +539,7 @@ class MessageViewMailBoxUserView extends AbstractViewNext
|
||||||
// fParseEmailLine = function(sLine) {
|
// fParseEmailLine = function(sLine) {
|
||||||
// return sLine ? _.compact(_.map([window.decodeURIComponent(sLine)], function(sItem) {
|
// return sLine ? _.compact(_.map([window.decodeURIComponent(sLine)], function(sItem) {
|
||||||
// var oEmailModel = new EmailModel();
|
// var oEmailModel = new EmailModel();
|
||||||
// oEmailModel.mailsoParse(sItem);
|
// oEmailModel.parse(sItem);
|
||||||
// return '' !== oEmailModel.email ? oEmailModel : null;
|
// return '' !== oEmailModel.email ? oEmailModel : null;
|
||||||
// })) : null;
|
// })) : null;
|
||||||
// }
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
|
|
||||||
import window from 'window';
|
import window from 'window';
|
||||||
|
import elementDatasetPolyfill from 'element-dataset';
|
||||||
|
|
||||||
|
import 'es6-object-assign/auto';
|
||||||
|
|
||||||
import {Promise} from 'es6-promise-polyfill/promise.js';
|
import {Promise} from 'es6-promise-polyfill/promise.js';
|
||||||
import {progressJs} from '../node_modules/Progress.js/src/progress.js';
|
import {progressJs} from '../vendors/Progress.js/src/progress.js';
|
||||||
|
|
||||||
window.Promise = window.Promise || Promise;
|
window.Promise = window.Promise || Promise;
|
||||||
window.progressJs = window.progressJs || progressJs();
|
window.progressJs = window.progressJs || progressJs();
|
||||||
|
|
@ -21,7 +24,10 @@ window.progressJs.onbeforeend(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
elementDatasetPolyfill();
|
||||||
|
|
||||||
require('json3');
|
require('json3');
|
||||||
|
require('intersection-observer');
|
||||||
require('../vendors/modernizr/modernizr-custom.js');
|
require('../vendors/modernizr/modernizr-custom.js');
|
||||||
require('Common/Booter');
|
require('Common/Booter');
|
||||||
|
|
||||||
|
|
|
||||||
100
docker-compose.yml
Normal file
100
docker-compose.yml
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
version: '2'
|
||||||
|
services:
|
||||||
|
mail:
|
||||||
|
image: tvial/docker-mailserver:latest
|
||||||
|
hostname: mail
|
||||||
|
container_name: rl.mail
|
||||||
|
domainname: domain.com
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 25:25
|
||||||
|
- 143:143
|
||||||
|
volumes:
|
||||||
|
- maildata:/var/mail
|
||||||
|
- mailstate:/var/mail-state
|
||||||
|
- ./.docker/mail/config/:/tmp/docker-mailserver/
|
||||||
|
environment:
|
||||||
|
- ENABLE_SPAMASSASSIN=0
|
||||||
|
- ENABLE_CLAMAV=0
|
||||||
|
- ENABLE_FAIL2BAN=0
|
||||||
|
- ENABLE_POSTGREY=0
|
||||||
|
- ENABLE_MANAGESIEVE=1
|
||||||
|
- ONE_DIR=1
|
||||||
|
- DMS_DEBUG=0
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
- SYS_PTRACE
|
||||||
|
db:
|
||||||
|
image: mysql:5.7
|
||||||
|
hostname: db
|
||||||
|
container_name: rl.db
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: root
|
||||||
|
MYSQL_USER: rainloop
|
||||||
|
MYSQL_PASSWORD: rainloop
|
||||||
|
MYSQL_DATABASE: rainloop
|
||||||
|
volumes:
|
||||||
|
- mysql:/var/lib/mysql
|
||||||
|
- tmp:/tmp
|
||||||
|
php:
|
||||||
|
build:
|
||||||
|
context: ./.docker/php
|
||||||
|
hostname: php
|
||||||
|
container_name: rl.php
|
||||||
|
expose:
|
||||||
|
- 9000
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
- mail
|
||||||
|
volumes:
|
||||||
|
- ./:/var/www
|
||||||
|
# - ./build/local/:/var/www
|
||||||
|
- ./.docker/php/rainloop.ini:/usr/local/etc/php/conf.d/rainloop.ini
|
||||||
|
- tmp:/tmp
|
||||||
|
node:
|
||||||
|
build:
|
||||||
|
context: ./.docker/node
|
||||||
|
hostname: node
|
||||||
|
container_name: rl.node
|
||||||
|
working_dir: /var/www
|
||||||
|
command: sh -c 'yarn --version'
|
||||||
|
volumes:
|
||||||
|
- ./:/var/www
|
||||||
|
- tmp:/tmp
|
||||||
|
nginx:
|
||||||
|
image: nginx:latest
|
||||||
|
hostname: nginx
|
||||||
|
container_name: rl.nginx
|
||||||
|
depends_on:
|
||||||
|
- php
|
||||||
|
ports:
|
||||||
|
- 443:443
|
||||||
|
- 80:80
|
||||||
|
volumes:
|
||||||
|
- ./:/var/www
|
||||||
|
# - ./build/local/:/var/www
|
||||||
|
- ./.docker/nginx/ssl:/etc/nginx/ssl
|
||||||
|
- ./.docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
- tmp:/tmp
|
||||||
|
tx:
|
||||||
|
build:
|
||||||
|
context: ./.docker/tx
|
||||||
|
hostname: tx
|
||||||
|
container_name: rl.tx
|
||||||
|
working_dir: /var/www
|
||||||
|
command: sh -c 'tx --version'
|
||||||
|
volumes:
|
||||||
|
- ./:/var/www
|
||||||
|
- ./.docker/.cache/tx/root:/root
|
||||||
|
- tmp:/tmp
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql:
|
||||||
|
driver: local
|
||||||
|
maildata:
|
||||||
|
driver: local
|
||||||
|
mailstate:
|
||||||
|
driver: local
|
||||||
|
tmp:
|
||||||
|
driver: local
|
||||||
93
gulpfile.js
93
gulpfile.js
|
|
@ -1,4 +1,5 @@
|
||||||
/* RainLoop Webmail (c) RainLoop Team | Licensed under AGPL 3 */
|
/* RainLoop Webmail (c) RainLoop Team | Licensed under AGPL 3 */
|
||||||
|
/* eslint-disable */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var
|
var
|
||||||
|
|
@ -130,37 +131,6 @@ function copyFile(sFile, sNewFile, callback)
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
function signFile(sFile, callback)
|
|
||||||
{
|
|
||||||
var exec = require('child_process').exec;
|
|
||||||
exec('gpg2 --openpgp -u 87DA4591 -a -b ' + sFile, function(err) {
|
|
||||||
if (err) {
|
|
||||||
gutil.log('gpg error: skip');
|
|
||||||
}
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function signFileTask(callback) {
|
|
||||||
if (argv.sign)
|
|
||||||
{
|
|
||||||
signFile(cfg.destPath + cfg.zipFile, function() {
|
|
||||||
if (cfg.zipFileShort)
|
|
||||||
{
|
|
||||||
signFile(cfg.destPath + cfg.zipFileShort, callback);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
cfg.paths.globjs = 'dev/**/*.js';
|
cfg.paths.globjs = 'dev/**/*.js';
|
||||||
cfg.paths.static = 'rainloop/v/' + cfg.devVersion + '/static/';
|
cfg.paths.static = 'rainloop/v/' + cfg.devVersion + '/static/';
|
||||||
cfg.paths.staticJS = 'rainloop/v/' + cfg.devVersion + '/static/js/';
|
cfg.paths.staticJS = 'rainloop/v/' + cfg.devVersion + '/static/js/';
|
||||||
|
|
@ -198,9 +168,9 @@ cfg.paths.css = {
|
||||||
'vendors/flags/flags-fixed.css',
|
'vendors/flags/flags-fixed.css',
|
||||||
'node_modules/opentip/css/opentip.css',
|
'node_modules/opentip/css/opentip.css',
|
||||||
'node_modules/pikaday/css/pikaday.css',
|
'node_modules/pikaday/css/pikaday.css',
|
||||||
'node_modules/lightgallery/dist/css/lightgallery.min.css',
|
'vendors/lightgallery/dist/css/lightgallery.min.css',
|
||||||
'node_modules/lightgallery/dist/css/lg-transitions.min.css',
|
'vendors/lightgallery/dist/css/lg-transitions.min.css',
|
||||||
'node_modules/Progress.js/minified/progressjs.min.css',
|
'vendors/Progress.js/minified/progressjs.min.css',
|
||||||
'dev/Styles/_progressjs.css'
|
'dev/Styles/_progressjs.css'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -223,10 +193,10 @@ cfg.paths.js = {
|
||||||
name: 'libs.js',
|
name: 'libs.js',
|
||||||
src: [
|
src: [
|
||||||
'node_modules/jquery/dist/jquery.min.js',
|
'node_modules/jquery/dist/jquery.min.js',
|
||||||
|
'node_modules/jquery-migrate/dist/jquery-migrate.min.js',
|
||||||
'node_modules/jquery-mousewheel/jquery.mousewheel.js',
|
'node_modules/jquery-mousewheel/jquery.mousewheel.js',
|
||||||
'node_modules/jquery-scrollstop/jquery.scrollstop.js',
|
'node_modules/jquery-scrollstop/jquery.scrollstop.js',
|
||||||
'node_modules/jquery-lazyload/jquery.lazyload.js ',
|
'node_modules/jquery-backstretch/jquery.backstretch.min.js',
|
||||||
'node_modules/jquery.backstretch/jquery.backstretch.min.js',
|
|
||||||
'vendors/jquery-ui/js/jquery-ui-1.10.3.custom.min.js', // custom
|
'vendors/jquery-ui/js/jquery-ui-1.10.3.custom.min.js', // custom
|
||||||
'vendors/jquery-nanoscroller/jquery.nanoscroller.js', // custom (modified)
|
'vendors/jquery-nanoscroller/jquery.nanoscroller.js', // custom (modified)
|
||||||
'vendors/jquery-wakeup/jquery.wakeup.js', // no-npm
|
'vendors/jquery-wakeup/jquery.wakeup.js', // no-npm
|
||||||
|
|
@ -242,7 +212,7 @@ cfg.paths.js = {
|
||||||
'node_modules/underscore/underscore-min.js',
|
'node_modules/underscore/underscore-min.js',
|
||||||
'node_modules/moment/min/moment.min.js',
|
'node_modules/moment/min/moment.min.js',
|
||||||
'node_modules/knockout/build/output/knockout-latest.js',
|
'node_modules/knockout/build/output/knockout-latest.js',
|
||||||
'node_modules/knockout-projections/dist/knockout-projections.min.js',
|
'node_modules/knockout-transformations/dist/knockout-transformations.min.js',
|
||||||
'node_modules/knockout-sortable/build/knockout-sortable.min.js ',
|
'node_modules/knockout-sortable/build/knockout-sortable.min.js ',
|
||||||
'node_modules/matchmedia-polyfill/matchMedia.js',
|
'node_modules/matchmedia-polyfill/matchMedia.js',
|
||||||
'node_modules/matchmedia-polyfill/matchMedia.addListener.js',
|
'node_modules/matchmedia-polyfill/matchMedia.addListener.js',
|
||||||
|
|
@ -250,11 +220,11 @@ cfg.paths.js = {
|
||||||
'node_modules/autolinker/dist/Autolinker.min.js',
|
'node_modules/autolinker/dist/Autolinker.min.js',
|
||||||
'node_modules/opentip/lib/opentip.js',
|
'node_modules/opentip/lib/opentip.js',
|
||||||
'node_modules/opentip/lib/adapter-jquery.js',
|
'node_modules/opentip/lib/adapter-jquery.js',
|
||||||
'node_modules/lightgallery/dist/js/lightgallery.min.js',
|
'vendors/lightgallery/dist/js/lightgallery.min.js',
|
||||||
'node_modules/lightgallery/dist/js/lg-fullscreen.min.js',
|
'vendors/lightgallery/dist/js/lg-fullscreen.min.js',
|
||||||
'node_modules/lightgallery/dist/js/lg-thumbnail.min.js',
|
'vendors/lightgallery/dist/js/lg-thumbnail.min.js',
|
||||||
'node_modules/lightgallery/dist/js/lg-zoom.min.js',
|
'vendors/lightgallery/dist/js/lg-zoom.min.js',
|
||||||
'node_modules/lightgallery/dist/js/lg-autoplay.min.js',
|
'vendors/lightgallery/dist/js/lg-autoplay.min.js',
|
||||||
'node_modules/ifvisible.js/src/ifvisible.min.js'
|
'node_modules/ifvisible.js/src/ifvisible.min.js'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -267,7 +237,7 @@ cfg.paths.js = {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// assers
|
// assets
|
||||||
|
|
||||||
gulp.task('assets:clean', function() {
|
gulp.task('assets:clean', function() {
|
||||||
return cleanDir(cfg.paths.static);
|
return cleanDir(cfg.paths.static);
|
||||||
|
|
@ -427,7 +397,7 @@ gulp.task('fontastic-fonts:clear', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('lightgallery-fonts:copy', ['lightgallery-fonts:clear'], function() {
|
gulp.task('lightgallery-fonts:copy', ['lightgallery-fonts:clear'], function() {
|
||||||
return gulp.src('node_modules/lightgallery/dist/fonts/lg.*')
|
return gulp.src('vendors/lightgallery/dist/fonts/lg.*')
|
||||||
.pipe(gulp.dest('rainloop/v/' + cfg.devVersion + '/static/css/fonts'));
|
.pipe(gulp.dest('rainloop/v/' + cfg.devVersion + '/static/css/fonts'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -521,8 +491,6 @@ gulp.task('rainloop:shortname', ['rainloop:zip'], function(callback) {
|
||||||
copyFile(cfg.destPath + cfg.zipFile, cfg.destPath + cfg.zipFileShort, callback);
|
copyFile(cfg.destPath + cfg.zipFile, cfg.destPath + cfg.zipFileShort, callback);
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('rainloop:sign', ['rainloop:shortname'], signFileTask);
|
|
||||||
|
|
||||||
// build (OwnCloud)
|
// build (OwnCloud)
|
||||||
gulp.task('rainloop:owncloud:copy', function() {
|
gulp.task('rainloop:owncloud:copy', function() {
|
||||||
|
|
||||||
|
|
@ -594,7 +562,27 @@ gulp.task('rainloop:owncloud:shortname', ['rainloop:owncloud:zip'], function(cal
|
||||||
copyFile(cfg.destPath + cfg.zipFile, cfg.destPath + cfg.zipFileShort, callback);
|
copyFile(cfg.destPath + cfg.zipFile, cfg.destPath + cfg.zipFileShort, callback);
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('rainloop:owncloud:sign', ['rainloop:owncloud:shortname'], signFileTask);
|
gulp.task('plugins:build', [], function() {
|
||||||
|
|
||||||
|
var name = argv.name || '';
|
||||||
|
if (true === name || !name) {
|
||||||
|
throw new Error('Empty name parameter');
|
||||||
|
}
|
||||||
|
|
||||||
|
var
|
||||||
|
source = 'plugins/' + name,
|
||||||
|
vesrion = fs.readFileSync(source + '/VERSION', 'utf8')
|
||||||
|
;
|
||||||
|
|
||||||
|
cfg.destPath = 'build/dist/plugins/';
|
||||||
|
cfg.zipFile = name + '-' + vesrion + '.zip';
|
||||||
|
|
||||||
|
fs.mkdirSync(cfg.destPath, '0777', true);
|
||||||
|
|
||||||
|
return gulp.src(source + '/**/*', {base: source})
|
||||||
|
.pipe(require('gulp-zip')(cfg.zipFile))
|
||||||
|
.pipe(gulp.dest(cfg.destPath));
|
||||||
|
});
|
||||||
|
|
||||||
// main
|
// main
|
||||||
gulp.task('moment', ['moment:locales']);
|
gulp.task('moment', ['moment:locales']);
|
||||||
|
|
@ -607,11 +595,11 @@ gulp.task('clean', ['js:clean', 'css:clean', 'assets:clean']);
|
||||||
|
|
||||||
gulp.task('rainloop:start', ['rainloop:copy', 'rainloop:setup']);
|
gulp.task('rainloop:start', ['rainloop:copy', 'rainloop:setup']);
|
||||||
|
|
||||||
gulp.task('rainloop', ['rainloop:start', 'rainloop:zip', 'rainloop:clean', 'rainloop:shortname', 'rainloop:sign']);
|
gulp.task('rainloop', ['rainloop:start', 'rainloop:zip', 'rainloop:clean', 'rainloop:shortname']);
|
||||||
|
|
||||||
gulp.task('owncloud', ['rainloop:owncloud:copy',
|
gulp.task('owncloud', ['rainloop:owncloud:copy',
|
||||||
'rainloop:owncloud:copy-rainloop', 'rainloop:owncloud:copy-rainloop:clean',
|
'rainloop:owncloud:copy-rainloop', 'rainloop:owncloud:copy-rainloop:clean',
|
||||||
'rainloop:owncloud:setup', 'rainloop:owncloud:zip', 'rainloop:owncloud:clean', 'rainloop:owncloud:shortname', 'rainloop:owncloud:sign']);
|
'rainloop:owncloud:setup', 'rainloop:owncloud:zip', 'rainloop:owncloud:clean', 'rainloop:owncloud:shortname']);
|
||||||
|
|
||||||
// default
|
// default
|
||||||
gulp.task('default', function(callback) {
|
gulp.task('default', function(callback) {
|
||||||
|
|
@ -627,8 +615,13 @@ gulp.task('watch', ['css:main', 'js:validate'], function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// aliases
|
// aliases
|
||||||
|
gulp.task('lint', ['js:eslint']);
|
||||||
gulp.task('build', ['rainloop']);
|
gulp.task('build', ['rainloop']);
|
||||||
|
|
||||||
|
gulp.task('all', function(callback) {
|
||||||
|
runSequence('rainloop', 'owncloud', callback);
|
||||||
|
});
|
||||||
|
|
||||||
gulp.task('d', ['default']);
|
gulp.task('d', ['default']);
|
||||||
gulp.task('w', ['watch']);
|
gulp.task('w', ['watch']);
|
||||||
gulp.task('l', ['js:libs']);
|
gulp.task('l', ['js:libs']);
|
||||||
|
|
@ -636,3 +629,5 @@ gulp.task('v', ['js:validate']);
|
||||||
|
|
||||||
gulp.task('b', ['build']);
|
gulp.task('b', ['build']);
|
||||||
gulp.task('o', ['owncloud']);
|
gulp.task('o', ['owncloud']);
|
||||||
|
|
||||||
|
gulp.task('p', ['plugins:build']);
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,15 @@
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2015",
|
"target": "es2015",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"experimentalDecorators": true
|
"experimentalDecorators": true,
|
||||||
|
"baseUrl": "./dev",
|
||||||
|
"paths": {
|
||||||
|
"*": [
|
||||||
|
"*",
|
||||||
|
]
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"build",
|
"build",
|
||||||
|
|
|
||||||
135
package.json
135
package.json
|
|
@ -3,19 +3,22 @@
|
||||||
"title": "RainLoop Webmail",
|
"title": "RainLoop Webmail",
|
||||||
"description": "Simple, modern & fast web-based email client",
|
"description": "Simple, modern & fast web-based email client",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.11.1",
|
"version": "1.12.1",
|
||||||
"ownCloudVersion": "5.0.0",
|
"ownCloudVersion": "5.1.1",
|
||||||
"homepage": "http://rainloop.net",
|
"homepage": "https://www.rainloop.net",
|
||||||
"main": "gulpfile.js",
|
|
||||||
"author": {
|
"author": {
|
||||||
"name": "RainLoop Team",
|
"name": "RainLoop Team",
|
||||||
"email": "support@rainloop.net",
|
"email": "support@rainloop.net",
|
||||||
"web": "http://www.rainloop.net"
|
"web": "https://www.rainloop.net"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/RainLoop/rainloop-webmail.git"
|
"url": "git://github.com/RainLoop/rainloop-webmail.git"
|
||||||
},
|
},
|
||||||
|
"scripts": {
|
||||||
|
"watch-css": "gulp watch",
|
||||||
|
"watch-js": "webpack --color --watch"
|
||||||
|
},
|
||||||
"license": "SEE LICENSE IN LICENSE",
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -24,7 +27,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "RainLoop Software License",
|
"type": "RainLoop Software License",
|
||||||
"ulr": "http://www.rainloop.net/licensing/"
|
"ulr": "https://www.rainloop.net/licensing/"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
@ -33,94 +36,94 @@
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"webmail",
|
"webmail",
|
||||||
"php",
|
"php",
|
||||||
|
"javascript",
|
||||||
"simple",
|
"simple",
|
||||||
"modern",
|
"modern",
|
||||||
"mail",
|
"mail",
|
||||||
"web-based",
|
"web-based",
|
||||||
"email",
|
"email",
|
||||||
"client",
|
"client",
|
||||||
"openpgp",
|
"openpgp"
|
||||||
"plugins"
|
|
||||||
],
|
],
|
||||||
"readmeFilename": "README.md",
|
"readmeFilename": "README.md",
|
||||||
"engines": {
|
|
||||||
"node": ">= 4"
|
|
||||||
},
|
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"last 3 versions",
|
"last 3 versions",
|
||||||
"ie >= 9",
|
"IE >= 9",
|
||||||
"firefox esr"
|
"firefox esr"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"Progress.js": "github:usablica/progress.js#v0.1.0",
|
"@babel/core": "7.4.0",
|
||||||
"autolinker": "1.4.3",
|
"@babel/plugin-proposal-class-properties": "7.4.0",
|
||||||
"babel-core": "6.25.0",
|
"@babel/plugin-proposal-decorators": "7.4.0",
|
||||||
"babel-eslint": "7.2.3",
|
"@babel/plugin-transform-runtime": "7.4.0",
|
||||||
"babel-loader": "7.1.1",
|
"@babel/preset-env": "7.4.2",
|
||||||
"babel-plugin-transform-decorators-legacy": "1.3.4",
|
"@babel/runtime-corejs2": "7.4.2",
|
||||||
"babel-plugin-transform-runtime": "6.23.0",
|
"autolinker": "3.0.5",
|
||||||
"babel-preset-env": "1.6.0",
|
"babel-eslint": "10.0.1",
|
||||||
"babel-preset-stage-0": "6.24.1",
|
"babel-loader": "8.0.5",
|
||||||
"babel-runtime": "6.23.0",
|
"classnames": "2.2.6",
|
||||||
"classnames": "2.2.5",
|
"copy-webpack-plugin": "5.0.2",
|
||||||
"copy-webpack-plugin": "4.0.1",
|
"css-loader": "2.1.1",
|
||||||
|
"element-dataset": "2.2.6",
|
||||||
|
"emailjs-addressparser": "2.0.2",
|
||||||
|
"es6-object-assign": "1.1.0",
|
||||||
"es6-promise-polyfill": "1.2.0",
|
"es6-promise-polyfill": "1.2.0",
|
||||||
"eslint": "4.1.1",
|
"eslint": "5.15.3",
|
||||||
"eslint-plugin-compat": "1.0.4",
|
|
||||||
"gulp": "3.9.1",
|
"gulp": "3.9.1",
|
||||||
"gulp-autoprefixer": "4.0.0",
|
"gulp-autoprefixer": "6.0.0",
|
||||||
"gulp-cached": "1.1.1",
|
"gulp-cached": "1.1.1",
|
||||||
"gulp-chmod": "2.0.0",
|
"gulp-chmod": "2.0.0",
|
||||||
"gulp-clean-css": "3.5.0",
|
"gulp-clean-css": "3.10.0",
|
||||||
"gulp-concat-util": "0.5.5",
|
"gulp-concat-util": "0.5.5",
|
||||||
"gulp-eol": "0.1.2",
|
"gulp-eol": "0.2.0",
|
||||||
"gulp-eslint": "4.0.0",
|
"gulp-eslint": "5.0.0",
|
||||||
"gulp-expect-file": "0.0.7",
|
"gulp-expect-file": "1.0.1",
|
||||||
"gulp-filter": "5.0.0",
|
"gulp-filter": "5.1.0",
|
||||||
"gulp-header": "1.8.8",
|
"gulp-header": "2.0.7",
|
||||||
"gulp-if": "2.0.2",
|
"gulp-if": "2.0.2",
|
||||||
"gulp-less": "3.3.2",
|
"gulp-less": "4.0.1",
|
||||||
"gulp-livereload": "3.8.1",
|
"gulp-livereload": "4.0.1",
|
||||||
"gulp-notify": "3.0.0",
|
"gulp-notify": "3.2.0",
|
||||||
"gulp-plumber": "1.1.0",
|
"gulp-plumber": "1.2.1",
|
||||||
"gulp-rename": "1.2.2",
|
"gulp-rename": "1.4.0",
|
||||||
"gulp-replace": "0.6.1",
|
"gulp-replace": "1.0.0",
|
||||||
"gulp-rimraf": "0.2.1",
|
"gulp-rimraf": "0.2.2",
|
||||||
"gulp-size": "2.1.0",
|
"gulp-size": "3.0.0",
|
||||||
"gulp-stripbom": "1.0.4",
|
"gulp-stripbom": "1.0.4",
|
||||||
"gulp-through": "0.4.0",
|
"gulp-through": "0.4.0",
|
||||||
"gulp-uglify": "3.0.0",
|
"gulp-uglify": "3.0.2",
|
||||||
"gulp-util": "3.0.8",
|
"gulp-util": "3.0.8",
|
||||||
"gulp-zip": "4.0.0",
|
"gulp-zip": "4.2.0",
|
||||||
"ifvisible.js": "1.0.6",
|
"ifvisible.js": "1.0.6",
|
||||||
"jquery": "2.2.4",
|
"intersection-observer": "0.5.1",
|
||||||
"jquery-lazyload": "1.9.7",
|
"jquery": "3.3.1",
|
||||||
|
"jquery-backstretch": "2.1.17",
|
||||||
|
"jquery-migrate": "3.0.1",
|
||||||
"jquery-mousewheel": "3.1.13",
|
"jquery-mousewheel": "3.1.13",
|
||||||
"jquery-scrollstop": "1.2.0",
|
"jquery-scrollstop": "1.2.0",
|
||||||
"jquery.backstretch": "2.1.15",
|
"js-cookie": "2.2.0",
|
||||||
"js-cookie": "2.1.4",
|
"json-loader": "0.5.7",
|
||||||
"json-loader": "0.5.4",
|
|
||||||
"json3": "3.3.2",
|
"json3": "3.3.2",
|
||||||
"knockout": "3.4.2",
|
"knockout": "3.4.2",
|
||||||
"knockout-projections": "github:stevesanderson/knockout-projections#v1.1.0",
|
"knockout-sortable": "1.1.1",
|
||||||
"knockout-sortable": "0.14.1",
|
"knockout-transformations": "2.1.0",
|
||||||
"lightgallery": "1.2.21",
|
"lozad": "1.9.0",
|
||||||
"matchmedia-polyfill": "0.3.0",
|
"matchmedia-polyfill": "0.3.1",
|
||||||
"moment": "2.18.1",
|
"moment": "2.24.0",
|
||||||
"node-fs": "0.1.7",
|
"node-fs": "0.1.7",
|
||||||
"node-notifier": "5.1.2",
|
"node-notifier": "5.4.0",
|
||||||
"normalize.css": "7.0.0",
|
"normalize.css": "8.0.1",
|
||||||
"openpgp": "2.5.5",
|
"openpgp": "2.6.2",
|
||||||
"opentip": "2.4.3",
|
"opentip": "2.4.3",
|
||||||
"pikaday": "1.6.1",
|
"pikaday": "1.8.0",
|
||||||
"raw-loader": "0.5.1",
|
"raw-loader": "2.0.0",
|
||||||
"rifraf": "2.0.3",
|
"rimraf": "2.6.3",
|
||||||
"rimraf": "2.6.1",
|
"run-sequence": "2.2.1",
|
||||||
"run-sequence": "2.0.0",
|
"simplestatemanager": "4.1.1",
|
||||||
"simplestatemanager": "3.4.0",
|
"style-loader": "0.23.1",
|
||||||
"style-loader": "0.18.2",
|
"underscore": "1.9.1",
|
||||||
"underscore": "1.8.3",
|
"webpack": "4.29.6",
|
||||||
"webpack": "3.1.0",
|
"webpack-cli": "3.3.0",
|
||||||
"webpack-notifier": "1.5.0"
|
"webpack-notifier": "1.7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,10 @@
|
||||||
|
|
||||||
class AutoDomainGrabPlugin extends \RainLoop\Plugins\AbstractPlugin
|
class AutoDomainGrabPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
{
|
{
|
||||||
|
|
||||||
|
private $imap_prefix = "mail.";
|
||||||
|
private $smtp_prefix = "mail.";
|
||||||
|
|
||||||
public function Init()
|
public function Init()
|
||||||
{
|
{
|
||||||
$this->addHook('filter.smtp-credentials', 'FilterSmtpCredentials');
|
$this->addHook('filter.smtp-credentials', 'FilterSmtpCredentials');
|
||||||
|
|
@ -19,7 +23,7 @@ class AutoDomainGrabPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function detects the IMAP Host, and if it is set to "auto", replaces it with the email domain.
|
* This function detects the IMAP Host, and if it is set to "auto", replaces it with the MX or email domain.
|
||||||
*
|
*
|
||||||
* @param \RainLoop\Model\Account $oAccount
|
* @param \RainLoop\Model\Account $oAccount
|
||||||
* @param array $aImapCredentials
|
* @param array $aImapCredentials
|
||||||
|
|
@ -31,13 +35,22 @@ class AutoDomainGrabPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
// Check for mail.$DOMAIN as entered value in RL settings
|
// Check for mail.$DOMAIN as entered value in RL settings
|
||||||
if (!empty($aImapCredentials['Host']) && 'auto' === $aImapCredentials['Host'])
|
if (!empty($aImapCredentials['Host']) && 'auto' === $aImapCredentials['Host'])
|
||||||
{
|
{
|
||||||
$aImapCredentials['Host'] = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email());
|
$domain = substr(strrchr($oAccount->Email(), "@"), 1);
|
||||||
|
$mxhosts = array();
|
||||||
|
if(getmxrr($domain, $mxhosts) && sizeof($mxhosts) > 0)
|
||||||
|
{
|
||||||
|
$aImapCredentials['Host'] = $mxhosts[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$aImapCredentials['Host'] = $this->imap_prefix.$domain;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function detects the SMTP Host, and if it is set to "auto", replaces it with the email domain.
|
* This function detects the SMTP Host, and if it is set to "auto", replaces it with the MX or email domain.
|
||||||
*
|
*
|
||||||
* @param \RainLoop\Model\Account $oAccount
|
* @param \RainLoop\Model\Account $oAccount
|
||||||
* @param array $aSmtpCredentials
|
* @param array $aSmtpCredentials
|
||||||
|
|
@ -49,7 +62,16 @@ class AutoDomainGrabPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
// Check for mail.$DOMAIN as entered value in RL settings
|
// Check for mail.$DOMAIN as entered value in RL settings
|
||||||
if (!empty($aSmtpCredentials['Host']) && 'auto' === $aSmtpCredentials['Host'])
|
if (!empty($aSmtpCredentials['Host']) && 'auto' === $aSmtpCredentials['Host'])
|
||||||
{
|
{
|
||||||
$aSmtpCredentials['Host'] = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email());
|
$domain = substr(strrchr($oAccount->Email(), "@"), 1);
|
||||||
|
$mxhosts = array();
|
||||||
|
if(getmxrr($domain, $mxhosts) && sizeof($mxhosts) > 0)
|
||||||
|
{
|
||||||
|
$aSmtpCredentials['Host'] = $mxhosts[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$aSmtpCredentials['Host'] = $this->smtp_prefix.$domain;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ class ChangePasswordCustomSqlDriver implements \RainLoop\Providers\ChangePasswor
|
||||||
|
|
||||||
$dsn = 'mysql:host='.$this->mHost.';dbname='.$this->mDatabase.';charset=utf8';
|
$dsn = 'mysql:host='.$this->mHost.';dbname='.$this->mDatabase.';charset=utf8';
|
||||||
$options = array(
|
$options = array(
|
||||||
PDO::ATTR_EMULATE_PREPARES => false,
|
PDO::ATTR_EMULATE_PREPARES => true,
|
||||||
PDO::ATTR_PERSISTENT => true,
|
PDO::ATTR_PERSISTENT => true,
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
|
||||||
);
|
);
|
||||||
|
|
@ -160,15 +160,52 @@ class ChangePasswordCustomSqlDriver implements \RainLoop\Providers\ChangePasswor
|
||||||
$sEmailUser = \MailSo\Base\Utils::GetAccountNameFromEmail($sEmail);
|
$sEmailUser = \MailSo\Base\Utils::GetAccountNameFromEmail($sEmail);
|
||||||
$sEmailDomain = \MailSo\Base\Utils::GetDomainFromEmail($sEmail);
|
$sEmailDomain = \MailSo\Base\Utils::GetDomainFromEmail($sEmail);
|
||||||
|
|
||||||
//simple check
|
// some variables cannot be prepared
|
||||||
|
$this->mSql = str_replace(array(
|
||||||
|
':table'
|
||||||
|
), array(
|
||||||
|
$this->mTable
|
||||||
|
), $this->mSql);
|
||||||
|
|
||||||
$old = array(':email', ':oldpass', ':newpass', ':domain', ':username', ':table' );
|
$placeholders = array(
|
||||||
$new = array($sEmail, $sPrevPassword, $sNewPassword, $sEmailDomain, $sEmailUser, $this->mTable);
|
':email' => $sEmail,
|
||||||
|
':oldpass' => $sPrevPassword,
|
||||||
|
':newpass' => $sNewPassword,
|
||||||
|
':domain' => $sEmailDomain,
|
||||||
|
':username' => $sEmailUser
|
||||||
|
);
|
||||||
|
|
||||||
$this->mSql = str_replace($old, $new, $this->mSql);
|
// we have to check that all placehoders are used in the query, passing any unused placeholders will generate an error
|
||||||
|
$used_placeholders = array();
|
||||||
|
|
||||||
|
foreach($placeholders as $placeholder => $value) {
|
||||||
|
if(preg_match_all('/'.$placeholder . '(?![a-zA-Z0-9\-])'.'/', $this->mSql) === 1) {
|
||||||
|
// backwards-compabitibility: remove single and double quotes around placeholders
|
||||||
|
$this->mSql = str_replace('`'.$placeholder.'`', $placeholder, $this->mSql);
|
||||||
|
$this->mSql = str_replace("'".$placeholder."'", $placeholder, $this->mSql);
|
||||||
|
$this->mSql = str_replace('"'.$placeholder.'"', $placeholder, $this->mSql);
|
||||||
|
$used_placeholders[$placeholder] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$statement = $conn->prepare($this->mSql);
|
||||||
|
|
||||||
|
// everything is ready (hopefully), bind the values
|
||||||
|
foreach($used_placeholders as $placeholder => $value) {
|
||||||
|
$statement->bindValue($placeholder, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// and execute
|
||||||
|
$mSqlReturn = $statement->execute();
|
||||||
|
|
||||||
|
/* can be used for debugging
|
||||||
|
ob_start();
|
||||||
|
$statement->debugDumpParams();
|
||||||
|
$r = ob_get_contents();
|
||||||
|
ob_end_clean();
|
||||||
|
$this->oLogger->Write($r);
|
||||||
|
*/
|
||||||
|
|
||||||
$update = $conn->prepare($this->mSql);
|
|
||||||
$mSqlReturn = $update->execute(array());
|
|
||||||
if ($mSqlReturn == true)
|
if ($mSqlReturn == true)
|
||||||
{
|
{
|
||||||
$bResult = true;
|
$bResult = true;
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ class ChangePasswordCustomSqlPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
\RainLoop\Plugins\Property::NewInstance('mTable')->SetLabel('MySQL Table'),
|
\RainLoop\Plugins\Property::NewInstance('mTable')->SetLabel('MySQL Table'),
|
||||||
\RainLoop\Plugins\Property::NewInstance('mSql')->SetLabel('SQL statement')
|
\RainLoop\Plugins\Property::NewInstance('mSql')->SetLabel('SQL statement')
|
||||||
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
|
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
|
||||||
->SetDescription('SQL statement (allowed wildcards :table, :email, :oldpass, :newpass, :domain, :username). When using MD5 OR SHA1 at tables, write it directly here as SQL functions. Fro non-SQL encryptions use another plugin or wait for new version.')
|
->SetDescription('SQL statement (allowed wildcards :table, :email, :oldpass, :newpass, :domain, :username). Use SQL functions for encryption.')
|
||||||
->SetDefaultValue('UPDATE :table SET password = md5(:newpass) WHERE domain = :domain AND username = :username and oldpass = md5(:oldpass)')
|
->SetDefaultValue('UPDATE :table SET password = md5(:newpass) WHERE domain = :domain AND username = :username and oldpass = md5(:oldpass)')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
20
plugins/contact-group-excel-paste/LICENCE
Normal file
20
plugins/contact-group-excel-paste/LICENCE
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2017 https://github.com/korukugashi/
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
4
plugins/contact-group-excel-paste/README
Normal file
4
plugins/contact-group-excel-paste/README
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
Add ability to paste multi row email addresses (from Excel for instance) to a contact in a single row, in order to create a contact group.
|
||||||
|
|
||||||
|
Usage : open address book, paste your emails separated by line breaks in an email field. Emails will be collapsed with "," separator.
|
||||||
|
Then, when you write an email, select the email group and all addresses will be added.
|
||||||
1
plugins/contact-group-excel-paste/VERSION
Normal file
1
plugins/contact-group-excel-paste/VERSION
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
0.1
|
||||||
9
plugins/contact-group-excel-paste/index.php
Normal file
9
plugins/contact-group-excel-paste/index.php
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class ContactGroupExcelPastePlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
|
{
|
||||||
|
public function Init()
|
||||||
|
{
|
||||||
|
$this->addJs('js/excel_contact_group.js');
|
||||||
|
}
|
||||||
|
}
|
||||||
20
plugins/contact-group-excel-paste/js/excel_contact_group.js
Normal file
20
plugins/contact-group-excel-paste/js/excel_contact_group.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
(function(window, $) {
|
||||||
|
$(function() {
|
||||||
|
$(window.document).on('paste', '.RL-PopupsContacts .contactValueInput', function() {
|
||||||
|
// trigger the process on click and paste
|
||||||
|
var $this = $(this);
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
$this.trigger('keyup');
|
||||||
|
});
|
||||||
|
}).on('keyup', '.RL-PopupsContacts .contactValueInput', function() {
|
||||||
|
var $this = $(this),
|
||||||
|
value = $this.val(),
|
||||||
|
match = value && value.match(/@/ig);
|
||||||
|
|
||||||
|
if (match && match.length > 1) {
|
||||||
|
$this.val($this.val().replace(/\n| /ig, ','));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}(window, $));
|
||||||
293
plugins/froxlor-change-password/FroxlorChangePasswordDriver.php
Normal file
293
plugins/froxlor-change-password/FroxlorChangePasswordDriver.php
Normal file
|
|
@ -0,0 +1,293 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FroxlorChangePasswordDriver implements \RainLoop\Providers\ChangePassword\ChangePasswordInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $sDsn = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $sUser = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $sPassword = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $sAllowedEmails = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \MailSo\Log\Logger
|
||||||
|
*/
|
||||||
|
private $oLogger = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sDsn
|
||||||
|
* @param string $sUser
|
||||||
|
* @param string $sPassword
|
||||||
|
*
|
||||||
|
* @return \FroxlorChangePasswordDriver
|
||||||
|
*/
|
||||||
|
public function SetConfig($sDsn, $sUser, $sPassword)
|
||||||
|
{
|
||||||
|
$this->sDsn = $sDsn;
|
||||||
|
$this->sUser = $sUser;
|
||||||
|
$this->sPassword = $sPassword;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sAllowedEmails
|
||||||
|
*
|
||||||
|
* @return \FroxlorChangePasswordDriver
|
||||||
|
*/
|
||||||
|
public function SetAllowedEmails($sAllowedEmails)
|
||||||
|
{
|
||||||
|
$this->sAllowedEmails = $sAllowedEmails;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \MailSo\Log\Logger $oLogger
|
||||||
|
*
|
||||||
|
* @return \FroxlorChangePasswordDriver
|
||||||
|
*/
|
||||||
|
public function SetLogger($oLogger)
|
||||||
|
{
|
||||||
|
if ($oLogger instanceof \MailSo\Log\Logger)
|
||||||
|
{
|
||||||
|
$this->oLogger = $oLogger;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \RainLoop\Account $oAccount
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function PasswordChangePossibility($oAccount)
|
||||||
|
{
|
||||||
|
return $oAccount && $oAccount->Email() &&
|
||||||
|
\RainLoop\Plugins\Helper::ValidateWildcardValues($oAccount->Email(), $this->sAllowedEmails);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \RainLoop\Account $oAccount
|
||||||
|
* @param string $sPrevPassword
|
||||||
|
* @param string $sNewPassword
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function ChangePassword(\RainLoop\Account $oAccount, $sPrevPassword, $sNewPassword)
|
||||||
|
{
|
||||||
|
if ($this->oLogger) {
|
||||||
|
$this->oLogger->Write('Froxlor: Try to change password for '.$oAccount->Email());
|
||||||
|
}
|
||||||
|
|
||||||
|
$bResult = false;
|
||||||
|
if (!empty($this->sDsn) && 0 < \strlen($this->sUser) && 0 < \strlen($this->sPassword) && $oAccount)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$oPdo = new \PDO($this->sDsn, $this->sUser, $this->sPassword);
|
||||||
|
$oPdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||||
|
|
||||||
|
$oStmt = $oPdo->prepare('SELECT password_enc, id FROM mail_users WHERE username = ? LIMIT 1');
|
||||||
|
if ($oStmt->execute(array($oAccount->IncLogin())))
|
||||||
|
{
|
||||||
|
$aFetchResult = $oStmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||||
|
if (\is_array($aFetchResult) && isset($aFetchResult[0]['password_enc'], $aFetchResult[0]['id']))
|
||||||
|
{
|
||||||
|
$sDbPassword = \stripslashes($aFetchResult[0]['password_enc']);
|
||||||
|
|
||||||
|
if ( $this->validatePasswordLogin( $sDbPassword, $sPrevPassword ) ) {
|
||||||
|
$sEncNewPassword = $this->cryptPassword($sNewPassword, 3);
|
||||||
|
$oStmt = $oPdo->prepare('UPDATE mail_users SET password_enc = ?,password = ? WHERE id = ?');
|
||||||
|
$bResult = (bool) $oStmt->execute(
|
||||||
|
array($sEncNewPassword, $sNewPassword, $aFetchResult[0]['id']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (\Exception $oException)
|
||||||
|
{
|
||||||
|
if ($this->oLogger)
|
||||||
|
{
|
||||||
|
$this->oLogger->WriteException($oException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sPassword
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function cryptPassword($sPassword, $type = 3)
|
||||||
|
{
|
||||||
|
return $this->makeCryptPassword($sPassword,$type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file is part of the Froxlor project.
|
||||||
|
* Copyright (c) 2010 the Froxlor Team (see authors).
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the COPYING
|
||||||
|
* file that was distributed with this source code. You can also view the
|
||||||
|
* COPYING file online at http://files.froxlor.org/misc/COPYING.txt
|
||||||
|
*
|
||||||
|
* @copyright (c) the authors
|
||||||
|
* @author Michal Wojcik <m.wojcik@sonet3.pl>
|
||||||
|
* @author Michael Kaufmann <mkaufmann@nutime.de>
|
||||||
|
* @author Froxlor team <team@froxlor.org> (2010-)
|
||||||
|
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
|
||||||
|
* @package Functions
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make crypted password from clear text password
|
||||||
|
*
|
||||||
|
* @author Michal Wojcik <m.wojcik@sonet3.pl>
|
||||||
|
* @author Michael Kaufmann <mkaufmann@nutime.de>
|
||||||
|
* @author Froxlor team <team@froxlor.org> (2010-)
|
||||||
|
*
|
||||||
|
* 0 - default crypt (depenend on system configuration)
|
||||||
|
* 1 - MD5 $1$
|
||||||
|
* 2 - BLOWFISH $2a$ | $2y$07$ (on php 5.3.7+)
|
||||||
|
* 3 - SHA-256 $5$ (default)
|
||||||
|
* 4 - SHA-512 $6$
|
||||||
|
*
|
||||||
|
* @param string $password Password to be crypted
|
||||||
|
*
|
||||||
|
* @return string encrypted password
|
||||||
|
*/
|
||||||
|
private function makeCryptPassword ($password,$type = 3) {
|
||||||
|
switch ($type) {
|
||||||
|
case 0:
|
||||||
|
$cryptPassword = \crypt($password);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
$cryptPassword = \crypt($password, '$1$' . $this->generatePassword(true). $this->generatePassword(true));
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (\version_compare(\phpversion(), '5.3.7', '<')) {
|
||||||
|
$cryptPassword = \crypt($password, '$2a$' . $this->generatePassword(true). $this->generatePassword(true));
|
||||||
|
} else {
|
||||||
|
// Blowfish hashing with a salt as follows: "$2a$", "$2x$" or "$2y$",
|
||||||
|
// a two digit cost parameter, "$", and 22 characters from the alphabet "./0-9A-Za-z"
|
||||||
|
$cryptPassword = \crypt(
|
||||||
|
$password,
|
||||||
|
'$2y$07$' . \substr($this->generatePassword(true).$this->generatePassword(true).$this->generatePassword(true), 0, 22)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
$cryptPassword = \crypt($password, '$5$' . $this->generatePassword(true). $this->generatePassword(true));
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
$cryptPassword = \crypt($password, '$6$' . $this->generatePassword(true). $this->generatePassword(true));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$cryptPassword = \crypt($password);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cryptPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random password
|
||||||
|
*
|
||||||
|
* @param boolean $isSalt
|
||||||
|
* optional, create a hash for a salt used in makeCryptPassword because crypt() does not like some special characters in its salts, default is false
|
||||||
|
*/
|
||||||
|
private function generatePassword($isSalt = false)
|
||||||
|
{
|
||||||
|
$alpha_lower = 'abcdefghijklmnopqrstuvwxyz';
|
||||||
|
$alpha_upper = \strtoupper($alpha_lower);
|
||||||
|
$numeric = '0123456789';
|
||||||
|
$special = '!?<>§$%&+#=';
|
||||||
|
$length = 10;
|
||||||
|
|
||||||
|
$pw = $this->special_shuffle($alpha_lower);
|
||||||
|
$n = \floor(($length) / 4);
|
||||||
|
$pw .= \mb_substr($this->special_shuffle($alpha_upper), 0, $n);
|
||||||
|
$pw .= \mb_substr($this->special_shuffle($numeric), 0, $n);
|
||||||
|
$pw = \mb_substr($pw, - $length);
|
||||||
|
return $this->special_shuffle($pw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* multibyte-character safe shuffle function
|
||||||
|
*
|
||||||
|
* @param string $str
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function special_shuffle($str = null)
|
||||||
|
{
|
||||||
|
$len = \mb_strlen($str);
|
||||||
|
$sploded = array();
|
||||||
|
while ($len -- > 0) {
|
||||||
|
$sploded[] = \mb_substr($str, $len, 1);
|
||||||
|
}
|
||||||
|
\shuffle($sploded);
|
||||||
|
return \join('', $sploded);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function validatePasswordLogin
|
||||||
|
*
|
||||||
|
* compare user password-hash with given user-password
|
||||||
|
* and check if they are the same
|
||||||
|
* additionally it updates the hash if the system settings changed
|
||||||
|
* or if the very old md5() sum is used
|
||||||
|
*
|
||||||
|
* @param array $userinfo user-data from table
|
||||||
|
* @param string $password the password to validate
|
||||||
|
* @param string $table either panel_customers or panel_admins
|
||||||
|
* @param string $uid user-id-field in $table
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
private function validatePasswordLogin($pwd_hash, $password = null) {
|
||||||
|
|
||||||
|
$systype = 3; // SHA256
|
||||||
|
$update_hash = false;
|
||||||
|
// check for good'ole md5
|
||||||
|
if (\strlen($pwd_hash) == 32 && \ctype_xdigit($pwd_hash)) {
|
||||||
|
$pwd_check = \md5($password);
|
||||||
|
$update_hash = true;
|
||||||
|
} else {
|
||||||
|
// cut out the salt from the hash
|
||||||
|
$pwd_salt = \str_replace(\substr(\strrchr($pwd_hash, "$"), 1), "", $pwd_hash);
|
||||||
|
// create same hash to compare
|
||||||
|
$pwd_check = \crypt($password, $pwd_salt);
|
||||||
|
// check whether the hash needs to be updated
|
||||||
|
$hash_type_chk = \substr($pwd_hash, 0, 3);
|
||||||
|
if (($systype == 1 && $hash_type_chk != '$1$') || // MD5
|
||||||
|
($systype == 2 && $hash_type_chk != '$2$') || // BLOWFISH
|
||||||
|
($systype == 3 && $hash_type_chk != '$5$') || // SHA256
|
||||||
|
($systype == 4 && $hash_type_chk != '$6$') // SHA512
|
||||||
|
) {
|
||||||
|
$update_hash = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pwd_check;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
20
plugins/froxlor-change-password/LICENSE
Normal file
20
plugins/froxlor-change-password/LICENSE
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2017 Bob Kromonos Achten
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
1
plugins/froxlor-change-password/README
Normal file
1
plugins/froxlor-change-password/README
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Plugin that adds functionality to change the email account password (Froxlor).
|
||||||
1
plugins/froxlor-change-password/VERSION
Normal file
1
plugins/froxlor-change-password/VERSION
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
1.0
|
||||||
76
plugins/froxlor-change-password/index.php
Normal file
76
plugins/froxlor-change-password/index.php
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FroxlorchangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
|
{
|
||||||
|
public function Init()
|
||||||
|
{
|
||||||
|
$this->addHook('main.fabrica', 'MainFabrica');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function Supported()
|
||||||
|
{
|
||||||
|
if (!extension_loaded('pdo') || !class_exists('PDO'))
|
||||||
|
{
|
||||||
|
return 'The PHP exention PDO (mysql) must be installed to use this plugin';
|
||||||
|
}
|
||||||
|
|
||||||
|
$aDrivers = \PDO::getAvailableDrivers();
|
||||||
|
if (!is_array($aDrivers) || !in_array('mysql', $aDrivers))
|
||||||
|
{
|
||||||
|
return 'The PHP exention PDO (mysql) must be installed to use this plugin';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sName
|
||||||
|
* @param mixed $oProvider
|
||||||
|
*/
|
||||||
|
public function MainFabrica($sName, &$oProvider)
|
||||||
|
{
|
||||||
|
switch ($sName)
|
||||||
|
{
|
||||||
|
case 'change-password':
|
||||||
|
|
||||||
|
$sDsn = \trim($this->Config()->Get('plugin', 'pdo_dsn', ''));
|
||||||
|
$sUser = (string) $this->Config()->Get('plugin', 'user', '');
|
||||||
|
$sPassword = (string) $this->Config()->Get('plugin', 'password', '');
|
||||||
|
|
||||||
|
if (!empty($sDsn) && 0 < \strlen($sUser) && 0 < \strlen($sPassword))
|
||||||
|
{
|
||||||
|
include_once __DIR__.'/FroxlorChangePasswordDriver.php';
|
||||||
|
|
||||||
|
$oProvider = new FroxlorChangePasswordDriver();
|
||||||
|
$oProvider->SetLogger($this->Manager()->Actions()->Logger());
|
||||||
|
$oProvider->SetConfig($sDsn, $sUser, $sPassword);
|
||||||
|
$oProvider->SetAllowedEmails(\strtolower(\trim($this->Config()->Get('plugin', 'allowed_emails', ''))));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function configMapping()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
\RainLoop\Plugins\Property::NewInstance('pdo_dsn')->SetLabel('Froxlor PDO dsn')
|
||||||
|
->SetDefaultValue('mysql:host=127.0.0.1;dbname=froxlor'),
|
||||||
|
\RainLoop\Plugins\Property::NewInstance('user')->SetLabel('DB User')
|
||||||
|
->SetDefaultValue('root'),
|
||||||
|
\RainLoop\Plugins\Property::NewInstance('password')->SetLabel('DB Password')
|
||||||
|
->SetType(\RainLoop\Enumerations\PluginPropertyType::PASSWORD)
|
||||||
|
->SetDefaultValue(''),
|
||||||
|
\RainLoop\Plugins\Property::NewInstance('allowed_emails')->SetLabel('Allowed emails')
|
||||||
|
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
|
||||||
|
->SetDescription('Allowed emails, space as delimiter, wildcard supported. Example: user1@domain1.net user2@domain1.net *@domain2.net')
|
||||||
|
->SetDefaultValue('*')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ class HmailserverChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
{
|
{
|
||||||
if (!class_exists('COM'))
|
if (!class_exists('COM'))
|
||||||
{
|
{
|
||||||
return 'The PHP exention COM must be installed to use this plugin';
|
return 'The PHP extension COM must be installed to use this plugin';
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,13 @@ class IspconfigChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
{
|
{
|
||||||
if (!extension_loaded('pdo') || !class_exists('PDO'))
|
if (!extension_loaded('pdo') || !class_exists('PDO'))
|
||||||
{
|
{
|
||||||
return 'The PHP exention PDO (mysql) must be installed to use this plugin';
|
return 'The PHP extension PDO (mysql) must be installed to use this plugin';
|
||||||
}
|
}
|
||||||
|
|
||||||
$aDrivers = \PDO::getAvailableDrivers();
|
$aDrivers = \PDO::getAvailableDrivers();
|
||||||
if (!is_array($aDrivers) || !in_array('mysql', $aDrivers))
|
if (!is_array($aDrivers) || !in_array('mysql', $aDrivers))
|
||||||
{
|
{
|
||||||
return 'The PHP exention PDO (mysql) must be installed to use this plugin';
|
return 'The PHP extension PDO (mysql) must be installed to use this plugin';
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,13 @@ class IspmailChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
{
|
{
|
||||||
if (!extension_loaded('pdo') || !class_exists('PDO'))
|
if (!extension_loaded('pdo') || !class_exists('PDO'))
|
||||||
{
|
{
|
||||||
return 'The PHP exention PDO (mysql) must be installed to use this plugin';
|
return 'The PHP extension PDO (mysql) must be installed to use this plugin';
|
||||||
}
|
}
|
||||||
|
|
||||||
$aDrivers = \PDO::getAvailableDrivers();
|
$aDrivers = \PDO::getAvailableDrivers();
|
||||||
if (!is_array($aDrivers) || !in_array('mysql', $aDrivers))
|
if (!is_array($aDrivers) || !in_array('mysql', $aDrivers))
|
||||||
{
|
{
|
||||||
return 'The PHP exention PDO (mysql) must be installed to use this plugin';
|
return 'The PHP extension PDO (mysql) must be installed to use this plugin';
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
|
|
|
||||||
|
|
@ -118,13 +118,25 @@ class ChangePasswordLdapDriver implements \RainLoop\Providers\ChangePassword\Cha
|
||||||
'{login}' => $oAccount->Login(),
|
'{login}' => $oAccount->Login(),
|
||||||
'{imap:login}' => $oAccount->Login(),
|
'{imap:login}' => $oAccount->Login(),
|
||||||
'{imap:host}' => $oAccount->DomainIncHost(),
|
'{imap:host}' => $oAccount->DomainIncHost(),
|
||||||
'{imap:port}' => $oAccount->DomainIncPort()
|
'{imap:port}' => $oAccount->DomainIncPort(),
|
||||||
|
'{gecos}' => function_exists('posix_getpwnam') ? posix_getpwnam($oAccount->Login()) : ''
|
||||||
));
|
));
|
||||||
|
|
||||||
$oCon = @\ldap_connect($this->sHostName, $this->iHostPort);
|
$oCon = @\ldap_connect($this->sHostName, $this->iHostPort);
|
||||||
if ($oCon)
|
if ($oCon)
|
||||||
{
|
{
|
||||||
@\ldap_set_option($oCon, LDAP_OPT_PROTOCOL_VERSION, 3);
|
if (!@\ldap_set_option($oCon, LDAP_OPT_PROTOCOL_VERSION, 3))
|
||||||
|
{
|
||||||
|
$this->oLogger->Write(
|
||||||
|
'Failed to set LDAP Protocol version to 3, TLS not supported.',
|
||||||
|
\MailSo\Log\Enumerations\Type::WARNING,
|
||||||
|
'LDAP'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (@!ldap_start_tls($oCon))
|
||||||
|
{
|
||||||
|
$this->oLogger->Write("ldap_start_tls failed: ".$oCon, \MailSo\Log\Enumerations\Type::WARNING, 'LDAP');
|
||||||
|
}
|
||||||
|
|
||||||
if (!@\ldap_bind($oCon, $sUserDn, $sPrevPassword))
|
if (!@\ldap_bind($oCon, $sUserDn, $sPrevPassword))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ class LdapChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
{
|
{
|
||||||
if (!\function_exists('ldap_connect'))
|
if (!\function_exists('ldap_connect'))
|
||||||
{
|
{
|
||||||
return 'The LDAP PHP exention must be installed to use this plugin';
|
return 'The LDAP PHP extension must be installed to use this plugin';
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
|
|
@ -64,7 +64,7 @@ class LdapChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
->SetType(\RainLoop\Enumerations\PluginPropertyType::INT)
|
->SetType(\RainLoop\Enumerations\PluginPropertyType::INT)
|
||||||
->SetDefaultValue(389),
|
->SetDefaultValue(389),
|
||||||
\RainLoop\Plugins\Property::NewInstance('user_dn_format')->SetLabel('User DN format')
|
\RainLoop\Plugins\Property::NewInstance('user_dn_format')->SetLabel('User DN format')
|
||||||
->SetDescription('LDAP user dn format. Supported tokens: {email}, {email:user}, {email:domain}, {login}, {domain}, {domain:dc}, {imap:login}, {imap:host}, {imap:port}')
|
->SetDescription('LDAP user dn format. Supported tokens: {email}, {email:user}, {email:domain}, {login}, {domain}, {domain:dc}, {imap:login}, {imap:host}, {imap:port}, {gecos}')
|
||||||
->SetDefaultValue('uid={imap:login},ou=Users,{domain:dc}'),
|
->SetDefaultValue('uid={imap:login},ou=Users,{domain:dc}'),
|
||||||
\RainLoop\Plugins\Property::NewInstance('password_field')->SetLabel('Password field')
|
\RainLoop\Plugins\Property::NewInstance('password_field')->SetLabel('Password field')
|
||||||
->SetDefaultValue('userPassword'),
|
->SetDefaultValue('userPassword'),
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,12 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
private $sAccessDn = '';
|
private $sAccessDn = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
private $sAccessPassword = '';
|
private $sAccessPassword = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
|
|
@ -32,6 +32,11 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges
|
||||||
*/
|
*/
|
||||||
private $sObjectClass = 'inetOrgPerson';
|
private $sObjectClass = 'inetOrgPerson';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $sUidField = 'uid';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
|
|
@ -64,14 +69,18 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges
|
||||||
*
|
*
|
||||||
* @return \LdapContactsSuggestions
|
* @return \LdapContactsSuggestions
|
||||||
*/
|
*/
|
||||||
public function SetConfig($sHostName, $iHostPort, $sAccessDn, $sAccessPassword, $sUsersDn, $sObjectClass, $sNameField, $sEmailField)
|
public function SetConfig($sHostName, $iHostPort, $sAccessDn, $sAccessPassword, $sUsersDn, $sObjectClass, $sUidField, $sNameField, $sEmailField)
|
||||||
{
|
{
|
||||||
$this->sHostName = $sHostName;
|
$this->sHostName = $sHostName;
|
||||||
$this->iHostPort = $iHostPort;
|
$this->iHostPort = $iHostPort;
|
||||||
|
if (0 < \strlen($sAccessDn))
|
||||||
|
{
|
||||||
$this->sAccessDn = $sAccessDn;
|
$this->sAccessDn = $sAccessDn;
|
||||||
$this->sAccessPassword = $sAccessPassword;
|
$this->sAccessPassword = $sAccessPassword;
|
||||||
|
}
|
||||||
$this->sUsersDn = $sUsersDn;
|
$this->sUsersDn = $sUsersDn;
|
||||||
$this->sObjectClass = $sObjectClass;
|
$this->sObjectClass = $sObjectClass;
|
||||||
|
$this->sUidField = $sUidField;
|
||||||
$this->sNameField = $sNameField;
|
$this->sNameField = $sNameField;
|
||||||
$this->sEmailField = $sEmailField;
|
$this->sEmailField = $sEmailField;
|
||||||
|
|
||||||
|
|
@ -128,9 +137,9 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
private function findNameAndEmail($aLdapItem, $aEmailFields, $aNameFields)
|
private function findNameAndEmail($aLdapItem, $aEmailFields, $aNameFields, $aUidFields)
|
||||||
{
|
{
|
||||||
$sEmail = $sName = '';
|
$sEmail = $sName = $sUid = '';
|
||||||
if ($aLdapItem)
|
if ($aLdapItem)
|
||||||
{
|
{
|
||||||
foreach ($aEmailFields as $sField)
|
foreach ($aEmailFields as $sField)
|
||||||
|
|
@ -156,9 +165,21 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($aUidFields as $sField)
|
||||||
|
{
|
||||||
|
if (!empty($aLdapItem[$sField][0]))
|
||||||
|
{
|
||||||
|
$sUid = \trim($aLdapItem[$sField][0]);
|
||||||
|
if (!empty($sUid))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return array($sEmail, $sName);
|
return array($sEmail, $sName, $sUid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -180,8 +201,16 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges
|
||||||
@\ldap_set_option($oCon, LDAP_OPT_PROTOCOL_VERSION, 3);
|
@\ldap_set_option($oCon, LDAP_OPT_PROTOCOL_VERSION, 3);
|
||||||
|
|
||||||
if (!@\ldap_bind($oCon, $this->sAccessDn, $this->sAccessPassword))
|
if (!@\ldap_bind($oCon, $this->sAccessDn, $this->sAccessPassword))
|
||||||
|
{
|
||||||
|
if (is_null($this->sAccessDn))
|
||||||
|
{
|
||||||
|
$this->logLdapError($oCon, 'ldap_bind (anonymous)');
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
$this->logLdapError($oCon, 'ldap_bind');
|
$this->logLdapError($oCon, 'ldap_bind');
|
||||||
|
}
|
||||||
|
|
||||||
return $aResult;
|
return $aResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -200,11 +229,13 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges
|
||||||
|
|
||||||
$aEmails = empty($this->sEmailField) ? array() : \explode(',', $this->sEmailField);
|
$aEmails = empty($this->sEmailField) ? array() : \explode(',', $this->sEmailField);
|
||||||
$aNames = empty($this->sNameField) ? array() : \explode(',', $this->sNameField);
|
$aNames = empty($this->sNameField) ? array() : \explode(',', $this->sNameField);
|
||||||
|
$aUIDs = empty($this->sUidField) ? array() : \explode(',', $this->sUidField);
|
||||||
|
|
||||||
$aEmails = \array_map('trim', $aEmails);
|
$aEmails = \array_map('trim', $aEmails);
|
||||||
$aNames = \array_map('trim', $aNames);
|
$aNames = \array_map('trim', $aNames);
|
||||||
|
$aUIDs = \array_map('trim', $aUIDs);
|
||||||
|
|
||||||
$aFields = \array_merge($aEmails, $aNames);
|
$aFields = \array_merge($aEmails, $aNames, $aUIDs);
|
||||||
|
|
||||||
$aItems = array();
|
$aItems = array();
|
||||||
$sSubFilter = '';
|
$sSubFilter = '';
|
||||||
|
|
@ -238,7 +269,7 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges
|
||||||
if ($aItem)
|
if ($aItem)
|
||||||
{
|
{
|
||||||
$sName = $sEmail = '';
|
$sName = $sEmail = '';
|
||||||
list ($sEmail, $sName) = $this->findNameAndEmail($aItem, $aEmails, $aNames);
|
list ($sEmail, $sName) = $this->findNameAndEmail($aItem, $aEmails, $aNames, $aUIDs);
|
||||||
if (!empty($sEmail))
|
if (!empty($sEmail))
|
||||||
{
|
{
|
||||||
$aResult[] = array($sEmail, $sName);
|
$aResult[] = array($sEmail, $sName);
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
1.0
|
1.1
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ class LdapContactsSuggestionsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
{
|
{
|
||||||
if (!\function_exists('ldap_connect'))
|
if (!\function_exists('ldap_connect'))
|
||||||
{
|
{
|
||||||
return 'The LDAP PHP exention must be installed to use this plugin';
|
return 'The LDAP PHP extension must be installed to use this plugin';
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
|
|
@ -41,16 +41,16 @@ class LdapContactsSuggestionsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
$sAccessPassword = \trim($this->Config()->Get('plugin', 'access_password', ''));
|
$sAccessPassword = \trim($this->Config()->Get('plugin', 'access_password', ''));
|
||||||
$sUsersDn = \trim($this->Config()->Get('plugin', 'users_dn_format', ''));
|
$sUsersDn = \trim($this->Config()->Get('plugin', 'users_dn_format', ''));
|
||||||
$sObjectClass = \trim($this->Config()->Get('plugin', 'object_class', ''));
|
$sObjectClass = \trim($this->Config()->Get('plugin', 'object_class', ''));
|
||||||
|
$sSearchField = \trim($this->Config()->Get('plugin', 'search_field', ''));
|
||||||
$sNameField = \trim($this->Config()->Get('plugin', 'name_field', ''));
|
$sNameField = \trim($this->Config()->Get('plugin', 'name_field', ''));
|
||||||
$sEmailField = \trim($this->Config()->Get('plugin', 'mail_field', ''));
|
$sEmailField = \trim($this->Config()->Get('plugin', 'mail_field', ''));
|
||||||
|
|
||||||
if (0 < \strlen($sAccessDn) && 0 < \strlen($sAccessPassword) && 0 < \strlen($sUsersDn) &&
|
if (0 < \strlen($sUsersDn) && 0 < \strlen($sObjectClass) && 0 < \strlen($sEmailField))
|
||||||
0 < \strlen($sObjectClass) && 0 < \strlen($sEmailField))
|
|
||||||
{
|
{
|
||||||
include_once __DIR__.'/LdapContactsSuggestions.php';
|
include_once __DIR__.'/LdapContactsSuggestions.php';
|
||||||
|
|
||||||
$oProvider = new LdapContactsSuggestions();
|
$oProvider = new LdapContactsSuggestions();
|
||||||
$oProvider->SetConfig($sHostName, $iHostPort, $sAccessDn, $sAccessPassword, $sUsersDn, $sObjectClass, $sNameField, $sEmailField);
|
$oProvider->SetConfig($sHostName, $iHostPort, $sAccessDn, $sAccessPassword, $sUsersDn, $sObjectClass, $sSearchField, $sNameField, $sEmailField);
|
||||||
|
|
||||||
$mResult[] = $oProvider;
|
$mResult[] = $oProvider;
|
||||||
}
|
}
|
||||||
|
|
@ -71,6 +71,7 @@ class LdapContactsSuggestionsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
->SetType(\RainLoop\Enumerations\PluginPropertyType::INT)
|
->SetType(\RainLoop\Enumerations\PluginPropertyType::INT)
|
||||||
->SetDefaultValue(389),
|
->SetDefaultValue(389),
|
||||||
\RainLoop\Plugins\Property::NewInstance('access_dn')->SetLabel('Access dn (login)')
|
\RainLoop\Plugins\Property::NewInstance('access_dn')->SetLabel('Access dn (login)')
|
||||||
|
->SetDescription('LDAP bind DN to authentifcate with. If left blank, anonymous bind will be tried and Access password will be ignored')
|
||||||
->SetDefaultValue(''),
|
->SetDefaultValue(''),
|
||||||
\RainLoop\Plugins\Property::NewInstance('access_password')->SetLabel('Access password')
|
\RainLoop\Plugins\Property::NewInstance('access_password')->SetLabel('Access password')
|
||||||
->SetType(\RainLoop\Enumerations\PluginPropertyType::PASSWORD)
|
->SetType(\RainLoop\Enumerations\PluginPropertyType::PASSWORD)
|
||||||
|
|
@ -80,6 +81,8 @@ class LdapContactsSuggestionsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
->SetDefaultValue('ou=People,dc=domain,dc=com'),
|
->SetDefaultValue('ou=People,dc=domain,dc=com'),
|
||||||
\RainLoop\Plugins\Property::NewInstance('object_class')->SetLabel('objectClass value')
|
\RainLoop\Plugins\Property::NewInstance('object_class')->SetLabel('objectClass value')
|
||||||
->SetDefaultValue('inetOrgPerson'),
|
->SetDefaultValue('inetOrgPerson'),
|
||||||
|
\RainLoop\Plugins\Property::NewInstance('search_field')->SetLabel('Search field')
|
||||||
|
->SetDefaultValue('uid'),
|
||||||
\RainLoop\Plugins\Property::NewInstance('name_field')->SetLabel('Name field')
|
\RainLoop\Plugins\Property::NewInstance('name_field')->SetLabel('Name field')
|
||||||
->SetDefaultValue('givenname'),
|
->SetDefaultValue('givenname'),
|
||||||
\RainLoop\Plugins\Property::NewInstance('mail_field')->SetLabel('Mail field')
|
\RainLoop\Plugins\Property::NewInstance('mail_field')->SetLabel('Mail field')
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,13 @@ class MailcowChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
{
|
{
|
||||||
if (!extension_loaded('pdo') || !class_exists('PDO'))
|
if (!extension_loaded('pdo') || !class_exists('PDO'))
|
||||||
{
|
{
|
||||||
return 'The PHP exention PDO (mysql) must be installed to use this plugin';
|
return 'The PHP extension PDO (mysql) must be installed to use this plugin';
|
||||||
}
|
}
|
||||||
|
|
||||||
$aDrivers = \PDO::getAvailableDrivers();
|
$aDrivers = \PDO::getAvailableDrivers();
|
||||||
if (!is_array($aDrivers) || !in_array('mysql', $aDrivers))
|
if (!is_array($aDrivers) || !in_array('mysql', $aDrivers))
|
||||||
{
|
{
|
||||||
return 'The PHP exention PDO (mysql) must be installed to use this plugin';
|
return 'The PHP extension PDO (mysql) must be installed to use this plugin';
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
|
|
|
||||||
20
plugins/mailinabox-change-password/LICENSE
Normal file
20
plugins/mailinabox-change-password/LICENSE
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2017 Marius Gripsgard <marius@ubports.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
@ -0,0 +1,157 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Mail-in-a-box Password Change Plugin
|
||||||
|
*
|
||||||
|
* Based on VirtualminChangePasswordDriver
|
||||||
|
*
|
||||||
|
* Author: Marius Gripsgard
|
||||||
|
*/
|
||||||
|
class MailInABoxChangePasswordDriver implements \RainLoop\Providers\ChangePassword\ChangePasswordInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $sAllowedEmails = '';
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $sHost = '';
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $sAdminUser = '';
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $sAdminPassword = '';
|
||||||
|
/**
|
||||||
|
* @var \MailSo\Log\Logger
|
||||||
|
*/
|
||||||
|
private $oLogger = null;
|
||||||
|
/**
|
||||||
|
* @param string $sHost
|
||||||
|
* @param string $sAdminUser
|
||||||
|
* @param string $sAdminPassword
|
||||||
|
*
|
||||||
|
* @return \MailInABoxChangePasswordDriver
|
||||||
|
*/
|
||||||
|
public function SetConfig($sHost, $sAdminUser, $sAdminPassword)
|
||||||
|
{
|
||||||
|
$this->sHost = $sHost;
|
||||||
|
$this->sAdminUser = $sAdminUser;
|
||||||
|
$this->sAdminPassword = $sAdminPassword;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param string $sAllowedEmails
|
||||||
|
*
|
||||||
|
* @return \MailInABoxChangePasswordDriver
|
||||||
|
*/
|
||||||
|
public function SetAllowedEmails($sAllowedEmails)
|
||||||
|
{
|
||||||
|
$this->sAllowedEmails = $sAllowedEmails;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param \MailSo\Log\Logger $oLogger
|
||||||
|
*
|
||||||
|
* @return \MailInABoxChangePasswordDriver
|
||||||
|
*/
|
||||||
|
public function SetLogger($oLogger)
|
||||||
|
{
|
||||||
|
if ($oLogger instanceof \MailSo\Log\Logger)
|
||||||
|
{
|
||||||
|
$this->oLogger = $oLogger;
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param string $sDesc
|
||||||
|
* @param int $iType = \MailSo\Log\Enumerations\Type::INFO
|
||||||
|
*
|
||||||
|
* @return \MailInABoxChangePasswordDriver
|
||||||
|
*/
|
||||||
|
public function WriteLog($sDesc, $iType = \MailSo\Log\Enumerations\Type::INFO)
|
||||||
|
{
|
||||||
|
if ($this->oLogger)
|
||||||
|
{
|
||||||
|
$this->oLogger->Write($sDesc, $iType);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param \RainLoop\Model\Account $oAccount
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function PasswordChangePossibility($oAccount)
|
||||||
|
{
|
||||||
|
return $oAccount && $oAccount->Email() &&
|
||||||
|
\RainLoop\Plugins\Helper::ValidateWildcardValues($oAccount->Email(), $this->sAllowedEmails);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param \RainLoop\Model\Account $oAccount
|
||||||
|
* @param string $sPrevPassword
|
||||||
|
* @param string $sNewPassword
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function ChangePassword(\RainLoop\Account $oAccount, $sPrevPassword, $sNewPassword)
|
||||||
|
{
|
||||||
|
$this->WriteLog('Mail-in-a-box: Try to change password for '.$oAccount->Email());
|
||||||
|
$bResult = false;
|
||||||
|
if (!empty($this->sHost) && !empty($this->sAdminUser) && !empty($this->sAdminPassword) && $oAccount)
|
||||||
|
{
|
||||||
|
$this->WriteLog('Mail-in-a-box:[Check] Required Fields Present');
|
||||||
|
$sEmail = \trim(\strtolower($oAccount->Email()));
|
||||||
|
$sHost = \rtrim(\trim($this->sHost), '/');
|
||||||
|
$sUrl = $sHost.'/admin/mail/users/password';
|
||||||
|
|
||||||
|
$sAdminUser = $this->sAdminUser;
|
||||||
|
$sAdminPassword = $this->sAdminPassword;
|
||||||
|
$iCode = 0;
|
||||||
|
$aPost = array(
|
||||||
|
'email' => $sEmail,
|
||||||
|
'password' => $sNewPassword,
|
||||||
|
);
|
||||||
|
$aOptions = array(
|
||||||
|
CURLOPT_URL => $sUrl,
|
||||||
|
CURLOPT_HEADER => false,
|
||||||
|
CURLOPT_FAILONERROR => true,
|
||||||
|
CURLOPT_SSL_VERIFYPEER => false,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_POSTFIELDS => \http_build_query($aPost, '', '&'),
|
||||||
|
CURLOPT_TIMEOUT => 20,
|
||||||
|
CURLOPT_SSL_VERIFYHOST => false,
|
||||||
|
CURLOPT_USERPWD => $sAdminUser.':'.$sAdminPassword,
|
||||||
|
CURLOPT_HTTPAUTH => CURLAUTH_BASIC
|
||||||
|
);
|
||||||
|
$oCurl = \curl_init();
|
||||||
|
\curl_setopt_array($oCurl, $aOptions);
|
||||||
|
$this->WriteLog('Mail-in-a-box: Send post request: '.$sUrl);
|
||||||
|
$mResult = \curl_exec($oCurl);
|
||||||
|
$iCode = (int) \curl_getinfo($oCurl, CURLINFO_HTTP_CODE);
|
||||||
|
$sContentType = (string) \curl_getinfo($oCurl, CURLINFO_CONTENT_TYPE);
|
||||||
|
$this->WriteLog('Mail-in-a-box: Post request result: (Status: '.$iCode.', ContentType: '.$sContentType.')');
|
||||||
|
if (false === $mResult || 200 !== $iCode)
|
||||||
|
{
|
||||||
|
$this->WriteLog('Mail-in-a-box: Error: '.\curl_error($oCurl), \MailSo\Log\Enumerations\Type::WARNING);
|
||||||
|
}
|
||||||
|
if (\is_resource($oCurl))
|
||||||
|
{
|
||||||
|
\curl_close($oCurl);
|
||||||
|
}
|
||||||
|
if (false !== $mResult && 200 === $iCode)
|
||||||
|
{
|
||||||
|
$this->WriteLog('Mail-in-a-box: Password Change Status: Success');
|
||||||
|
$bResult = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->WriteLog('Mail-in-a-box[Error]: Empty Response: Code: '.$iCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $bResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
plugins/mailinabox-change-password/README
Normal file
1
plugins/mailinabox-change-password/README
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Plugin that adds functionality to change the email account password (Mail-in-a-Box).
|
||||||
1
plugins/mailinabox-change-password/VERSION
Normal file
1
plugins/mailinabox-change-password/VERSION
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
1.0
|
||||||
54
plugins/mailinabox-change-password/index.php
Normal file
54
plugins/mailinabox-change-password/index.php
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Mail-in-a-box Password Change Plugin
|
||||||
|
*
|
||||||
|
* Based on VirtualminChangePassword
|
||||||
|
*
|
||||||
|
* Author: Marius Gripsgard
|
||||||
|
*/
|
||||||
|
class MailInABoxChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
|
{
|
||||||
|
public function Init()
|
||||||
|
{
|
||||||
|
$this->addHook('main.fabrica', 'MainFabrica');
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param string $sName
|
||||||
|
* @param mixed $oProvider
|
||||||
|
*/
|
||||||
|
public function MainFabrica($sName, &$oProvider)
|
||||||
|
{
|
||||||
|
switch ($sName)
|
||||||
|
{
|
||||||
|
case 'change-password':
|
||||||
|
include_once __DIR__.'/MailInABoxChangePasswordDriver.php';
|
||||||
|
$sHost = \trim($this->Config()->Get('plugin', 'host', ''));
|
||||||
|
$sAdminUser = (string) $this->Config()->Get('plugin', 'admin_user', '');
|
||||||
|
$sAdminPassword = (string) $this->Config()->Get('plugin', 'admin_password', '');
|
||||||
|
$oProvider = new \MailInABoxChangePasswordDriver();
|
||||||
|
$oProvider->SetLogger($this->Manager()->Actions()->Logger());
|
||||||
|
$oProvider->SetConfig($sHost, $sAdminUser, $sAdminPassword);
|
||||||
|
$oProvider->SetAllowedEmails(\strtolower(\trim($this->Config()->Get('plugin', 'allowed_emails', ''))));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function configMapping()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
\RainLoop\Plugins\Property::NewInstance('host')->SetLabel('Mail-in-a-box Host')
|
||||||
|
->SetDefaultValue('https://box.mailinabox.email')
|
||||||
|
->SetDescription('Mail-in-a-box host URL. Example: https://box.mailinabox.email'),
|
||||||
|
\RainLoop\Plugins\Property::NewInstance('admin_user')->SetLabel('Admin User')
|
||||||
|
->SetDefaultValue(''),
|
||||||
|
\RainLoop\Plugins\Property::NewInstance('admin_password')->SetLabel('Admin Password')
|
||||||
|
->SetDefaultValue(''),
|
||||||
|
\RainLoop\Plugins\Property::NewInstance('allowed_emails')->SetLabel('Allowed emails')
|
||||||
|
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
|
||||||
|
->SetDescription('Allowed emails, space as delimiter, wildcard supported. Example: user1@domain1.net user2@domain1.net *@domain2.net')
|
||||||
|
->SetDefaultValue('*')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,11 @@
|
||||||
|
|
||||||
class ChangePasswordPostfixAdminDriver implements \RainLoop\Providers\ChangePassword\ChangePasswordInterface
|
class ChangePasswordPostfixAdminDriver implements \RainLoop\Providers\ChangePassword\ChangePasswordInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $sEngine = 'MySQL';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
|
|
@ -57,6 +62,17 @@ class ChangePasswordPostfixAdminDriver implements \RainLoop\Providers\ChangePass
|
||||||
*/
|
*/
|
||||||
private $oLogger = null;
|
private $oLogger = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sEngine
|
||||||
|
*
|
||||||
|
* @return \ChangePasswordPostfixAdminDriver
|
||||||
|
*/
|
||||||
|
public function SetEngine($sEngine)
|
||||||
|
{
|
||||||
|
$this->sEngine = $sEngine;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $sHost
|
* @param string $sHost
|
||||||
*
|
*
|
||||||
|
|
@ -215,7 +231,19 @@ class ChangePasswordPostfixAdminDriver implements \RainLoop\Providers\ChangePass
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
$sDsn = '';
|
||||||
|
switch($this->sEngine){
|
||||||
|
case 'MySQL':
|
||||||
$sDsn = 'mysql:host='.$this->sHost.';port='.$this->iPort.';dbname='.$this->sDatabase;
|
$sDsn = 'mysql:host='.$this->sHost.';port='.$this->iPort.';dbname='.$this->sDatabase;
|
||||||
|
break;
|
||||||
|
case 'PostgreSQL':
|
||||||
|
$sDsn = 'pgsql:host='.$this->sHost.';port='.$this->iPort.';dbname='.$this->sDatabase;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$sDsn = 'mysql:host='.$this->sHost.';port='.$this->iPort.';dbname='.$this->sDatabase;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
$oPdo = new \PDO($sDsn, $this->sUser, $this->sPassword);
|
$oPdo = new \PDO($sDsn, $this->sUser, $this->sPassword);
|
||||||
$oPdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
$oPdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||||
|
|
@ -258,7 +286,11 @@ class ChangePasswordPostfixAdminDriver implements \RainLoop\Providers\ChangePass
|
||||||
private function cryptPassword($sPassword, $oPdo)
|
private function cryptPassword($sPassword, $oPdo)
|
||||||
{
|
{
|
||||||
$sResult = '';
|
$sResult = '';
|
||||||
|
if (function_exists('random_bytes')) {
|
||||||
|
$sSalt = substr(base64_encode(random_bytes(32)), 0, 16);
|
||||||
|
} else {
|
||||||
$sSalt = substr(str_shuffle('./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'), 0, 16);
|
$sSalt = substr(str_shuffle('./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'), 0, 16);
|
||||||
|
}
|
||||||
switch (strtolower($this->sEncrypt))
|
switch (strtolower($this->sEncrypt))
|
||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
|
|
@ -289,6 +321,7 @@ class ChangePasswordPostfixAdminDriver implements \RainLoop\Providers\ChangePass
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'mysql_encrypt':
|
case 'mysql_encrypt':
|
||||||
|
if($this->sEngine == 'MySQL'){
|
||||||
$oStmt = $oPdo->prepare('SELECT ENCRYPT(?) AS encpass');
|
$oStmt = $oPdo->prepare('SELECT ENCRYPT(?) AS encpass');
|
||||||
if ($oStmt->execute(array($sPassword)))
|
if ($oStmt->execute(array($sPassword)))
|
||||||
{
|
{
|
||||||
|
|
@ -298,6 +331,9 @@ class ChangePasswordPostfixAdminDriver implements \RainLoop\Providers\ChangePass
|
||||||
$sResult = $aFetchResult[0]['encpass'];
|
$sResult = $aFetchResult[0]['encpass'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}else{
|
||||||
|
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CouldNotSaveNewPassword);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
1.2
|
1.3
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,13 @@ class PostfixadminChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
{
|
{
|
||||||
if (!extension_loaded('pdo') || !class_exists('PDO'))
|
if (!extension_loaded('pdo') || !class_exists('PDO'))
|
||||||
{
|
{
|
||||||
return 'The PHP exention PDO (mysql) must be installed to use this plugin';
|
return 'The PHP extension PDO must be installed to use this plugin';
|
||||||
}
|
}
|
||||||
|
|
||||||
$aDrivers = \PDO::getAvailableDrivers();
|
$aDrivers = \PDO::getAvailableDrivers();
|
||||||
if (!is_array($aDrivers) || !in_array('mysql', $aDrivers))
|
if (!is_array($aDrivers) || (!in_array('mysql', $aDrivers) && !in_array('pgsql', $aDrivers)))
|
||||||
{
|
{
|
||||||
return 'The PHP exention PDO (mysql) must be installed to use this plugin';
|
return 'The PHP extension PDO (mysql or pgsql) must be installed to use this plugin';
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
|
|
@ -41,6 +41,7 @@ class PostfixadminChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
$oProvider = new ChangePasswordPostfixAdminDriver();
|
$oProvider = new ChangePasswordPostfixAdminDriver();
|
||||||
|
|
||||||
$oProvider
|
$oProvider
|
||||||
|
->SetEngine($this->Config()->Get('plugin', 'engine',''))
|
||||||
->SetHost($this->Config()->Get('plugin', 'host', ''))
|
->SetHost($this->Config()->Get('plugin', 'host', ''))
|
||||||
->SetPort((int) $this->Config()->Get('plugin', 'port', 3306))
|
->SetPort((int) $this->Config()->Get('plugin', 'port', 3306))
|
||||||
->SetDatabase($this->Config()->Get('plugin', 'database', ''))
|
->SetDatabase($this->Config()->Get('plugin', 'database', ''))
|
||||||
|
|
@ -64,22 +65,26 @@ class PostfixadminChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
public function configMapping()
|
public function configMapping()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
\RainLoop\Plugins\Property::NewInstance('host')->SetLabel('MySQL Host')
|
\RainLoop\Plugins\Property::NewInstance('engine')->SetLabel('Engine')
|
||||||
|
->SetType(\RainLoop\Enumerations\PluginPropertyType::SELECTION)
|
||||||
|
->SetDefaultValue(array('MySQL', 'PostgreSQL'))
|
||||||
|
->SetDescription('Database Engine'),
|
||||||
|
\RainLoop\Plugins\Property::NewInstance('host')->SetLabel('Host')
|
||||||
->SetDefaultValue('127.0.0.1'),
|
->SetDefaultValue('127.0.0.1'),
|
||||||
\RainLoop\Plugins\Property::NewInstance('port')->SetLabel('MySQL Port')
|
\RainLoop\Plugins\Property::NewInstance('port')->SetLabel('Port')
|
||||||
->SetType(\RainLoop\Enumerations\PluginPropertyType::INT)
|
->SetType(\RainLoop\Enumerations\PluginPropertyType::INT)
|
||||||
->SetDefaultValue(3306),
|
->SetDefaultValue(3306),
|
||||||
\RainLoop\Plugins\Property::NewInstance('database')->SetLabel('MySQL Database')
|
\RainLoop\Plugins\Property::NewInstance('database')->SetLabel('Database')
|
||||||
->SetDefaultValue('postfixadmin'),
|
->SetDefaultValue('postfixadmin'),
|
||||||
\RainLoop\Plugins\Property::NewInstance('table')->SetLabel('MySQL table')
|
\RainLoop\Plugins\Property::NewInstance('table')->SetLabel('table')
|
||||||
->SetDefaultValue('mailbox'),
|
->SetDefaultValue('mailbox'),
|
||||||
\RainLoop\Plugins\Property::NewInstance('usercol')->SetLabel('MySQL username column')
|
\RainLoop\Plugins\Property::NewInstance('usercol')->SetLabel('username column')
|
||||||
->SetDefaultValue('username'),
|
->SetDefaultValue('username'),
|
||||||
\RainLoop\Plugins\Property::NewInstance('passcol')->SetLabel('MySQL password column')
|
\RainLoop\Plugins\Property::NewInstance('passcol')->SetLabel('password column')
|
||||||
->SetDefaultValue('password'),
|
->SetDefaultValue('password'),
|
||||||
\RainLoop\Plugins\Property::NewInstance('user')->SetLabel('MySQL User')
|
\RainLoop\Plugins\Property::NewInstance('user')->SetLabel('User')
|
||||||
->SetDefaultValue('postfixadmin'),
|
->SetDefaultValue('postfixadmin'),
|
||||||
\RainLoop\Plugins\Property::NewInstance('password')->SetLabel('MySQL Password')
|
\RainLoop\Plugins\Property::NewInstance('password')->SetLabel('Password')
|
||||||
->SetType(\RainLoop\Enumerations\PluginPropertyType::PASSWORD)
|
->SetType(\RainLoop\Enumerations\PluginPropertyType::PASSWORD)
|
||||||
->SetDefaultValue(''),
|
->SetDefaultValue(''),
|
||||||
\RainLoop\Plugins\Property::NewInstance('encrypt')->SetLabel('Encrypt')
|
\RainLoop\Plugins\Property::NewInstance('encrypt')->SetLabel('Encrypt')
|
||||||
|
|
|
||||||
20
plugins/rest-change-password/LICENSE
Normal file
20
plugins/rest-change-password/LICENSE
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2013 RainLoop Team
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
1
plugins/rest-change-password/README
Normal file
1
plugins/rest-change-password/README
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Plugin that adds functionality to change the email account password (Generic REST).
|
||||||
172
plugins/rest-change-password/RestChangePasswordDriver.php
Normal file
172
plugins/rest-change-password/RestChangePasswordDriver.php
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class RestChangePasswordDriver implements \RainLoop\Providers\ChangePassword\ChangePasswordInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $sUrl = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $sKey = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $sFieldEmail = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $sFieldOldpassword = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $sFieldNewpassword = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $sAllowedEmails = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \MailSo\Log\Logger
|
||||||
|
*/
|
||||||
|
private $oLogger = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sHost
|
||||||
|
* @param int $iPort
|
||||||
|
*
|
||||||
|
* @return \RestChangePasswordDriver
|
||||||
|
*/
|
||||||
|
public function SetConfig($sUrl, $sKey)
|
||||||
|
{
|
||||||
|
$this->sUrl = $sUrl;
|
||||||
|
$this->sKey = $sKey;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$oProvider->SetFieldNames($sFieldEmail, $sFieldOldpassword, $sFieldNewpassword);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sFieldEmail
|
||||||
|
* @param string $sFieldOldpassword
|
||||||
|
* @param string $sFieldNewpassword
|
||||||
|
*
|
||||||
|
* @return \RestChangePasswordDriver
|
||||||
|
*/
|
||||||
|
public function SetFieldNames($sFieldEmail, $sFieldOldpassword, $sFieldNewpassword)
|
||||||
|
{
|
||||||
|
$this->sFieldEmail = $sFieldEmail;
|
||||||
|
$this->sFieldOldpassword = $sFieldOldpassword;
|
||||||
|
$this->sFieldNewpassword = $sFieldNewpassword;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sAllowedEmails
|
||||||
|
*
|
||||||
|
* @return \RestChangePasswordDriver
|
||||||
|
*/
|
||||||
|
public function SetAllowedEmails($sAllowedEmails)
|
||||||
|
{
|
||||||
|
$this->sAllowedEmails = $sAllowedEmails;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \MailSo\Log\Logger $oLogger
|
||||||
|
*
|
||||||
|
* @return \RestChangePasswordDriver
|
||||||
|
*/
|
||||||
|
public function SetLogger($oLogger)
|
||||||
|
{
|
||||||
|
if ($oLogger instanceof \MailSo\Log\Logger)
|
||||||
|
{
|
||||||
|
$this->oLogger = $oLogger;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \RainLoop\Account $oAccount
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function PasswordChangePossibility($oAccount)
|
||||||
|
{
|
||||||
|
return $oAccount && $oAccount->Email() &&
|
||||||
|
\RainLoop\Plugins\Helper::ValidateWildcardValues($oAccount->Email(), $this->sAllowedEmails);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \RainLoop\Account $oAccount
|
||||||
|
* @param string $sPrevPassword
|
||||||
|
* @param string $sNewPassword
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function ChangePassword(\RainLoop\Account $oAccount, $sPrevPassword, $sNewPassword)
|
||||||
|
{
|
||||||
|
if ($this->oLogger)
|
||||||
|
{
|
||||||
|
$this->oLogger->Write('Rest: Try to change password for '.$oAccount->Email());
|
||||||
|
}
|
||||||
|
|
||||||
|
$bResult = false;
|
||||||
|
if (!empty($this->sHost) && 0 < $this->iPort && $oAccount)
|
||||||
|
{
|
||||||
|
$sEmail = \trim(\strtolower($oAccount->Email()));
|
||||||
|
|
||||||
|
# Adding the REST Api key to the url, try to use always https
|
||||||
|
$sUrl = str_replace('://', '://'+$this->sKey+"@", $this->sUrl);
|
||||||
|
|
||||||
|
$iCode = 0;
|
||||||
|
$oHttp = \MailSo\Base\Http::SingletonInstance();
|
||||||
|
|
||||||
|
if ($this->oLogger)
|
||||||
|
{
|
||||||
|
$this->oLogger->Write('Rest[Api Request]:'.$sUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
$mResult = $oHttp->SendPostRequest($sUrl,
|
||||||
|
array(
|
||||||
|
$this->sFieldEmail => $sEmail,
|
||||||
|
$this->sFieldOldpassword => $sPrevPassword,
|
||||||
|
$this->sFieldNewpassword => $sNewPassword,
|
||||||
|
), 'MailSo Http User Agent (v1)', $iCode, $this->oLogger);
|
||||||
|
|
||||||
|
if (false !== $mResult && 200 === $iCode)
|
||||||
|
{
|
||||||
|
$aRes = null;
|
||||||
|
@\parse_str($mResult, $aRes);
|
||||||
|
if (is_array($aRes) && (!isset($aRes['error']) || (int) $aRes['error'] !== 1))
|
||||||
|
{
|
||||||
|
$bResult = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ($this->oLogger)
|
||||||
|
{
|
||||||
|
$this->oLogger->Write('Rest[Error]: Response: '.$mResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ($this->oLogger)
|
||||||
|
{
|
||||||
|
$this->oLogger->Write('Rest[Error]: Empty Response: Code:'.$iCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
plugins/rest-change-password/VERSION
Normal file
1
plugins/rest-change-password/VERSION
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
1.0
|
||||||
78
plugins/rest-change-password/index.php
Normal file
78
plugins/rest-change-password/index.php
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class RestChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
|
{
|
||||||
|
public function Init()
|
||||||
|
{
|
||||||
|
$this->addHook('main.fabrica', 'MainFabrica');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sName
|
||||||
|
* @param mixed $oProvider
|
||||||
|
*/
|
||||||
|
public function MainFabrica($sName, &$oProvider)
|
||||||
|
{
|
||||||
|
switch ($sName)
|
||||||
|
{
|
||||||
|
case 'change-password':
|
||||||
|
|
||||||
|
$sUrl = \trim($this->Config()->Get('plugin', 'rest_url', ''));
|
||||||
|
$sKey = \trim($this->Config()->Get('plugin', 'rest_key', ''));
|
||||||
|
|
||||||
|
$sFieldEmail = \trim($this->Config()->Get('plugin', 'rest_field_email', ''));
|
||||||
|
$sFieldOldpassword = \trim($this->Config()->Get('plugin', 'rest_field_oldpassword', ''));
|
||||||
|
$sFieldNewpassword = \trim($this->Config()->Get('plugin', 'rest_field_newpassword', ''));
|
||||||
|
|
||||||
|
if (!empty($sHost) && (!empty($sKey)))
|
||||||
|
{
|
||||||
|
include_once __DIR__.'/RestChangePasswordDriver.php';
|
||||||
|
|
||||||
|
$oProvider = new RestChangePasswordDriver();
|
||||||
|
$oProvider->SetLogger($this->Manager()->Actions()->Logger());
|
||||||
|
$oProvider->SetConfig($sUrl, $sKey);
|
||||||
|
$oProvider->SetFieldNames($sFieldEmail, $sFieldOldpassword, $sFieldNewpassword);
|
||||||
|
$oProvider->SetAllowedEmails(\strtolower(\trim($this->Config()->Get('plugin', 'allowed_emails', ''))));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function configMapping()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
\RainLoop\Plugins\Property::NewInstance('rest_url')
|
||||||
|
->SetLabel('REST API Url')
|
||||||
|
->SetDefaultValue('')
|
||||||
|
->SetDescription('Ex: http://localhost:8080/api/change_password or https://domain.com/api/user/passsword_update'),
|
||||||
|
\RainLoop\Plugins\Property::NewInstance('rest_key')
|
||||||
|
->SetLabel('REST API key')
|
||||||
|
->SetType(\RainLoop\Enumerations\PluginPropertyType::PASSWORD)
|
||||||
|
->SetDescription('REST API Key for authentication, if you have "user" and "passsword" enter it as "user:password"')
|
||||||
|
->SetDefaultValue(''),
|
||||||
|
\RainLoop\Plugins\Property::NewInstance('rest_field_email')
|
||||||
|
->SetLabel('Field "email" name')
|
||||||
|
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
|
||||||
|
->SetDescription('Enter the name of the REST field name for email')
|
||||||
|
->SetDefaultValue('email'),
|
||||||
|
\RainLoop\Plugins\Property::NewInstance('rest_field_oldpassword')
|
||||||
|
->SetLabel('Field "oldpassword" name')
|
||||||
|
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
|
||||||
|
->SetDescription('Enter the name of the REST field name for oldpassword')
|
||||||
|
->SetDefaultValue('oldpassword'),
|
||||||
|
\RainLoop\Plugins\Property::NewInstance('rest_field_newpassword')
|
||||||
|
->SetLabel('Field "newpassword" name')
|
||||||
|
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
|
||||||
|
->SetDescription('Enter the name of the REST field name for newpassword')
|
||||||
|
->SetDefaultValue('newpassword'),
|
||||||
|
\RainLoop\Plugins\Property::NewInstance('allowed_emails')->SetLabel('Allowed emails')
|
||||||
|
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
|
||||||
|
->SetDescription('Allowed emails, space as delimiter, wildcard supported. Example: user1@domain1.net user2@domain1.net *@domain2.net')
|
||||||
|
->SetDefaultValue('*')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -42,7 +42,7 @@ class VestaChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
return array(
|
return array(
|
||||||
\RainLoop\Plugins\Property::NewInstance('vesta_host')->SetLabel('Vesta Host')
|
\RainLoop\Plugins\Property::NewInstance('vesta_host')->SetLabel('Vesta Host')
|
||||||
->SetDefaultValue('')
|
->SetDefaultValue('')
|
||||||
->SetDescription('Allowed patterns: {user:host-imap}, {user:host-smtp}, {user:domain}'),
|
->SetDescription('Ex: localhost or domain.com'),
|
||||||
\RainLoop\Plugins\Property::NewInstance('Vesta_port')->SetLabel('Vesta Port')
|
\RainLoop\Plugins\Property::NewInstance('Vesta_port')->SetLabel('Vesta Port')
|
||||||
->SetType(\RainLoop\Enumerations\PluginPropertyType::INT)
|
->SetType(\RainLoop\Enumerations\PluginPropertyType::INT)
|
||||||
->SetDefaultValue(8083),
|
->SetDefaultValue(8083),
|
||||||
|
|
|
||||||
|
|
@ -1 +1,9 @@
|
||||||
Deny from all
|
<ifModule mod_authz_core.c>
|
||||||
|
Require all denied
|
||||||
|
</ifModule>
|
||||||
|
<ifModule !mod_authz_core.c>
|
||||||
|
Deny from all
|
||||||
|
</ifModule>
|
||||||
|
<IfModule mod_autoindex.c>
|
||||||
|
Options -Indexes
|
||||||
|
</ifModule>
|
||||||
|
|
@ -22,7 +22,7 @@ if (!\defined('RAINLOOP_APP_LIBRARIES_PATH'))
|
||||||
function rainLoopSplAutoloadNamespaces()
|
function rainLoopSplAutoloadNamespaces()
|
||||||
{
|
{
|
||||||
return RAINLOOP_INCLUDE_AS_API_DEF ? array('RainLoop', 'Predis') :
|
return RAINLOOP_INCLUDE_AS_API_DEF ? array('RainLoop', 'Predis') :
|
||||||
array('RainLoop', 'Facebook', 'GuzzleHttp', 'PHPThumb', 'Predis', 'SabreForRainLoop', 'Imagine', 'Detection');
|
array('RainLoop', 'Facebook', 'PHPThumb', 'Predis', 'SabreForRainLoop', 'Imagine', 'Detection');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace GuzzleHttp\Adapter;
|
|
||||||
|
|
||||||
use GuzzleHttp\Message\ResponseInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adapter interface used to transfer HTTP requests.
|
|
||||||
*
|
|
||||||
* @link http://docs.guzzlephp.org/en/guzzle4/adapters.html for a full
|
|
||||||
* explanation of adapters and their responsibilities.
|
|
||||||
*/
|
|
||||||
interface AdapterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Transfers an HTTP request and populates a response
|
|
||||||
*
|
|
||||||
* @param TransactionInterface $transaction Transaction abject to populate
|
|
||||||
*
|
|
||||||
* @return ResponseInterface
|
|
||||||
*/
|
|
||||||
public function send(TransactionInterface $transaction);
|
|
||||||
}
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace GuzzleHttp\Adapter\Curl;
|
|
||||||
|
|
||||||
use GuzzleHttp\Adapter\TransactionInterface;
|
|
||||||
use GuzzleHttp\Exception\AdapterException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides context for a Curl transaction, including active handles,
|
|
||||||
* pending transactions, and whether or not this is a batch or single
|
|
||||||
* transaction.
|
|
||||||
*/
|
|
||||||
class BatchContext
|
|
||||||
{
|
|
||||||
/** @var resource Curl multi resource */
|
|
||||||
private $multi;
|
|
||||||
|
|
||||||
/** @var \SplObjectStorage Map of transactions to curl resources */
|
|
||||||
private $handles;
|
|
||||||
|
|
||||||
/** @var \Iterator Yields pending transactions */
|
|
||||||
private $pending;
|
|
||||||
|
|
||||||
/** @var bool Whether or not to throw transactions */
|
|
||||||
private $throwsExceptions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param resource $multiHandle Initialized curl_multi resource
|
|
||||||
* @param bool $throwsExceptions Whether or not exceptions are thrown
|
|
||||||
* @param \Iterator $pending Iterator yielding pending transactions
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
$multiHandle,
|
|
||||||
$throwsExceptions,
|
|
||||||
\Iterator $pending = null
|
|
||||||
) {
|
|
||||||
$this->multi = $multiHandle;
|
|
||||||
$this->handles = new \SplObjectStorage();
|
|
||||||
$this->throwsExceptions = $throwsExceptions;
|
|
||||||
$this->pending = $pending;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a transaction for a given curl handle
|
|
||||||
*
|
|
||||||
* @param resource $handle Curl handle
|
|
||||||
*
|
|
||||||
* @return TransactionInterface
|
|
||||||
* @throws AdapterException if a transaction is not found
|
|
||||||
*/
|
|
||||||
public function findTransaction($handle)
|
|
||||||
{
|
|
||||||
foreach ($this->handles as $transaction) {
|
|
||||||
if ($this->handles[$transaction] === $handle) {
|
|
||||||
return $transaction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new AdapterException('No curl handle was found');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if there are any remaining pending transactions
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function hasPending()
|
|
||||||
{
|
|
||||||
return $this->pending && $this->pending->valid();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pop the next transaction from the transaction queue
|
|
||||||
*
|
|
||||||
* @return TransactionInterface|null
|
|
||||||
*/
|
|
||||||
public function nextPending()
|
|
||||||
{
|
|
||||||
if (!$this->hasPending()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$current = $this->pending->current();
|
|
||||||
$this->pending->next();
|
|
||||||
|
|
||||||
return $current;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the batch is to throw exceptions on error
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function throwsExceptions()
|
|
||||||
{
|
|
||||||
return $this->throwsExceptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the curl_multi handle
|
|
||||||
*
|
|
||||||
* @return resource
|
|
||||||
*/
|
|
||||||
public function getMultiHandle()
|
|
||||||
{
|
|
||||||
return $this->multi;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a transaction to the multi handle
|
|
||||||
*
|
|
||||||
* @param TransactionInterface $transaction Transaction to add
|
|
||||||
* @param resource $handle Resource to use with the handle
|
|
||||||
*
|
|
||||||
* @throws AdapterException If the handle is already registered
|
|
||||||
*/
|
|
||||||
public function addTransaction(TransactionInterface $transaction, $handle)
|
|
||||||
{
|
|
||||||
if (isset($this->handles[$transaction])) {
|
|
||||||
throw new AdapterException('Transaction already registered');
|
|
||||||
}
|
|
||||||
|
|
||||||
$code = curl_multi_add_handle($this->multi, $handle);
|
|
||||||
if ($code != CURLM_OK) {
|
|
||||||
MultiAdapter::throwMultiError($code);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->handles[$transaction] = $handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a transaction and associated handle from the context
|
|
||||||
*
|
|
||||||
* @param TransactionInterface $transaction Transaction to remove
|
|
||||||
*
|
|
||||||
* @return array Returns the curl_getinfo array
|
|
||||||
* @throws AdapterException if the transaction is not found
|
|
||||||
*/
|
|
||||||
public function removeTransaction(TransactionInterface $transaction)
|
|
||||||
{
|
|
||||||
if (!isset($this->handles[$transaction])) {
|
|
||||||
throw new AdapterException('Transaction not registered');
|
|
||||||
}
|
|
||||||
|
|
||||||
$handle = $this->handles[$transaction];
|
|
||||||
|
|
||||||
$code = curl_multi_remove_handle($this->multi, $handle);
|
|
||||||
if ($code != CURLM_OK) {
|
|
||||||
MultiAdapter::throwMultiError($code);
|
|
||||||
}
|
|
||||||
|
|
||||||
$info = curl_getinfo($handle);
|
|
||||||
curl_close($handle);
|
|
||||||
unset($this->handles[$transaction]);
|
|
||||||
|
|
||||||
return $info;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue